Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Added password hashing explanation
  • Loading branch information
Phin Jensen committed Jul 2, 2014
1 parent e9dfc39 commit bb849f0
Show file tree
Hide file tree
Showing 2 changed files with 380 additions and 0 deletions.
190 changes: 190 additions & 0 deletions glossary/UserDB
Expand Up @@ -120,6 +120,196 @@ UserDB default logfile var/log/userdb.log
</para>
</section>

<section>
<title>Password hashing</title>
<para>Interchange has support for SHA1 password hashing.</para>
<para>Use of SHA1 passwords can be specified in the same manner
as currently MD5 can be:

<programlisting>
UserDB ui sha1 1
</programlisting>
</para>

<para>There is also a "promote" feature. When active, and passwords
of any of the other algorithms are present, on next
login the user's password will be promoted to the
target hashing algorithm. This way, password strength
can be increased organically.</para>

<para>To utilize the promotion feature, you add a similar line
for the UserDB definition:

<programlisting>
UserDB ui promote 1
</programlisting>
</para>

<para>Promote implies that strength is increased, but in reality
promotion will move in any direction desired. The requested
hashing algorithm is the target, and whatever the form of the
passwords in the database, they will be converted to the target.</para>

<para>E.g., if neither sha1 nor md5 is specified, and the database
currently has md5 passwords, if promote is added, it will have
the effect of promoting to crypt(), the target hashing algorithm
(which happens to be the default).</para>

<para>If promote is not used, the change is fully backward compatible.
Whatever method is specified will be used, and if the database
has passwords of a different algorithm, authentication will fail.</para>

<para>You should not specify more than 1 hashing type. If you specify
both md5 and sha1, you'll be subject to the whims of hash
ordering from keys().</para>

<para>Also note that, before promoting to a stronger hash, you should
ensure your database's password field is long enough to hold the
new, longer datum.</para>

<para>Interchange also has full bcrypt support. Bcrypt support requires
the modules Digest::Bcrypt and Crypt::Random. You can enable it with
the "bcrypt key in catalog UserDB setting. Example: <literal>UserDB default bcrypt 1</literal></para>

<para>Pads out passwords to 72-character limit of bcrypt to increase
difficulty of brute-forcing weak passwords. Optional "pepper" (highly
recommended) is available to make padding pattern unique per catalog.</para>

<para>Defaults to cost of 13.</para>

<para>Storage follows general guidelines of modular crypt format
against the password (discussed below). Example storage structure:
<literal>$2y$14$F4PQQ6QTuRFo0FBAYP1rhQIqJSTg7iHSS619fmiAOhvk5b5Ui8o6o</literal>
</para>

<para>Interchange uses a "more complex than usual" approach to manage
the identifier than the standard MCF. This complexity is used to
specify which algorithm "pre digested" the raw password. They
are as follows:
<itemizedlist>
<listitem><para>
$2y$ - standard, default identifier. Means bcrypt processed
the raw password directly.
</para></listitem>
<listitem><para>
$2s$ - s => SHA1. Indicates bcrypt process first runs the raw
password through the SHA1 algorithm before encrypting. If you
update passwords originally stored as SHA1 as a background
process, the resulting bcrypt structures should all have this
identifier. Example storage structure: <literal>$2s$14$F4PQQ6QTuRFo0FBAYP1rhQIqJSTg7iHSS619fmiAOhvk5b5Ui8o6o</literal>
</para></listitem>
<listitem><para>
$2m$ - m => MD5. Same as $2s$ but for passwords that are
originally stored MD5. Example storage structure: <literal>$2m$14$iJ7kMcGiNXRvBTRBIHVrmw1Rfq224SXd0QzSsKOupop4nZTVhEotA</literal>
</para></listitem>
<listitem><para>
$2n$..$ - n => md5_salted encryption algorithm. '..' are the 2
salt characters in the original stored password, made available
so that the "pre digest" step can accurately reproduce the
salted MD5 structure before bcrypting and comparing. Example storage structure:
<literal>$2n$jQ$14$MZjidwOjuROki9TXdJofsgp2ne2Vrm6JJtLcF+0f51mE1ncee0XZk</literal>
</para></listitem>
<listitem><para>
$2c$..$ - c => crypt(). Same as md5_salted, but with crypt()instead.
Example storage structure: <literal>$2c$m4$14$QeCj3irfIJOWoWKHUtNpUQVxwXl8Sl4zRo79d7BRPQpDTSlaCTJv0
</para></listitem>
</itemizedlist>
</para>

<para>The "pre digested" feature allows a site developer to create
a background process for updating an existing user table with
bcrypted passwords even if the table is already encrypted by
one of the previously supported ciphers. Thus, in a matter of
minutes to weeks (depending on the size of your user table and
chosen bcrypt cost) your passwords can be fully upgraded to
bcrypt without having to wait on the organic process "promote"
allows, or having to know any of your users' original
passwords.</para>

<para>Routine construct_bcrypt() in Vend::UserDB. Takes a
single hash ref argument with keys "password", "type"
(optional), and "profile" (optional). Returns a
properly-formatted bcrypt structure suitable for being stored
in the password field of the user table of interest.</para>

<para>Anticipated usage scenario would be for a developer with an
already encrypted user table (sha1, md5, md5_salted, or crypt)
to create an Interchange job that slurps in all the encrypted
passwords, passes them along with the type of encryption that
created them (described below), and gets in return the
appropriate bcrypt structure reflecting that original
encryption type to write back to the user table's password
field.</para>

<para>
If "type" is left off, assumes code is encrypting against
the raw password. Returns structure with identifier $2y$.
Otherwise, "type" is any of the supported Interchange
encryption options:
<itemizedlist>
<listitem><para>sha1 (identifier returned is $2s$)</para></listitem>
<listitem><para>md5 (identifier returned is $2m$)</para></listitem>
<listitem><para>md5_salted (identifier returned is $2n$..$)</para></listitem>
<listitem><para>crypt (identifier returned is $2c$..$)</para></listitem>
</itemizedlist>
</para>

<para>If "profile" is left off, uses "default" profile, which
is typically the definition for the userdb table. Common
other profile is "ui", which defines the access table for
the admin.</para>

<para>Whatever profile is being used, it must have been set to use
bcrypt before executing code that calls construct_bcrypt().
If it's set to anything other than bcrypt, the routine dies
with an error.</para>

<para>Example usage: if my "ui" profile is configured with
"crypt" (as it is by default), I have crypt() passwords in
the access table:

<programlisting>
UserDB ui crypt 1
</programlisting>
</para>

<para>I first change and promote to bcrypt by replacing the above
with:

<programlisting>
UserDB ui promote 1
UserDB ui bcrypt 1
UserDB ui bcrypt_pepper {some reasonably long random string}
</programlisting>
</para>

<para>Then, rather than wait for every user to eventually log
in, I run all my crypt passwords through construct_bcrypt().
If I have, for example, a password of cWNLm21WqgOKU:

<programlisting>
my $bcrypt_password = Vend::UserDB::construct_bcrypt(
{
password => 'cWNLm21WqgOKU',
type => 'crypt',
profile => 'ui',
}
)
</programlisting>

and $bcrypt_password now holds something like:
<literal>$2c$cW$14$QeCj3irfIJOWoWKHUtNpUQVxwXl8Sl4zRo79d7BRPQpDTSlaCTJv0</literal>
which can directly overwrite cWNLm21WqgOKU in the password
field.</para>

<para>The "promote" flag has been expanded to recognize intra-bcrypt
config changes between the cost of a stored password and the
current cost being used for encryption. E.g., if the current
cost setting for bcrypt is 14, but the storage structure
indicates $2y$13$..., promote catches that and updates the
password in the database to the calculated structure for cost.</para>
</section>

<section>
<title>Creating the login page</title>
<para>
Expand Down
190 changes: 190 additions & 0 deletions glossary/hashing
@@ -0,0 +1,190 @@
<section>
<title>Password hashing</title>
<para>Interchange has support for SHA1 password hashing.</para>
<para>Use of SHA1 passwords can be specified in the same manner
as currently MD5 can be:

<programlisting>
UserDB ui sha1 1
</programlisting>
</para>

<para>There is also a "promote" feature. When active, and passwords
of any of the other algorithms are present, on next
login the user's password will be promoted to the
target hashing algorithm. This way, password strength
can be increased organically.</para>

<para>To utilize the promotion feature, you add a similar line
for the UserDB definition:

<programlisting>
UserDB ui promote 1
</programlisting>
</para>

<para>Promote implies that strength is increased, but in reality
promotion will move in any direction desired. The requested
hashing algorithm is the target, and whatever the form of the
passwords in the database, they will be converted to the target.</para>

<para>E.g., if neither sha1 nor md5 is specified, and the database
currently has md5 passwords, if promote is added, it will have
the effect of promoting to crypt(), the target hashing algorithm
(which happens to be the default).</para>

<para>If promote is not used, the change is fully backward compatible.
Whatever method is specified will be used, and if the database
has passwords of a different algorithm, authentication will fail.</para>

<para>You should not specify more than 1 hashing type. If you specify
both md5 and sha1, you'll be subject to the whims of hash
ordering from keys().</para>

<para>Also note that, before promoting to a stronger hash, you should
ensure your database's password field is long enough to hold the
new, longer datum.</para>

<para>Interchange also has full bcrypt support. Bcrypt support requires
the modules Digest::Bcrypt and Crypt::Random. You can enable it with
the "bcrypt key in catalog UserDB setting. Example: <literal>UserDB default bcrypt 1</literal></para>

<para>Pads out passwords to 72-character limit of bcrypt to increase
difficulty of brute-forcing weak passwords. Optional "pepper" (highly
recommended) is available to make padding pattern unique per catalog.</para>

<para>Defaults to cost of 13.</para>

<para>Storage follows general guidelines of modular crypt format
against the password (discussed below). Example storage structure:
<literal>$2y$14$F4PQQ6QTuRFo0FBAYP1rhQIqJSTg7iHSS619fmiAOhvk5b5Ui8o6o</literal>
</para>

<para>Interchange uses a "more complex than usual" approach to manage
the identifier than the standard MCF. This complexity is used to
specify which algorithm "pre digested" the raw password. They
are as follows:
<itemizedlist>
<listitem><para>
$2y$ - standard, default identifier. Means bcrypt processed
the raw password directly.
</para></listitem>
<listitem><para>
$2s$ - s => SHA1. Indicates bcrypt process first runs the raw
password through the SHA1 algorithm before encrypting. If you
update passwords originally stored as SHA1 as a background
process, the resulting bcrypt structures should all have this
identifier. Example storage structure: <literal>$2s$14$F4PQQ6QTuRFo0FBAYP1rhQIqJSTg7iHSS619fmiAOhvk5b5Ui8o6o</literal>
</para></listitem>
<listitem><para>
$2m$ - m => MD5. Same as $2s$ but for passwords that are
originally stored MD5. Example storage structure: <literal>$2m$14$iJ7kMcGiNXRvBTRBIHVrmw1Rfq224SXd0QzSsKOupop4nZTVhEotA</literal>
</para></listitem>
<listitem><para>
$2n$..$ - n => md5_salted encryption algorithm. '..' are the 2
salt characters in the original stored password, made available
so that the "pre digest" step can accurately reproduce the
salted MD5 structure before bcrypting and comparing. Example storage structure:
<literal>$2n$jQ$14$MZjidwOjuROki9TXdJofsgp2ne2Vrm6JJtLcF+0f51mE1ncee0XZk</literal>
</para></listitem>
<listitem><para>
$2c$..$ - c => crypt(). Same as md5_salted, but with crypt()instead.
Example storage structure: <literal>$2c$m4$14$QeCj3irfIJOWoWKHUtNpUQVxwXl8Sl4zRo79d7BRPQpDTSlaCTJv0
</para></listitem>
</itemizedlist>
</para>

<para>The "pre digested" feature allows a site developer to create
a background process for updating an existing user table with
bcrypted passwords even if the table is already encrypted by
one of the previously supported ciphers. Thus, in a matter of
minutes to weeks (depending on the size of your user table and
chosen bcrypt cost) your passwords can be fully upgraded to
bcrypt without having to wait on the organic process "promote"
allows, or having to know any of your users' original
passwords.</para>

<para>Routine construct_bcrypt() in Vend::UserDB. Takes a
single hash ref argument with keys "password", "type"
(optional), and "profile" (optional). Returns a
properly-formatted bcrypt structure suitable for being stored
in the password field of the user table of interest.</para>

<para>Anticipated usage scenario would be for a developer with an
already encrypted user table (sha1, md5, md5_salted, or crypt)
to create an Interchange job that slurps in all the encrypted
passwords, passes them along with the type of encryption that
created them (described below), and gets in return the
appropriate bcrypt structure reflecting that original
encryption type to write back to the user table's password
field.</para>

<para>
If "type" is left off, assumes code is encrypting against
the raw password. Returns structure with identifier $2y$.
Otherwise, "type" is any of the supported Interchange
encryption options:
<itemizedlist>
<listitem><para>sha1 (identifier returned is $2s$)</para></listitem>
<listitem><para>md5 (identifier returned is $2m$)</para></listitem>
<listitem><para>md5_salted (identifier returned is $2n$..$)</para></listitem>
<listitem><para>crypt (identifier returned is $2c$..$)</para></listitem>
</itemizedlist>
</para>

<para>If "profile" is left off, uses "default" profile, which
is typically the definition for the userdb table. Common
other profile is "ui", which defines the access table for
the admin.</para>

<para>Whatever profile is being used, it must have been set to use
bcrypt before executing code that calls construct_bcrypt().
If it's set to anything other than bcrypt, the routine dies
with an error.</para>

<para>Example usage: if my "ui" profile is configured with
"crypt" (as it is by default), I have crypt() passwords in
the access table:

<programlisting>
UserDB ui crypt 1
</programlisting>
</para>

<para>I first change and promote to bcrypt by replacing the above
with:

<programlisting>
UserDB ui promote 1
UserDB ui bcrypt 1
UserDB ui bcrypt_pepper {some reasonably long random string}
</programlisting>
</para>

<para>Then, rather than wait for every user to eventually log
in, I run all my crypt passwords through construct_bcrypt().
If I have, for example, a password of cWNLm21WqgOKU:

<programlisting>
my $bcrypt_password = Vend::UserDB::construct_bcrypt(
{
password => 'cWNLm21WqgOKU',
type => 'crypt',
profile => 'ui',
}
)
</programlisting>

and $bcrypt_password now holds something like:
<literal>$2c$cW$14$QeCj3irfIJOWoWKHUtNpUQVxwXl8Sl4zRo79d7BRPQpDTSlaCTJv0</literal>
which can directly overwrite cWNLm21WqgOKU in the password
field.</para>

<para>The "promote" flag has been expanded to recognize intra-bcrypt
config changes between the cost of a stored password and the
current cost being used for encryption. E.g., if the current
cost setting for bcrypt is 14, but the storage structure
indicates $2y$13$..., promote catches that and updates the
password in the database to the calculated structure for cost.</para>
</section>

0 comments on commit bb849f0

Please sign in to comment.