SEC-526: Fixed. Support for different case prefixes ({SHA}, {sha} etc).

This commit is contained in:
Luke Taylor 2007-08-27 16:23:14 +00:00
parent 0425d3b638
commit f8689b18b2
2 changed files with 51 additions and 8 deletions

View File

@ -29,7 +29,8 @@ import java.security.MessageDigest;
/** /**
* A version of {@link ShaPasswordEncoder} which supports Ldap SHA and SSHA (salted-SHA) encodings. The values are * A version of {@link ShaPasswordEncoder} which supports Ldap SHA and SSHA (salted-SHA) encodings. The values are
* base-64 encoded and have the label "{SHA}" (or "{SSHA}") prepended to the encoded hash. * base-64 encoded and have the label "{SHA}" (or "{SSHA}") prepended to the encoded hash. These can be made lower-case
* in the encoded password, if required, by setting the <tt>forceLowerCasePrefix</tt> property to true.
* *
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$
@ -40,7 +41,12 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
/** The number of bytes in a SHA hash */ /** The number of bytes in a SHA hash */
private static final int SHA_LENGTH = 20; private static final int SHA_LENGTH = 20;
private static final String SSHA_PREFIX = "{SSHA}"; private static final String SSHA_PREFIX = "{SSHA}";
private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase();
private static final String SHA_PREFIX = "{SHA}"; private static final String SHA_PREFIX = "{SHA}";
private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase();
//~ Instance fields ================================================================================================
private boolean forceLowerCasePrefix;
//~ Constructors =================================================================================================== //~ Constructors ===================================================================================================
@ -76,7 +82,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
try { try {
sha = MessageDigest.getInstance("SHA"); sha = MessageDigest.getInstance("SHA");
} catch (java.security.NoSuchAlgorithmException e) { } catch (java.security.NoSuchAlgorithmException e) {
throw new LdapDataAccessException("No SHA implementation available!"); throw new LdapDataAccessException("No SHA implementation available!", e);
} }
sha.update(rawPass.getBytes()); sha.update(rawPass.getBytes());
@ -88,7 +94,15 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
byte[] hash = combineHashAndSalt(sha.digest(), (byte[]) salt); byte[] hash = combineHashAndSalt(sha.digest(), (byte[]) salt);
return ((salt == null) ? SHA_PREFIX : SSHA_PREFIX) + new String(Base64.encodeBase64(hash)); String prefix;
if (salt == null) {
prefix = forceLowerCasePrefix ? SHA_PREFIX_LC : SHA_PREFIX;
} else {
prefix = forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX;
}
return prefix + new String(Base64.encodeBase64(hash));
} }
private byte[] extractSalt(String encPass) { private byte[] extractSalt(String encPass) {
@ -106,19 +120,28 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
* Checks the validity of an unencoded password against an encoded one in the form * Checks the validity of an unencoded password against an encoded one in the form
* "{SSHA}sQuQF8vj8Eg2Y1hPdh3bkQhCKQBgjhQI". * "{SSHA}sQuQF8vj8Eg2Y1hPdh3bkQhCKQBgjhQI".
* *
* @param encPass the SSHA or SHA encoded password * @param encPass the actual SSHA or SHA encoded password
* @param rawPass unencoded password to be verified. * @param rawPass unencoded password to be verified.
* @param salt ignored. If the format is SSHA the salt bytes will be extracted from the encoded password. * @param salt ignored. If the format is SSHA the salt bytes will be extracted from the encoded password.
* *
* @return true if they match. * @return true if they match (independent of the case of the prefix).
*/ */
public boolean isPasswordValid(String encPass, String rawPass, Object salt) { public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
if (encPass.startsWith(SSHA_PREFIX)) { String encPassWithoutPrefix;
if (encPass.startsWith(SSHA_PREFIX) || encPass.startsWith(SSHA_PREFIX_LC)) {
encPassWithoutPrefix = encPass.substring(6);
salt = extractSalt(encPass); salt = extractSalt(encPass);
} else { } else {
encPassWithoutPrefix = encPass.substring(5);
salt = null; salt = null;
} }
return encPass.equals(encodePassword(rawPass, salt)); // Compare the encoded passwords without the prefix
return encodePassword(rawPass, salt).endsWith(encPassWithoutPrefix);
}
public void setForceLowerCasePrefix(boolean forceLowerCasePrefix) {
this.forceLowerCasePrefix = forceLowerCasePrefix;
} }
} }

View File

@ -55,14 +55,34 @@ public class LdapShaPasswordEncoderTests extends TestCase {
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd' * Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
*/ */
public void testValidPasswordSucceeds() { public void testValidPasswordSucceeds() {
sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null)); assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
sha.setForceLowerCasePrefix(true);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
} }
/** /**
* Test values generated by 'slappasswd -s boabspasswurd' * Test values generated by 'slappasswd -s boabspasswurd'
*/ */
public void testValidSaltedPasswordSucceeds() { public void testValidSaltedPasswordSucceeds() {
sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null)); assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{SSHA}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null)); assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null));
sha.setForceLowerCasePrefix(true);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null));
}
public void testCorrectPrefixCaseIsUsed() {
sha.setForceLowerCasePrefix(false);
assertEquals("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{SSHA}"));
sha.setForceLowerCasePrefix(true);
assertEquals("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{ssha}"));
} }
} }