diff --git a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java index cce8c11c9c..f6c677057b 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java +++ b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java @@ -14,6 +14,8 @@ package org.springframework.security.crypto.bcrypt; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; import java.util.Arrays; import java.security.SecureRandom; @@ -912,17 +914,6 @@ public class BCrypt { } static boolean equalsNoEarlyReturn(String a, String b) { - char[] caa = a.toCharArray(); - char[] cab = b.toCharArray(); - - if (caa.length != cab.length) { - return false; - } - - byte ret = 0; - for (int i = 0; i < caa.length; i++) { - ret |= caa[i] ^ cab[i]; - } - return ret == 0; + return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8)); } } diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/AbstractPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/AbstractPasswordEncoder.java index e3857d38c7..f70e289adc 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/AbstractPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/AbstractPasswordEncoder.java @@ -19,6 +19,8 @@ import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; +import java.security.MessageDigest; + import static org.springframework.security.crypto.util.EncodingUtils.concatenate; import static org.springframework.security.crypto.util.EncodingUtils.subArray; @@ -59,14 +61,6 @@ public abstract class AbstractPasswordEncoder implements PasswordEncoder { * Constant time comparison to prevent against timing attacks. */ protected static boolean matches(byte[] expected, byte[] actual) { - if (expected.length != actual.length) { - return false; - } - - int result = 0; - for (int i = 0; i < expected.length; i++) { - result |= expected[i] ^ actual[i]; - } - return result == 0; + return MessageDigest.isEqual(expected, actual); } } diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java b/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java index e82ae0b482..6bf5b937ae 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java @@ -17,6 +17,8 @@ package org.springframework.security.crypto.password; import org.springframework.security.crypto.codec.Utf8; +import java.security.MessageDigest; + /** * Utility for constant time comparison to prevent against timing attacks. * @@ -33,16 +35,8 @@ class PasswordEncoderUtils { 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; - int result = expectedLength == actualLength ? 0 : 1; - for (int i = 0; i < actualLength; i++) { - byte expectedByte = expectedLength <= 0 ? 0 : expectedBytes[i % expectedLength]; - byte actualByte = actualBytes[i % actualLength]; - result |= expectedByte ^ actualByte; - } - return result == 0; + return MessageDigest.isEqual(expectedBytes, actualBytes); } private static byte[] bytesUtf8(String s) { diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoder.java index 552a0be7cf..3fa38e6774 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoder.java @@ -16,6 +16,7 @@ package org.springframework.security.crypto.password; import java.security.GeneralSecurityException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; @@ -141,22 +142,7 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder { public boolean matches(CharSequence rawPassword, String encodedPassword) { byte[] digested = decode(encodedPassword); byte[] salt = subArray(digested, 0, this.saltGenerator.getKeyLength()); - return matches(digested, encode(rawPassword, salt)); - } - - /** - * Constant time comparison to prevent against timing attacks. - */ - private static boolean matches(byte[] expected, byte[] actual) { - if (expected.length != actual.length) { - return false; - } - - int result = 0; - for (int i = 0; i < expected.length; i++) { - result |= expected[i] ^ actual[i]; - } - return result == 0; + return MessageDigest.isEqual(digested, encode(rawPassword, salt)); } private byte[] decode(String encodedBytes) { diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java index 95d7ea5e68..c07fb5bf9f 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java @@ -23,6 +23,8 @@ import org.springframework.security.crypto.codec.Utf8; import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; +import java.security.MessageDigest; + /** * This {@link PasswordEncoder} is provided for legacy purposes only and is not considered * secure. @@ -79,7 +81,7 @@ public final class StandardPasswordEncoder implements PasswordEncoder { public boolean matches(CharSequence rawPassword, String encodedPassword) { byte[] digested = decode(encodedPassword); byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength()); - return matches(digested, digest(rawPassword, salt)); + return MessageDigest.isEqual(digested, digest(rawPassword, salt)); } // internal helpers @@ -105,21 +107,6 @@ public final class StandardPasswordEncoder implements PasswordEncoder { return Hex.decode(encodedPassword); } - /** - * Constant time comparison to prevent against timing attacks. - */ - private boolean matches(byte[] expected, byte[] actual) { - if (expected.length != actual.length) { - return false; - } - - int result = 0; - for (int i = 0; i < expected.length; i++) { - result |= expected[i] ^ actual[i]; - } - return result == 0; - } - private static final int DEFAULT_ITERATIONS = 1024; } diff --git a/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java index 198f91adf1..21fed49ba7 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java @@ -15,6 +15,7 @@ */ package org.springframework.security.crypto.scrypt; +import java.security.MessageDigest; import java.util.Base64; import org.apache.commons.logging.Log; @@ -152,15 +153,7 @@ public class SCryptPasswordEncoder implements PasswordEncoder { byte[] generated = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization, keyLength); - if (derived.length != generated.length) { - return false; - } - - int result = 0; - for (int i = 0; i < derived.length; i++) { - result |= derived[i] ^ generated[i]; - } - return result == 0; + return MessageDigest.isEqual(derived, generated); } private String digest(CharSequence rawPassword, byte[] salt) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java index 67723d189a..614cf904d1 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java @@ -256,15 +256,8 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices { private static boolean equals(String expected, String actual) { byte[] expectedBytes = bytesUtf8(expected); byte[] actualBytes = bytesUtf8(actual); - if (expectedBytes.length != actualBytes.length) { - return false; - } - int result = 0; - for (int i = 0; i < expectedBytes.length; i++) { - result |= expectedBytes[i] ^ actualBytes[i]; - } - return result == 0; + return MessageDigest.isEqual(expectedBytes, actualBytes); } private static byte[] bytesUtf8(String s) {