SEC-2122: Change doc to prioritize bcrypt use

This commit is contained in:
Luke Taylor 2013-05-17 18:42:47 +01:00
parent 34893cd53a
commit d6524feb62
5 changed files with 57 additions and 44 deletions

View File

@ -17,6 +17,9 @@ package org.springframework.security.crypto.password;
/** /**
* Service interface for encoding passwords. * Service interface for encoding passwords.
*
* The preferred implementation is {@code BCryptPasswordEncoder}.
*
* @author Keith Donald * @author Keith Donald
*/ */
public interface PasswordEncoder { public interface PasswordEncoder {

View File

@ -28,6 +28,9 @@ import org.springframework.security.crypto.keygen.KeyGenerators;
* random 8-byte random salt value. It uses an additional system-wide secret value to provide additional protection. * random 8-byte random salt value. It uses an additional system-wide secret value to provide additional protection.
* <p> * <p>
* The digest algorithm is invoked on the concatenated bytes of the salt, secret and password. * The digest algorithm is invoked on the concatenated bytes of the salt, secret and password.
* <p>
* If you are developing a new system, {@link org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder} is
* a better choice both in terms of security and interoperability with other languages.
* *
* @author Keith Donald * @author Keith Donald
* @author Luke Taylor * @author Luke Taylor

View File

@ -219,14 +219,27 @@
<title>Password Encoding</title> <title>Password Encoding</title>
<para xlink:href="#spring-security-crypto-passwordencoders">Spring Security's <para xlink:href="#spring-security-crypto-passwordencoders">Spring Security's
<interfacename>PasswordEncoder</interfacename> interface is used to support the use of <interfacename>PasswordEncoder</interfacename> interface is used to support the use of
passwords which are encoded in some way in persistent storage. This will normally mean passwords which are encoded in some way in persistent storage. You should never store
that the passwords are <quote>hashed</quote> using a digest algorithm such as MD5 or passwords in plain text. Always use a one-way password hashing algorithm such as bcrypt
SHA. Spring Security 3.1's <link xlink:href="#spring-security-crypto-passwordencoders" which uses a built-in salt value which is different for each stored password. Do not use
><literal>crypto</literal></link> package introduces a simpler API which encourages a plain hash function such as MD5 or SHA, or even a salted version. Bcrypt is deliberately
best-practice for password hashing. We would encourage you to use these APIs for new designed to be slow and to hinder offline password cracking, whereas standard hash algorithms
development and regard the classes in package are fast and can easily be used to test thousands of passwords in parallel on custom
<literal>org.springframework.security.authentication.encoding</literal> as legacy hardware. You might think this doesn't apply to you since your password database is
implementations. The <classname>DaoAuthenticationProvider</classname> can be injected secure and offline attacks aren't a risk. If so, do some research and read up on all
the high-profile sites which have been compromised in this way and have been pilloried
for storing their passwords insecurely. It's best to be on the safe side. Using
<code>org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"</code>
is a good choice for security. There are also compatible implementations in other common
programming languages so it a good choice for interoperability too.</para>
<para>
If you are using a legacy system which already has hashed passwords, then you will
need to use an encoder which matches your current algorithm, at least until you can
migrate your users to a more secure scheme (usually this will involve asking the user
to set a new password, since hashes are irreversible). Spring Security has a package
containing legacy password encoding implementation, namely,
<literal>org.springframework.security.authentication.encoding</literal>.
The <classname>DaoAuthenticationProvider</classname> can be injected
with either the new or legacy <interfacename>PasswordEncoder</interfacename> with either the new or legacy <interfacename>PasswordEncoder</interfacename>
types.</para> types.</para>
<section> <section>
@ -251,7 +264,8 @@
<title>Adding Salt to a Hash</title> <title>Adding Salt to a Hash</title>
<para> One potential problem with the use of password hashes that it is relatively easy <para> One potential problem with the use of password hashes that it is relatively easy
to get round the one-way property of the hash if a common word is used for the to get round the one-way property of the hash if a common word is used for the
input. For example, if you search for the hash value input. People tend to choose similar passwords and huge dictionaries of these from
previously hacked sites are available online. For example, if you search for the hash value
<literal>5f4dcc3b5aa765d61d8327deb882cf99</literal> using google, you will quickly <literal>5f4dcc3b5aa765d61d8327deb882cf99</literal> using google, you will quickly
find the original word <quote>password</quote>. In a similar way, an attacker can find the original word <quote>password</quote>. In a similar way, an attacker can
build a dictionary of hashes from a standard word list and use this to lookup the build a dictionary of hashes from a standard word list and use this to lookup the
@ -263,19 +277,16 @@
value is usually preferable to none. Using a salt means that an attacker has to value is usually preferable to none. Using a salt means that an attacker has to
build a separate dictionary of hashes for each salt value, making the attack more build a separate dictionary of hashes for each salt value, making the attack more
complicated (but not impossible).</para> complicated (but not impossible).</para>
<para>The <classname>StandardPasswordEncoder</classname> in the <link <para>Bcrypt automatically generates a random salt value for each password when it
xlink:href="#spring-security-crypto-passwordencoders" is encoded, and stores it in the bcrypt string in a standard format.
><literal>crypto</literal></link> package uses a random 8-byte salt, which is stored <note><para>The legacy approach to handling salt was to inject a
in the same field as the password.<note>
<para>The legacy approach to handling salt was to inject a
<interfacename>SaltSource</interfacename> into the <interfacename>SaltSource</interfacename> into the
<classname>DaoAuthenticationProvider</classname>, which would obtain a salt <classname>DaoAuthenticationProvider</classname>, which would obtain a salt
value for a particular user and pass it to the value for a particular user and pass it to the
<interfacename>PasswordEncoder</interfacename>. Using a random salt and <interfacename>PasswordEncoder</interfacename>. Using bcrypt means you don't have
combining it with the password data field means you don't have to worry about worry about the details of salt handling (such as where the the value is stored),
the details of salt handling (such as where the the value is stored), as it is as it is all done internally. So we'd strongly recommend you use bcrypt
all done internally. So we'd strongly recommend you use this approach unless you unless you already have a system in place which stores the salt separately.</para>
already have a system in place which stores the salt separately.</para>
</note></para> </note></para>
</section> </section>
<section> <section>
@ -296,7 +307,9 @@
be using upper-case while the output from the encoder is lower-case. Make sure you be using upper-case while the output from the encoder is lower-case. Make sure you
write a test to check the output from your configured password encoder with a known write a test to check the output from your configured password encoder with a known
password and salt combination and check that it matches the database value before password and salt combination and check that it matches the database value before
going further and attempting to authenticate through your application.</para> going further and attempting to authenticate through your application. Using a standard
like bcrypt will avoid these issues.
</para>
<para>If you want to generate encoded passwords directly in Java for storage in your <para>If you want to generate encoded passwords directly in Java for storage in your
user database, then you can use the <methodname>encode</methodname> method on the user database, then you can use the <methodname>encode</methodname> method on the
<interfacename>PasswordEncoder</interfacename>.</para> <interfacename>PasswordEncoder</interfacename>.</para>

View File

@ -120,18 +120,19 @@ public interface PasswordEncoder {
This method is designed to support password-based authentication schemes. This method is designed to support password-based authentication schemes.
</para> </para>
<para> <para>
The <classname>StandardPasswordEncoder</classname> implementation applies 1024 iterations of the SHA-256 hashing algorithm to the rawPassword combined with a site-wide secret and 8-byte random salt: The <classname>BCryptPasswordEncoder</classname> implementation uses the widely supported "bcrypt" algorithm
to hash the passwords. Bcrypt uses a random 16 byte salt value and is a deliberately slow algorithm, in order to
hinder password crackers. The amount of work it does can be tuned using the "strength" parameter which takes values
from 4 to 31. The higher the value, the more work has to be done to calculate the hash. The default value is 10.
You can change this value in your deployed system without affecting existing passwords, as the value is also stored
in the encoded hash.
</para> </para>
<programlisting language="java"><![CDATA[ <programlisting language="java"><![CDATA[
StandardPasswordEncoder encoder = new StandardPasswordEncoder("secret"); // Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword"); String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));]]> assertTrue(encoder.matches("myPassword", result));]]>
</programlisting> </programlisting>
<para>
The random salt ensures each hash is unique when the same password is used multiple times.
The site-wide secret should be stored in a safe place separate from where passwords are stored, and is used to protect against a bruce force attack in the event the database of passwords is compromised.
1024 iterations of the hashing algorithm strengthens the key and makes it more difficult to compromise using a brute force attack.
</para>
</section> </section>
</chapter> </chapter>

View File

@ -400,13 +400,16 @@
namespace. </para> namespace. </para>
<section xml:id="ns-password-encoder"> <section xml:id="ns-password-encoder">
<title>Adding a Password Encoder</title> <title>Adding a Password Encoder</title>
<para> Often your password data will be encoded using a hashing algorithm. This is <para> Passwords should always be encoded using a secure hashing algorithm designed for the purpose
supported by the <literal>&lt;password-encoder&gt;</literal> element. With SHA (not a standard algorithm like SHA or MD5). This is supported by the
<literal>&lt;password-encoder&gt;</literal> element. With bcrypt
encoded passwords, the original authentication provider configuration would look encoded passwords, the original authentication provider configuration would look
like this: <programlisting language="xml"><![CDATA[ like this: <programlisting language="xml"><![CDATA[
<beans:bean name="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<authentication-manager> <authentication-manager>
<authentication-provider> <authentication-provider>
<password-encoder hash="sha"/> <password-encoder ref="bcryptEncoder"/>
<user-service> <user-service>
<user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f" <user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f"
authorities="ROLE_USER, ROLE_ADMIN" /> authorities="ROLE_USER, ROLE_ADMIN" />
@ -417,20 +420,10 @@
</authentication-manager> </authentication-manager>
]]> ]]>
</programlisting></para> </programlisting></para>
<para> When using hashed passwords, it's also a good idea to use a salt value to <para>Bcrypt is a good choice for most cases, unless you have a legacy system which forces
protect against dictionary attacks and Spring Security supports this too. you to use a different algorithm. If you are using a simple hashing algorithm or, even worse,
Ideally you would want to use a randomly generated salt value for each user, but storing plain text passwords, then you should consider migrating to a more secure option like bcrypt.
you can use any property of the <classname>UserDetails</classname> object which </para>
is loaded by your <classname>UserDetailsService</classname>. For example, to use
the <literal>username</literal> property, you would use <programlisting language="xml"><![CDATA[
<password-encoder hash="sha">
<salt-source user-property="username"/>
</password-encoder>
]]></programlisting> You can use a custom password encoder bean by using the
<literal>ref</literal> attribute of <literal>password-encoder</literal>. This
should contain the name of a bean in the application context which is an
instance of Spring Security's <interfacename>PasswordEncoder</interfacename>
interface. </para>
</section> </section>
</section> </section>
</section> </section>