mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-28 23:02:15 +00:00
Refactored Digest encoding for better support of all MessageDigest algorithms, such as the SHA family.
This commit is contained in:
parent
35093e09f6
commit
00620b6992
@ -12,42 +12,25 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.acegisecurity.providers.encoding;
|
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>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>
|
* 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 colin sampaleanu
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class Md5PasswordEncoder extends BaseDigestPasswordEncoder implements PasswordEncoder {
|
public class Md5PasswordEncoder extends MessageDigestPasswordEncoder {
|
||||||
//~ Methods ========================================================================================================
|
|
||||||
|
|
||||||
public String encodePassword(String rawPass, Object salt) {
|
public Md5PasswordEncoder() {
|
||||||
String saltedPass = mergePasswordAndSalt(rawPass, salt, false);
|
super("MD5");
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
return pass1.equals(pass2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/>
|
||||||
|
* <bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.MessageDigestPasswordEncoder"><br/>
|
||||||
|
* <constructor-arg value="MD5"/><br/>
|
||||||
|
* </bean>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
@ -12,42 +12,44 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.acegisecurity.providers.encoding;
|
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>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>
|
* 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/>
|
||||||
|
* <bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.ShaPasswordEncoder"><br/>
|
||||||
|
* <constructor-arg value="256"/><br/>
|
||||||
|
* </bean>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Ray Krueger
|
||||||
* @author colin sampaleanu
|
* @author colin sampaleanu
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class ShaPasswordEncoder extends BaseDigestPasswordEncoder implements PasswordEncoder {
|
public class ShaPasswordEncoder extends MessageDigestPasswordEncoder {
|
||||||
//~ Methods ========================================================================================================
|
|
||||||
|
|
||||||
public String encodePassword(String rawPass, Object salt) {
|
/**
|
||||||
String saltedPass = mergePasswordAndSalt(rawPass, salt, false);
|
* Initializes the ShaPasswordEncoder for SHA-1 strength
|
||||||
|
*/
|
||||||
if (!getEncodeHashAsBase64()) {
|
public ShaPasswordEncoder() {
|
||||||
return DigestUtils.shaHex(saltedPass);
|
this(1);
|
||||||
}
|
|
||||||
|
|
||||||
byte[] encoded = Base64.encodeBase64(DigestUtils.sha(saltedPass));
|
|
||||||
|
|
||||||
return new String(encoded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
|
/**
|
||||||
String pass1 = "" + encPass;
|
* Initialize the ShaPasswordEncoder with a given SHA stength as supported by the JVM
|
||||||
String pass2 = encodePassword(rawPass, salt);
|
* EX: <code>ShaPasswordEncoder encoder = new ShaPasswordEncoder(256);</code> initializes with SHA-256
|
||||||
|
*
|
||||||
return pass1.equals(pass2);
|
* @param strength EX: 1, 256, 384, 512
|
||||||
|
*/
|
||||||
|
public ShaPasswordEncoder(int strength) {
|
||||||
|
super("SHA-" + strength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import junit.framework.TestCase;
|
|||||||
*
|
*
|
||||||
* @author colin sampaleanu
|
* @author colin sampaleanu
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
|
* @author Ray Krueger
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class Md5PasswordEncoderTests extends TestCase {
|
public class Md5PasswordEncoderTests extends TestCase {
|
||||||
@ -36,11 +37,17 @@ public class Md5PasswordEncoderTests extends TestCase {
|
|||||||
String encoded = pe.encodePassword(raw, salt);
|
String encoded = pe.encodePassword(raw, salt);
|
||||||
assertTrue(pe.isPasswordValid(encoded, raw, salt));
|
assertTrue(pe.isPasswordValid(encoded, raw, salt));
|
||||||
assertFalse(pe.isPasswordValid(encoded, badRaw, 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);
|
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));
|
assertTrue(pe.isPasswordValid(encoded, raw, salt));
|
||||||
assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
|
assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
|
||||||
assertTrue(encoded.length() != 32);
|
assertTrue(encoded.length() != 32);
|
||||||
|
@ -23,6 +23,7 @@ import junit.framework.TestCase;
|
|||||||
*
|
*
|
||||||
* @author colin sampaleanu
|
* @author colin sampaleanu
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
|
* @author Ray Krueger
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class ShaPasswordEncoderTests extends TestCase {
|
public class ShaPasswordEncoderTests extends TestCase {
|
||||||
@ -36,13 +37,36 @@ public class ShaPasswordEncoderTests extends TestCase {
|
|||||||
String encoded = pe.encodePassword(raw, salt);
|
String encoded = pe.encodePassword(raw, salt);
|
||||||
assertTrue(pe.isPasswordValid(encoded, raw, salt));
|
assertTrue(pe.isPasswordValid(encoded, raw, salt));
|
||||||
assertFalse(pe.isPasswordValid(encoded, badRaw, 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);
|
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));
|
assertTrue(pe.isPasswordValid(encoded, raw, salt));
|
||||||
assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
|
assertFalse(pe.isPasswordValid(encoded, badRaw, salt));
|
||||||
assertTrue(encoded.length() != 40);
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user