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.
*
* The preferred implementation is {@code BCryptPasswordEncoder}.
*
* @author Keith Donald
*/
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.
* <p>
* 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 Luke Taylor

View File

@ -219,14 +219,27 @@
<title>Password Encoding</title>
<para xlink:href="#spring-security-crypto-passwordencoders">Spring Security's
<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
that the passwords are <quote>hashed</quote> using a digest algorithm such as MD5 or
SHA. Spring Security 3.1's <link xlink:href="#spring-security-crypto-passwordencoders"
><literal>crypto</literal></link> package introduces a simpler API which encourages
best-practice for password hashing. We would encourage you to use these APIs for new
development and regard the classes in package
<literal>org.springframework.security.authentication.encoding</literal> as legacy
implementations. The <classname>DaoAuthenticationProvider</classname> can be injected
passwords which are encoded in some way in persistent storage. You should never store
passwords in plain text. Always use a one-way password hashing algorithm such as bcrypt
which uses a built-in salt value which is different for each stored password. Do not use
a plain hash function such as MD5 or SHA, or even a salted version. Bcrypt is deliberately
designed to be slow and to hinder offline password cracking, whereas standard hash algorithms
are fast and can easily be used to test thousands of passwords in parallel on custom
hardware. You might think this doesn't apply to you since your password database is
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>
types.</para>
<section>
@ -251,7 +264,8 @@
<title>Adding Salt to a Hash</title>
<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
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
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
@ -263,19 +277,16 @@
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
complicated (but not impossible).</para>
<para>The <classname>StandardPasswordEncoder</classname> in the <link
xlink:href="#spring-security-crypto-passwordencoders"
><literal>crypto</literal></link> package uses a random 8-byte salt, which is stored
in the same field as the password.<note>
<para>The legacy approach to handling salt was to inject a
<para>Bcrypt automatically generates a random salt value for each password when it
is encoded, and stores it in the bcrypt string in a standard format.
<note><para>The legacy approach to handling salt was to inject a
<interfacename>SaltSource</interfacename> into the
<classname>DaoAuthenticationProvider</classname>, which would obtain a salt
value for a particular user and pass it to the
<interfacename>PasswordEncoder</interfacename>. Using a random salt and
combining it with the password data field means you don't have to worry about
the details of salt handling (such as where the the value is stored), as it is
all done internally. So we'd strongly recommend you use this approach unless you
already have a system in place which stores the salt separately.</para>
<interfacename>PasswordEncoder</interfacename>. Using bcrypt means you don't have
worry about the details of salt handling (such as where the the value is stored),
as it is all done internally. So we'd strongly recommend you use bcrypt
unless you already have a system in place which stores the salt separately.</para>
</note></para>
</section>
<section>
@ -296,7 +307,9 @@
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
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
user database, then you can use the <methodname>encode</methodname> method on the
<interfacename>PasswordEncoder</interfacename>.</para>

View File

@ -120,18 +120,19 @@ public interface PasswordEncoder {
This method is designed to support password-based authentication schemes.
</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>
<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");
assertTrue(encoder.matches("myPassword", result));]]>
</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>
</chapter>

View File

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