SEC-1389: Added "iterations" property to BaseDigestpasswordEncoder to support "stretching" of passwords.
This commit is contained in:
parent
bd2fd3448b
commit
67c9a0b78d
|
@ -6,34 +6,39 @@ import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.core.codec.Base64;
|
||||||
import org.springframework.security.core.codec.Hex;
|
import org.springframework.security.core.codec.Hex;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base for digest password encoders.
|
* Base for digest password encoders.
|
||||||
* <p>This class can be used stand-alone, or one of the subclasses can be used for compatiblity and convenience.
|
* <p>
|
||||||
|
* This class can be used stand-alone, or one of the subclasses can be used for compatiblity and convenience.
|
||||||
* When using this class directly you must specify a
|
* When using this class directly you must specify a
|
||||||
* <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppA">
|
* <a href="http://java.sun.com/j2se/1.5.0/docs/guide/security/CryptoSpec.html#AppA">
|
||||||
* Message Digest Algorithm</a> to use as a constructor arg</p>
|
* Message Digest Algorithm</a> to use as a constructor arg.
|
||||||
*
|
* <p>
|
||||||
* <p>The encoded password hash is normally returned as Hex (32 char) version of the hash bytes.
|
* The encoded password hash is normally returned as Hex (32 char) version of the hash bytes.
|
||||||
* Setting the <tt>encodeHashAsBase64</tt> property to <tt>true</tt> will cause the encoded pass to be returned
|
* Setting the <tt>encodeHashAsBase64</tt> property to <tt>true</tt> will cause the encoded pass to be returned
|
||||||
* as Base64 text, which will consume 24 characters.
|
* as Base64 text, which will consume 24 characters.
|
||||||
* See {@link BaseDigestPasswordEncoder#setEncodeHashAsBase64(boolean)}
|
* See {@link BaseDigestPasswordEncoder#setEncodeHashAsBase64(boolean)}
|
||||||
* </p>
|
|
||||||
* <p>
|
* <p>
|
||||||
* This PasswordEncoder can be used directly as in the following example:<br/>
|
* This {@code PasswordEncoder} can be used directly as in the following example:
|
||||||
* <pre>
|
* <pre>
|
||||||
* <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder">
|
* <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder">
|
||||||
* <constructor-arg value="MD5"/>
|
* <constructor-arg value="MD5"/>
|
||||||
* </bean>
|
* </bean>
|
||||||
* </pre>
|
* </pre>
|
||||||
* </p>
|
* <p>
|
||||||
|
* If desired, the {@link #setIterations iterations} property can be set to enable
|
||||||
|
* "<a href="http://en.wikipedia.org/wiki/Key_strengthening">password stretching</a>" for the digest calculation.
|
||||||
*
|
*
|
||||||
* @author Ray Krueger
|
* @author Ray Krueger
|
||||||
|
* @author Luke Taylor
|
||||||
* @since 1.0.1
|
* @since 1.0.1
|
||||||
*/
|
*/
|
||||||
public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
|
public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
|
||||||
|
|
||||||
private final String algorithm;
|
private final String algorithm;
|
||||||
|
private int iterations = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The digest algorithm to use
|
* The digest algorithm to use
|
||||||
|
@ -81,6 +86,11 @@ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
|
||||||
throw new IllegalStateException("UTF-8 not supported!");
|
throw new IllegalStateException("UTF-8 not supported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "stretch" the encoded value if configured to do so
|
||||||
|
for (int i = 1; i < iterations; i++) {
|
||||||
|
digest = messageDigest.digest(digest);
|
||||||
|
}
|
||||||
|
|
||||||
if (getEncodeHashAsBase64()) {
|
if (getEncodeHashAsBase64()) {
|
||||||
return new String(Base64.encode(digest));
|
return new String(Base64.encode(digest));
|
||||||
} else {
|
} else {
|
||||||
|
@ -122,4 +132,17 @@ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
|
||||||
public String getAlgorithm() {
|
public String getAlgorithm() {
|
||||||
return algorithm;
|
return algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of iterations for which the calculated hash value should be "stretched". If this is greater
|
||||||
|
* than one, the initial digest is calculated, the digest function will be called repeatedly on the result for
|
||||||
|
* the additional number of iterations.
|
||||||
|
*
|
||||||
|
* @param iterations the number of iterations which will be executed on the hashed password/salt
|
||||||
|
* value. Defaults to 1.
|
||||||
|
*/
|
||||||
|
public void setIterations(int iterations) {
|
||||||
|
Assert.isTrue(iterations > 0, "Iterations value must be greater than zero");
|
||||||
|
this.iterations = iterations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
|
|
||||||
package org.springframework.security.authentication.encoding;
|
package org.springframework.security.authentication.encoding;
|
||||||
|
|
||||||
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,10 +26,12 @@ import junit.framework.TestCase;
|
||||||
* @author colin sampaleanu
|
* @author colin sampaleanu
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
* @author Ray Krueger
|
* @author Ray Krueger
|
||||||
|
* @author Luke Taylor
|
||||||
*/
|
*/
|
||||||
public class Md5PasswordEncoderTests extends TestCase {
|
public class Md5PasswordEncoderTests {
|
||||||
//~ Methods ========================================================================================================
|
//~ Methods ========================================================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testBasicFunctionality() {
|
public void testBasicFunctionality() {
|
||||||
Md5PasswordEncoder pe = new Md5PasswordEncoder();
|
Md5PasswordEncoder pe = new Md5PasswordEncoder();
|
||||||
String raw = "abc123";
|
String raw = "abc123";
|
||||||
|
@ -42,12 +44,14 @@ public class Md5PasswordEncoderTests extends TestCase {
|
||||||
assertEquals("MD5", pe.getAlgorithm());
|
assertEquals("MD5", pe.getAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testNonAsciiPasswordHasCorrectHash() {
|
@Test
|
||||||
|
public void nonAsciiPasswordHasCorrectHash() {
|
||||||
Md5PasswordEncoder md5 = new Md5PasswordEncoder();
|
Md5PasswordEncoder md5 = new Md5PasswordEncoder();
|
||||||
String encodedPassword = md5.encodePassword("\u4F60\u597d", null);
|
String encodedPassword = md5.encodePassword("\u4F60\u597d", null);
|
||||||
assertEquals("7eca689f0d3389d9dea66ae112e5cfd7", encodedPassword);
|
assertEquals("7eca689f0d3389d9dea66ae112e5cfd7", encodedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testBase64() throws Exception {
|
public void testBase64() throws Exception {
|
||||||
Md5PasswordEncoder pe = new Md5PasswordEncoder();
|
Md5PasswordEncoder pe = new Md5PasswordEncoder();
|
||||||
pe.setEncodeHashAsBase64(true);
|
pe.setEncodeHashAsBase64(true);
|
||||||
|
@ -59,4 +63,13 @@ public class Md5PasswordEncoderTests extends TestCase {
|
||||||
assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
|
assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
|
||||||
assertTrue(encoded.length() != 32);
|
assertTrue(encoded.length() != 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stretchFactorIsProcessedCorrectly() throws Exception {
|
||||||
|
Md5PasswordEncoder pe = new Md5PasswordEncoder();
|
||||||
|
pe.setIterations(2);
|
||||||
|
// Calculate value using:
|
||||||
|
// echo -n password{salt} | openssl md5 -binary | openssl md5
|
||||||
|
assertEquals("eb753fb0c370582b4ee01b30f304b9fc", pe.encodePassword("password", "salt"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue