Skip to content

Commit 4b0fb19

Browse files
larsgreferjzheaux
authored andcommitted
Use MessageDigest.isEqual() where possible
fixes #7058
1 parent cd54808 commit 4b0fb19

File tree

7 files changed

+18
-80
lines changed

7 files changed

+18
-80
lines changed

crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java

+3-12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
package org.springframework.security.crypto.bcrypt;
1515

1616
import java.io.UnsupportedEncodingException;
17+
import java.nio.charset.StandardCharsets;
18+
import java.security.MessageDigest;
1719
import java.util.Arrays;
1820
import java.security.SecureRandom;
1921

@@ -912,17 +914,6 @@ public static boolean checkpw(String plaintext, String hashed) {
912914
}
913915

914916
static boolean equalsNoEarlyReturn(String a, String b) {
915-
char[] caa = a.toCharArray();
916-
char[] cab = b.toCharArray();
917-
918-
if (caa.length != cab.length) {
919-
return false;
920-
}
921-
922-
byte ret = 0;
923-
for (int i = 0; i < caa.length; i++) {
924-
ret |= caa[i] ^ cab[i];
925-
}
926-
return ret == 0;
917+
return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
927918
}
928919
}

crypto/src/main/java/org/springframework/security/crypto/password/AbstractPasswordEncoder.java

+3-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
2020
import org.springframework.security.crypto.keygen.KeyGenerators;
2121

22+
import java.security.MessageDigest;
23+
2224
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
2325
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
2426

@@ -59,14 +61,6 @@ protected byte[] encodeAndConcatenate(CharSequence rawPassword, byte[] salt) {
5961
* Constant time comparison to prevent against timing attacks.
6062
*/
6163
protected static boolean matches(byte[] expected, byte[] actual) {
62-
if (expected.length != actual.length) {
63-
return false;
64-
}
65-
66-
int result = 0;
67-
for (int i = 0; i < expected.length; i++) {
68-
result |= expected[i] ^ actual[i];
69-
}
70-
return result == 0;
64+
return MessageDigest.isEqual(expected, actual);
7165
}
7266
}

crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java

+4-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import org.springframework.security.crypto.codec.Utf8;
1919

20+
import java.security.MessageDigest;
21+
2022
/**
2123
* Utility for constant time comparison to prevent against timing attacks.
2224
*
@@ -33,16 +35,8 @@ class PasswordEncoderUtils {
3335
static boolean equals(String expected, String actual) {
3436
byte[] expectedBytes = bytesUtf8(expected);
3537
byte[] actualBytes = bytesUtf8(actual);
36-
int expectedLength = expectedBytes == null ? -1 : expectedBytes.length;
37-
int actualLength = actualBytes == null ? -1 : actualBytes.length;
38-
39-
int result = expectedLength == actualLength ? 0 : 1;
40-
for (int i = 0; i < actualLength; i++) {
41-
byte expectedByte = expectedLength <= 0 ? 0 : expectedBytes[i % expectedLength];
42-
byte actualByte = actualBytes[i % actualLength];
43-
result |= expectedByte ^ actualByte;
44-
}
45-
return result == 0;
38+
39+
return MessageDigest.isEqual(expectedBytes, actualBytes);
4640
}
4741

4842
private static byte[] bytesUtf8(String s) {

crypto/src/main/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoder.java

+2-16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.security.crypto.password;
1717

1818
import java.security.GeneralSecurityException;
19+
import java.security.MessageDigest;
1920
import java.security.NoSuchAlgorithmException;
2021
import java.util.Base64;
2122

@@ -141,22 +142,7 @@ private String encode(byte[] bytes) {
141142
public boolean matches(CharSequence rawPassword, String encodedPassword) {
142143
byte[] digested = decode(encodedPassword);
143144
byte[] salt = subArray(digested, 0, this.saltGenerator.getKeyLength());
144-
return matches(digested, encode(rawPassword, salt));
145-
}
146-
147-
/**
148-
* Constant time comparison to prevent against timing attacks.
149-
*/
150-
private static boolean matches(byte[] expected, byte[] actual) {
151-
if (expected.length != actual.length) {
152-
return false;
153-
}
154-
155-
int result = 0;
156-
for (int i = 0; i < expected.length; i++) {
157-
result |= expected[i] ^ actual[i];
158-
}
159-
return result == 0;
145+
return MessageDigest.isEqual(digested, encode(rawPassword, salt));
160146
}
161147

162148
private byte[] decode(String encodedBytes) {

crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java

+3-16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
2424
import org.springframework.security.crypto.keygen.KeyGenerators;
2525

26+
import java.security.MessageDigest;
27+
2628
/**
2729
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered
2830
* secure.
@@ -79,7 +81,7 @@ public String encode(CharSequence rawPassword) {
7981
public boolean matches(CharSequence rawPassword, String encodedPassword) {
8082
byte[] digested = decode(encodedPassword);
8183
byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength());
82-
return matches(digested, digest(rawPassword, salt));
84+
return MessageDigest.isEqual(digested, digest(rawPassword, salt));
8385
}
8486

8587
// internal helpers
@@ -105,21 +107,6 @@ private byte[] decode(CharSequence encodedPassword) {
105107
return Hex.decode(encodedPassword);
106108
}
107109

108-
/**
109-
* Constant time comparison to prevent against timing attacks.
110-
*/
111-
private boolean matches(byte[] expected, byte[] actual) {
112-
if (expected.length != actual.length) {
113-
return false;
114-
}
115-
116-
int result = 0;
117-
for (int i = 0; i < expected.length; i++) {
118-
result |= expected[i] ^ actual[i];
119-
}
120-
return result == 0;
121-
}
122-
123110
private static final int DEFAULT_ITERATIONS = 1024;
124111

125112
}

crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java

+2-9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.security.crypto.scrypt;
1717

18+
import java.security.MessageDigest;
1819
import java.util.Base64;
1920

2021
import org.apache.commons.logging.Log;
@@ -152,15 +153,7 @@ private boolean decodeAndCheckMatches(CharSequence rawPassword, String encodedPa
152153
byte[] generated = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization,
153154
keyLength);
154155

155-
if (derived.length != generated.length) {
156-
return false;
157-
}
158-
159-
int result = 0;
160-
for (int i = 0; i < derived.length; i++) {
161-
result |= derived[i] ^ generated[i];
162-
}
163-
return result == 0;
156+
return MessageDigest.isEqual(derived, generated);
164157
}
165158

166159
private String digest(CharSequence rawPassword, byte[] salt) {

web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java

+1-8
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,8 @@ private boolean isInstanceOfUserDetails(Authentication authentication) {
256256
private static boolean equals(String expected, String actual) {
257257
byte[] expectedBytes = bytesUtf8(expected);
258258
byte[] actualBytes = bytesUtf8(actual);
259-
if (expectedBytes.length != actualBytes.length) {
260-
return false;
261-
}
262259

263-
int result = 0;
264-
for (int i = 0; i < expectedBytes.length; i++) {
265-
result |= expectedBytes[i] ^ actualBytes[i];
266-
}
267-
return result == 0;
260+
return MessageDigest.isEqual(expectedBytes, actualBytes);
268261
}
269262

270263
private static byte[] bytesUtf8(String s) {

0 commit comments

Comments
 (0)