From 67c9a0b78dce7536bfa2f50019f63f4ce9d3c03f Mon Sep 17 00:00:00 2001
From: Luke Taylor This class can be used stand-alone, or one of the subclasses can be used for compatiblity and convenience.
+ *
+ * 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
- *
- * Message Digest Algorithm to use as a constructor arg The encoded password hash is normally returned as Hex (32 char) version of the hash bytes.
+ *
+ * Message Digest Algorithm to use as a constructor arg.
+ *
+ * The encoded password hash is normally returned as Hex (32 char) version of the hash bytes.
* Setting the encodeHashAsBase64 property to true will cause the encoded pass to be returned
* as Base64 text, which will consume 24 characters.
* See {@link BaseDigestPasswordEncoder#setEncodeHashAsBase64(boolean)}
- *
- * This PasswordEncoder can be used directly as in the following example:
+ * This {@code PasswordEncoder} can be used directly as in the following example:
*
* <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder">
* <constructor-arg value="MD5"/>
* </bean>
*
- *
+ * If desired, the {@link #setIterations iterations} property can be set to enable + * "password stretching" for the digest calculation. * * @author Ray Krueger + * @author Luke Taylor * @since 1.0.1 */ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder { private final String algorithm; + private int iterations = 1; /** * The digest algorithm to use @@ -81,6 +86,11 @@ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder { 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()) { return new String(Base64.encode(digest)); } else { @@ -122,4 +132,17 @@ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder { public String getAlgorithm() { 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; + } } diff --git a/core/src/test/java/org/springframework/security/authentication/encoding/Md5PasswordEncoderTests.java b/core/src/test/java/org/springframework/security/authentication/encoding/Md5PasswordEncoderTests.java index bb66604f1e..06f849bf02 100644 --- a/core/src/test/java/org/springframework/security/authentication/encoding/Md5PasswordEncoderTests.java +++ b/core/src/test/java/org/springframework/security/authentication/encoding/Md5PasswordEncoderTests.java @@ -15,9 +15,9 @@ 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 Ben Alex * @author Ray Krueger + * @author Luke Taylor */ -public class Md5PasswordEncoderTests extends TestCase { +public class Md5PasswordEncoderTests { //~ Methods ======================================================================================================== + @Test public void testBasicFunctionality() { Md5PasswordEncoder pe = new Md5PasswordEncoder(); String raw = "abc123"; @@ -42,12 +44,14 @@ public class Md5PasswordEncoderTests extends TestCase { assertEquals("MD5", pe.getAlgorithm()); } - public void testNonAsciiPasswordHasCorrectHash() { + @Test + public void nonAsciiPasswordHasCorrectHash() { Md5PasswordEncoder md5 = new Md5PasswordEncoder(); String encodedPassword = md5.encodePassword("\u4F60\u597d", null); assertEquals("7eca689f0d3389d9dea66ae112e5cfd7", encodedPassword); } + @Test public void testBase64() throws Exception { Md5PasswordEncoder pe = new Md5PasswordEncoder(); pe.setEncodeHashAsBase64(true); @@ -59,4 +63,13 @@ public class Md5PasswordEncoderTests extends TestCase { assertFalse(pe.isPasswordValid(encoded, badRaw, salt)); 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")); + } }