SEC-1666: Use constant time comparison for sensitive data.
Constant time comparison helps to mitigate timing attacks. See the following link for more information * http://rdist.root.org/2010/07/19/exploiting-remote-timing-attacks/ * http://en.wikipedia.org/wiki/Timing_attack for more information.
This commit is contained in:
+1
-1
@@ -145,7 +145,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
String encodedRawPass = encodePassword(rawPass, salt).substring(startOfHash);
|
||||
|
||||
return encodedRawPass.equals(encPass.substring(startOfHash));
|
||||
return PasswordEncoderUtils.equals(encodedRawPass,encPass.substring(startOfHash));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ public class Md4PasswordEncoder extends BaseDigestPasswordEncoder {
|
||||
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
|
||||
String pass1 = "" + encPass;
|
||||
String pass2 = encodePassword(rawPass, salt);
|
||||
return pass1.equals(pass2);
|
||||
return PasswordEncoderUtils.equals(pass1,pass2);
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
|
||||
String pass1 = "" + encPass;
|
||||
String pass2 = encodePassword(rawPass, salt);
|
||||
|
||||
return pass1.equals(pass2);
|
||||
return PasswordEncoderUtils.equals(pass1,pass2);
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package org.springframework.security.authentication.encoding;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* Utility for constant time comparison to prevent against timing attacks.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class PasswordEncoderUtils {
|
||||
|
||||
/**
|
||||
* Constant time comparison to prevent against timing attacks.
|
||||
* @param expected
|
||||
* @param actual
|
||||
* @return
|
||||
*/
|
||||
static boolean equals(String expected, String actual) {
|
||||
byte[] expectedBytes = bytesUtf8(expected);
|
||||
byte[] actualBytes = bytesUtf8(actual);
|
||||
int expectedLength = expectedBytes == null ? -1 : expectedBytes.length;
|
||||
int actualLength = actualBytes == null ? -1 : actualBytes.length;
|
||||
if (expectedLength != actualLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
for (int i = 0; i < expectedLength; i++) {
|
||||
result |= expectedBytes[i] ^ actualBytes[i];
|
||||
}
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
private static byte[] bytesUtf8(String s) {
|
||||
if(s == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return s.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Could not get bytes in UTF-8 format",e);
|
||||
}
|
||||
}
|
||||
private PasswordEncoderUtils() {}
|
||||
}
|
||||
+7
-4
@@ -15,6 +15,8 @@
|
||||
|
||||
package org.springframework.security.authentication.encoding;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* <p>Plaintext implementation of PasswordEncoder.</p>
|
||||
* <P>As callers may wish to extract the password and salts separately from the encoded password, the salt must
|
||||
@@ -46,11 +48,12 @@ public class PlaintextPasswordEncoder extends BasePasswordEncoder {
|
||||
// authentication will fail as the encodePassword never allows them)
|
||||
String pass2 = mergePasswordAndSalt(rawPass, salt, false);
|
||||
|
||||
if (!ignorePasswordCase) {
|
||||
return pass1.equals(pass2);
|
||||
} else {
|
||||
return pass1.equalsIgnoreCase(pass2);
|
||||
if (ignorePasswordCase) {
|
||||
// Note: per String javadoc to get correct results for Locale insensitive, use English
|
||||
pass1 = pass1.toLowerCase(Locale.ENGLISH);
|
||||
pass2 = pass2.toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
return PasswordEncoderUtils.equals(pass1,pass2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.springframework.security.authentication.encoding;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class PasswordEncoderUtilsTests {
|
||||
|
||||
@Test
|
||||
public void differentLength() {
|
||||
assertFalse(PasswordEncoderUtils.equals("abc", "a"));
|
||||
assertFalse(PasswordEncoderUtils.equals("a", "abc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsNull() {
|
||||
assertFalse(PasswordEncoderUtils.equals(null, "a"));
|
||||
assertFalse(PasswordEncoderUtils.equals("a", null));
|
||||
assertTrue(PasswordEncoderUtils.equals(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsCaseSensitive() {
|
||||
assertFalse(PasswordEncoderUtils.equals("aBc", "abc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsSuccess() {
|
||||
assertTrue(PasswordEncoderUtils.equals("abcdef", "abcdef"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user