Browse Source

http://opensource.atlassian.com/projects/spring/browse/SEC-96

Refactored Digest encoding for better support of all MessageDigest algorithms, such as the SHA family.
Ray Krueger 19 năm trước cách đây
mục cha
commit
00620b6992

+ 9 - 26
core/src/main/java/org/acegisecurity/providers/encoding/Md5PasswordEncoder.java

@@ -12,42 +12,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.acegisecurity.providers.encoding;
 
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.digest.DigestUtils;
-
-
 /**
  * <p>MD5 implementation of PasswordEncoder.</p>
- *  <p>If a <code>null</code> password is presented, it will be treated as an empty <code>String</code> ("")
+ * <p>If a <code>null</code> password is presented, it will be treated as an empty <code>String</code> ("")
  * password.</p>
- *  <P>As MD5 is a one-way hash, the salt can contain any characters.</p>
+ * <P>As MD5 is a one-way hash, the salt can contain any characters.</p>
  *
+ * This is a convenience class that extends the
+ * {@link MessageDigestPasswordEncoder} and passes MD5 as the algorithm to use.
+ *
+ * @author Ray Krueger
  * @author colin sampaleanu
  * @author Ben Alex
  * @version $Id$
  */
-public class Md5PasswordEncoder extends BaseDigestPasswordEncoder implements PasswordEncoder {
-    //~ Methods ========================================================================================================
-
-    public String encodePassword(String rawPass, Object salt) {
-        String saltedPass = mergePasswordAndSalt(rawPass, salt, false);
-
-        if (!getEncodeHashAsBase64()) {
-            return DigestUtils.md5Hex(saltedPass);
-        }
-
-        byte[] encoded = Base64.encodeBase64(DigestUtils.md5(saltedPass));
-
-        return new String(encoded);
-    }
-
-    public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
-        String pass1 = "" + encPass;
-        String pass2 = encodePassword(rawPass, salt);
+public class Md5PasswordEncoder extends MessageDigestPasswordEncoder {
 
-        return pass1.equals(pass2);
+    public Md5PasswordEncoder() {
+        super("MD5");
     }
 }

+ 114 - 0
core/src/main/java/org/acegisecurity/providers/encoding/MessageDigestPasswordEncoder.java

@@ -0,0 +1,114 @@
+package org.acegisecurity.providers.encoding;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * <p>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>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>
+ *
+ * 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)}
+ * <br/>
+ * This PasswordEncoder can be used directly as in the following example:<br/>
+ * &lt;bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.MessageDigestPasswordEncoder"&gt;<br/>
+ * &nbsp;&nbsp;&lt;constructor-arg value="MD5"/><br/>
+ * &lt;/bean&gt;
+ *
+ *
+ * @author Ray Krueger
+ * @since 1.0.1
+ */
+public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
+
+    private final String algorithm;
+
+    /**
+     * The digest algorithm to use
+     * Supports the named <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppA">
+     * Message Digest Algorithms</a> in the Java environment.
+     *
+     * @param algorithm
+     */
+    public MessageDigestPasswordEncoder(String algorithm) {
+        this(algorithm, false);
+    }
+
+    /**
+     * Convenience constructor for specifying the algorithm and whether or not to enable base64 encoding
+     *
+     * @param algorithm
+     * @param encodeHashAsBase64
+     * @throws IllegalArgumentException if an unknown
+     */
+    public MessageDigestPasswordEncoder(String algorithm, boolean encodeHashAsBase64) throws IllegalArgumentException {
+        this.algorithm = algorithm;
+        setEncodeHashAsBase64(encodeHashAsBase64);
+        //Validity Check
+        getMessageDigest();
+    }
+
+    /**
+     * Encodes the rawPass using a MessageDigest.
+     * If a salt is specified it will be merged with the password before encoding.
+     *
+     * @param rawPass The plain text password
+     * @param salt The salt to sprinkle
+     * @return Hex string of password digest (or base64 encoded string if encodeHashAsBase64 is enabled.
+     */
+    public String encodePassword(String rawPass, Object salt) {
+        String saltedPass = mergePasswordAndSalt(rawPass, salt, false);
+
+        MessageDigest messageDigest = getMessageDigest();
+
+        byte[] digest = messageDigest.digest(saltedPass.getBytes());
+
+        if (getEncodeHashAsBase64()) {
+            return new String(Base64.encodeBase64(digest));
+        } else {
+            return new String(Hex.encodeHex(digest));
+        }
+    }
+
+    /**
+     * Get a MessageDigest instance for the given algorithm.
+     * Throws an IllegalArgumentException if <i>algorithm</i> is unknown
+     *
+     * @return MessageDigest instance
+     * @throws IllegalArgumentException if NoSuchAlgorithmException is thrown
+     */
+    protected final MessageDigest getMessageDigest() throws IllegalArgumentException {
+        try {
+            return MessageDigest.getInstance(algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException("No such algorithm [" +
+                    algorithm + "]");
+        }
+    }
+
+    /**
+     * Takes a previously encoded password and compares it with a rawpassword after mixing in the salt and
+     * encoding that value
+     *
+     * @param encPass previously encoded password
+     * @param rawPass plain text password
+     * @param salt salt to mix into password
+     * @return true or false
+     */
+    public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
+        String pass1 = "" + encPass;
+        String pass2 = encodePassword(rawPass, salt);
+
+        return pass1.equals(pass2);
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+}

+ 26 - 24
core/src/main/java/org/acegisecurity/providers/encoding/ShaPasswordEncoder.java

@@ -12,42 +12,44 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.acegisecurity.providers.encoding;
 
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.digest.DigestUtils;
-
-
 /**
  * <p>SHA implementation of PasswordEncoder.</p>
- *  <p>If a <code>null</code> password is presented, it will be treated as an empty <code>String</code> ("")
+ * <p>If a <code>null</code> password is presented, it will be treated as an empty <code>String</code> ("")
  * password.</p>
- *  <P>As SHA is a one-way hash, the salt can contain any characters.</p>
+ * <P>As SHA is a one-way hash, the salt can contain any characters.</p>
+ *
+ * The default strength for the SHA encoding is SHA-1. If you wish to use higher strengths use the argumented constructor.
+ * {@link #ShaPasswordEncoder(int strength)}
+ * <br/>
+ * The applicationContext example... <br/>
+ * &lt;bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.ShaPasswordEncoder"&gt;<br/>
+ * &nbsp;&nbsp;&lt;constructor-arg value="256"/><br/>
+ * &lt;/bean&gt;
+ *
  *
+ * @author Ray Krueger
  * @author colin sampaleanu
  * @author Ben Alex
  * @version $Id$
  */
-public class ShaPasswordEncoder extends BaseDigestPasswordEncoder implements PasswordEncoder {
-    //~ Methods ========================================================================================================
+public class ShaPasswordEncoder extends MessageDigestPasswordEncoder {
 
-    public String encodePassword(String rawPass, Object salt) {
-        String saltedPass = mergePasswordAndSalt(rawPass, salt, false);
-
-        if (!getEncodeHashAsBase64()) {
-            return DigestUtils.shaHex(saltedPass);
-        }
-
-        byte[] encoded = Base64.encodeBase64(DigestUtils.sha(saltedPass));
-
-        return new String(encoded);
+    /**
+     * Initializes the ShaPasswordEncoder for SHA-1 strength
+     */
+    public ShaPasswordEncoder() {
+        this(1);
     }
 
-    public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
-        String pass1 = "" + encPass;
-        String pass2 = encodePassword(rawPass, salt);
-
-        return pass1.equals(pass2);
+    /**
+     * Initialize the ShaPasswordEncoder with a given SHA stength as supported by the JVM
+     * EX: <code>ShaPasswordEncoder encoder = new ShaPasswordEncoder(256);</code> initializes with SHA-256
+     *
+     * @param strength EX: 1, 256, 384, 512
+     */
+    public ShaPasswordEncoder(int strength) {
+        super("SHA-" + strength);
     }
 }

+ 10 - 3
core/src/test/java/org/acegisecurity/providers/encoding/Md5PasswordEncoderTests.java

@@ -23,6 +23,7 @@ import junit.framework.TestCase;
  *
  * @author colin sampaleanu
  * @author Ben Alex
+ * @author Ray Krueger
  * @version $Id$
  */
 public class Md5PasswordEncoderTests extends TestCase {
@@ -36,11 +37,17 @@ public class Md5PasswordEncoderTests extends TestCase {
         String encoded = pe.encodePassword(raw, salt);
         assertTrue(pe.isPasswordValid(encoded, raw, salt));
         assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
-        assertTrue(encoded.length() == 32);
+        assertEquals("a68aafd90299d0b137de28fb4bb68573", encoded);
+        assertEquals("MD5", pe.getAlgorithm());
+    }
 
-        // now try Base64
+    public void testBase64() throws Exception {
+        Md5PasswordEncoder pe = new Md5PasswordEncoder();
         pe.setEncodeHashAsBase64(true);
-        encoded = pe.encodePassword(raw, salt);
+        String raw = "abc123";
+        String badRaw = "abc321";
+        String salt = "THIS_IS_A_SALT";
+        String encoded = pe.encodePassword(raw, salt);
         assertTrue(pe.isPasswordValid(encoded, raw, salt));
         assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
         assertTrue(encoded.length() != 32);

+ 27 - 3
core/src/test/java/org/acegisecurity/providers/encoding/ShaPasswordEncoderTests.java

@@ -23,6 +23,7 @@ import junit.framework.TestCase;
  *
  * @author colin sampaleanu
  * @author Ben Alex
+ * @author Ray Krueger
  * @version $Id$
  */
 public class ShaPasswordEncoderTests extends TestCase {
@@ -36,13 +37,36 @@ public class ShaPasswordEncoderTests extends TestCase {
         String encoded = pe.encodePassword(raw, salt);
         assertTrue(pe.isPasswordValid(encoded, raw, salt));
         assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
-        assertTrue(encoded.length() == 40);
+        assertEquals("b2f50ffcbd3407fe9415c062d55f54731f340d32", encoded);
 
-        // now try Base64
+    }
+
+    public void testBase64() throws Exception {
+        ShaPasswordEncoder pe = new ShaPasswordEncoder();
         pe.setEncodeHashAsBase64(true);
-        encoded = pe.encodePassword(raw, salt);
+        String raw = "abc123";
+        String badRaw = "abc321";
+        String salt = "THIS_IS_A_SALT";
+        String encoded = pe.encodePassword(raw, salt);
         assertTrue(pe.isPasswordValid(encoded, raw, salt));
         assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
         assertTrue(encoded.length() != 40);
     }
+
+    public void test256() throws Exception {
+        ShaPasswordEncoder pe = new ShaPasswordEncoder(256);
+        String encoded = pe.encodePassword("abc123", null);
+        assertEquals("6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090", encoded);
+        String encodedWithSalt = pe.encodePassword("abc123", "THIS_IS_A_SALT");
+        assertEquals("4b79b7de23eb23b78cc5ede227d532b8a51f89b2ec166f808af76b0dbedc47d7", encodedWithSalt);
+    }
+
+    public void testInvalidStrength() throws Exception {
+        try {
+            new ShaPasswordEncoder(666);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException e) {
+            //expected
+        }
+    }
 }