Преглед изворни кода

SEC-1389: Added "iterations" property to BaseDigestpasswordEncoder to support "stretching" of passwords.

Luke Taylor пре 15 година
родитељ
комит
67c9a0b78d

+ 31 - 8
core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java

@@ -6,34 +6,39 @@ import java.security.NoSuchAlgorithmException;
 
 import org.springframework.security.core.codec.Base64;
 import org.springframework.security.core.codec.Hex;
+import org.springframework.util.Assert;
 
 /**
  * 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
- * <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppA">
- * 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.
+ * <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>
+ * 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
  * as Base64 text, which will consume 24 characters.
  * See {@link BaseDigestPasswordEncoder#setEncodeHashAsBase64(boolean)}
- * </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>
  * &lt;bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder"&gt;
  *     &lt;constructor-arg value="MD5"/&gt;
  * &lt;/bean&gt;
  * </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 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;
+    }
 }

+ 17 - 4
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"));
+    }
 }