diff --git a/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2EncodingUtils.java b/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2EncodingUtils.java index 1cdeb21016..4502d58ee0 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2EncodingUtils.java +++ b/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2EncodingUtils.java @@ -58,7 +58,6 @@ final class Argon2EncodingUtils { */ static String encode(byte[] hash, Argon2Parameters parameters) throws IllegalArgumentException { StringBuilder stringBuilder = new StringBuilder(); - switch (parameters.getType()) { case Argon2Parameters.ARGON2_d: stringBuilder.append("$argon2d"); @@ -74,13 +73,10 @@ final class Argon2EncodingUtils { } stringBuilder.append("$v=").append(parameters.getVersion()).append("$m=").append(parameters.getMemory()) .append(",t=").append(parameters.getIterations()).append(",p=").append(parameters.getLanes()); - if (parameters.getSalt() != null) { stringBuilder.append("$").append(b64encoder.encodeToString(parameters.getSalt())); } - stringBuilder.append("$").append(b64encoder.encodeToString(hash)); - return stringBuilder.toString(); } @@ -106,15 +102,11 @@ final class Argon2EncodingUtils { */ static Argon2Hash decode(String encodedHash) throws IllegalArgumentException { Argon2Parameters.Builder paramsBuilder; - String[] parts = encodedHash.split("\\$"); - if (parts.length < 4) { throw new IllegalArgumentException("Invalid encoded Argon2-hash"); } - int currentPart = 1; - switch (parts[currentPart++]) { case "argon2d": paramsBuilder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_d); @@ -128,41 +120,27 @@ final class Argon2EncodingUtils { default: throw new IllegalArgumentException("Invalid algorithm type: " + parts[0]); } - if (parts[currentPart].startsWith("v=")) { paramsBuilder.withVersion(Integer.parseInt(parts[currentPart].substring(2))); currentPart++; } - String[] performanceParams = parts[currentPart++].split(","); - if (performanceParams.length != 3) { throw new IllegalArgumentException("Amount of performance parameters invalid"); } - - if (performanceParams[0].startsWith("m=")) { - paramsBuilder.withMemoryAsKB(Integer.parseInt(performanceParams[0].substring(2))); - } - else { + if (!performanceParams[0].startsWith("m=")) { throw new IllegalArgumentException("Invalid memory parameter"); } - - if (performanceParams[1].startsWith("t=")) { - paramsBuilder.withIterations(Integer.parseInt(performanceParams[1].substring(2))); - } - else { + paramsBuilder.withMemoryAsKB(Integer.parseInt(performanceParams[0].substring(2))); + if (!performanceParams[1].startsWith("t=")) { throw new IllegalArgumentException("Invalid iterations parameter"); } - - if (performanceParams[2].startsWith("p=")) { - paramsBuilder.withParallelism(Integer.parseInt(performanceParams[2].substring(2))); - } - else { + paramsBuilder.withIterations(Integer.parseInt(performanceParams[1].substring(2))); + if (!performanceParams[2].startsWith("p=")) { throw new IllegalArgumentException("Invalid parallelity parameter"); } - + paramsBuilder.withParallelism(Integer.parseInt(performanceParams[2].substring(2))); paramsBuilder.withSalt(b64decoder.decode(parts[currentPart++])); - return new Argon2Hash(b64decoder.decode(parts[currentPart]), paramsBuilder.build()); } diff --git a/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java index cca1800628..9a87152cc4 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java @@ -68,30 +68,27 @@ public class Argon2PasswordEncoder implements PasswordEncoder { private final BytesKeyGenerator saltGenerator; + public Argon2PasswordEncoder() { + this(DEFAULT_SALT_LENGTH, DEFAULT_HASH_LENGTH, DEFAULT_PARALLELISM, DEFAULT_MEMORY, DEFAULT_ITERATIONS); + } + public Argon2PasswordEncoder(int saltLength, int hashLength, int parallelism, int memory, int iterations) { this.hashLength = hashLength; this.parallelism = parallelism; this.memory = memory; this.iterations = iterations; - this.saltGenerator = KeyGenerators.secureRandom(saltLength); } - public Argon2PasswordEncoder() { - this(DEFAULT_SALT_LENGTH, DEFAULT_HASH_LENGTH, DEFAULT_PARALLELISM, DEFAULT_MEMORY, DEFAULT_ITERATIONS); - } - @Override public String encode(CharSequence rawPassword) { byte[] salt = this.saltGenerator.generateKey(); byte[] hash = new byte[this.hashLength]; - Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id).withSalt(salt) .withParallelism(this.parallelism).withMemoryAsKB(this.memory).withIterations(this.iterations).build(); Argon2BytesGenerator generator = new Argon2BytesGenerator(); generator.init(params); generator.generateBytes(rawPassword.toString().toCharArray(), hash); - return Argon2EncodingUtils.encode(hash, params); } @@ -101,9 +98,7 @@ public class Argon2PasswordEncoder implements PasswordEncoder { this.logger.warn("password hash is null"); return false; } - Argon2EncodingUtils.Argon2Hash decoded; - try { decoded = Argon2EncodingUtils.decode(encodedPassword); } @@ -111,13 +106,10 @@ public class Argon2PasswordEncoder implements PasswordEncoder { this.logger.warn("Malformed password hash", ex); return false; } - byte[] hashBytes = new byte[decoded.getHash().length]; - Argon2BytesGenerator generator = new Argon2BytesGenerator(); generator.init(decoded.getParameters()); generator.generateBytes(rawPassword.toString().toCharArray(), hashBytes); - return constantTimeArrayEquals(decoded.getHash(), hashBytes); } @@ -127,9 +119,7 @@ public class Argon2PasswordEncoder implements PasswordEncoder { this.logger.warn("password hash is null"); return false; } - Argon2Parameters parameters = Argon2EncodingUtils.decode(encodedPassword).getParameters(); - return parameters.getMemory() < this.memory || parameters.getIterations() < this.iterations; } @@ -137,7 +127,6 @@ public class Argon2PasswordEncoder implements PasswordEncoder { if (expected.length != actual.length) { return false; } - int result = 0; for (int i = 0; i < expected.length; i++) { result |= expected[i] ^ actual[i]; diff --git a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java index 43311f6f03..d17511b033 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java @@ -106,15 +106,15 @@ public class BCryptPasswordEncoder implements PasswordEncoder { if (rawPassword == null) { throw new IllegalArgumentException("rawPassword cannot be null"); } + String salt = getSalt(); + return BCrypt.hashpw(rawPassword.toString(), salt); + } - String salt; + private String getSalt() { if (this.random != null) { - salt = BCrypt.gensalt(this.version.getVersion(), this.strength, this.random); - } - else { - salt = BCrypt.gensalt(this.version.getVersion(), this.strength); + return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random); } - return BCrypt.hashpw(rawPassword.toString(), salt); + return BCrypt.gensalt(this.version.getVersion(), this.strength); } @Override @@ -122,17 +122,14 @@ public class BCryptPasswordEncoder implements PasswordEncoder { if (rawPassword == null) { throw new IllegalArgumentException("rawPassword cannot be null"); } - if (encodedPassword == null || encodedPassword.length() == 0) { this.logger.warn("Empty encoded password"); return false; } - if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) { this.logger.warn("Encoded password does not look like BCrypt"); return false; } - return BCrypt.checkpw(rawPassword.toString(), encodedPassword); } @@ -142,15 +139,12 @@ public class BCryptPasswordEncoder implements PasswordEncoder { this.logger.warn("Empty encoded password"); return false; } - Matcher matcher = this.BCRYPT_PATTERN.matcher(encodedPassword); if (!matcher.matches()) { throw new IllegalArgumentException("Encoded password does not look like BCrypt: " + encodedPassword); } - else { - int strength = Integer.parseInt(matcher.group(2)); - return strength < this.strength; - } + int strength = Integer.parseInt(matcher.group(2)); + return strength < this.strength; } /** @@ -160,7 +154,11 @@ public class BCryptPasswordEncoder implements PasswordEncoder { */ public enum BCryptVersion { - $2A("$2a"), $2Y("$2y"), $2B("$2b"); + $2A("$2a"), + + $2Y("$2y"), + + $2B("$2b"); private final String version; diff --git a/crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java b/crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java index 48360ca934..2691e75550 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java +++ b/crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java @@ -27,8 +27,7 @@ package org.springframework.security.crypto.codec; */ public final class Hex { - private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', - 'f' }; + private static final char[] HEX = "0123456789abcdef".toCharArray(); private Hex() { } @@ -36,7 +35,6 @@ public final class Hex { public static char[] encode(byte[] bytes) { final int nBytes = bytes.length; char[] result = new char[2 * nBytes]; - int j = 0; for (byte aByte : bytes) { // Char for top 4 bits @@ -44,23 +42,18 @@ public final class Hex { // Bottom 4 result[j++] = HEX[(0x0F & aByte)]; } - return result; } public static byte[] decode(CharSequence s) { int nChars = s.length(); - if (nChars % 2 != 0) { throw new IllegalArgumentException("Hex-encoded string must have an even number of characters"); } - byte[] result = new byte[nChars / 2]; - for (int i = 0; i < nChars; i += 2) { int msb = Character.digit(s.charAt(i), 16); int lsb = Character.digit(s.charAt(i + 1), 16); - if (msb < 0 || lsb < 0) { throw new IllegalArgumentException( "Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position"); diff --git a/crypto/src/main/java/org/springframework/security/crypto/codec/Utf8.java b/crypto/src/main/java/org/springframework/security/crypto/codec/Utf8.java index 7dd723a99b..093de89fdd 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/codec/Utf8.java +++ b/crypto/src/main/java/org/springframework/security/crypto/codec/Utf8.java @@ -44,7 +44,6 @@ public final class Utf8 { ByteBuffer bytes = CHARSET.newEncoder().encode(CharBuffer.wrap(string)); byte[] bytesCopy = new byte[bytes.limit()]; System.arraycopy(bytes.array(), 0, bytesCopy, 0, bytes.limit()); - return bytesCopy; } catch (CharacterCodingException ex) { diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java index bed724b6af..d8f2fc12b6 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java @@ -52,38 +52,6 @@ public final class AesBytesEncryptor implements BytesEncryptor { private static final String AES_GCM_ALGORITHM = "AES/GCM/NoPadding"; - public enum CipherAlgorithm { - - CBC(AES_CBC_ALGORITHM, NULL_IV_GENERATOR), GCM(AES_GCM_ALGORITHM, KeyGenerators.secureRandom(16)); - - private BytesKeyGenerator ivGenerator; - - private String name; - - CipherAlgorithm(String name, BytesKeyGenerator ivGenerator) { - this.name = name; - this.ivGenerator = ivGenerator; - } - - @Override - public String toString() { - return this.name; - } - - public AlgorithmParameterSpec getParameterSpec(byte[] iv) { - return (this != CBC) ? new GCMParameterSpec(128, iv) : new IvParameterSpec(iv); - } - - public Cipher createCipher() { - return CipherUtils.newCipher(this.toString()); - } - - public BytesKeyGenerator defaultIvGenerator() { - return this.ivGenerator; - } - - } - public AesBytesEncryptor(String password, CharSequence salt) { this(password, salt, null); } @@ -159,4 +127,38 @@ public final class AesBytesEncryptor implements BytesEncryptor { }; + public enum CipherAlgorithm { + + CBC(AES_CBC_ALGORITHM, NULL_IV_GENERATOR), + + GCM(AES_GCM_ALGORITHM, KeyGenerators.secureRandom(16)); + + private BytesKeyGenerator ivGenerator; + + private String name; + + CipherAlgorithm(String name, BytesKeyGenerator ivGenerator) { + this.name = name; + this.ivGenerator = ivGenerator; + } + + @Override + public String toString() { + return this.name; + } + + public AlgorithmParameterSpec getParameterSpec(byte[] iv) { + return (this != CBC) ? new GCMParameterSpec(128, iv) : new IvParameterSpec(iv); + } + + public Cipher createCipher() { + return CipherUtils.newCipher(this.toString()); + } + + public BytesKeyGenerator defaultIvGenerator() { + return this.ivGenerator; + } + + } + } diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java index 84181d885b..3aa6e6eab3 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java @@ -33,7 +33,6 @@ import org.springframework.security.crypto.util.EncodingUtils; * "AES/CBC/PKCS5Padding". * * @author William Tran - * */ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryptor { @@ -46,10 +45,9 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp } @Override + @SuppressWarnings("deprecation") public byte[] encrypt(byte[] bytes) { byte[] iv = this.ivGenerator.generateKey(); - - @SuppressWarnings("deprecation") PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( new CBCBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()), new PKCS7Padding()); blockCipher.init(true, new ParametersWithIV(this.secretKey, iv)); @@ -58,11 +56,10 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp } @Override + @SuppressWarnings("deprecation") public byte[] decrypt(byte[] encryptedBytes) { byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); - - @SuppressWarnings("deprecation") PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( new CBCBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()), new PKCS7Padding()); blockCipher.init(false, new ParametersWithIV(this.secretKey, iv)); diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java index 044d6a2a77..cce6dd6d99 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java @@ -44,23 +44,20 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp } @Override + @SuppressWarnings("deprecation") public byte[] encrypt(byte[] bytes) { byte[] iv = this.ivGenerator.generateKey(); - - @SuppressWarnings("deprecation") GCMBlockCipher blockCipher = new GCMBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()); blockCipher.init(true, new AEADParameters(this.secretKey, 128, iv, null)); - byte[] encrypted = process(blockCipher, bytes); return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; } @Override + @SuppressWarnings("deprecation") public byte[] decrypt(byte[] encryptedBytes) { byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); - - @SuppressWarnings("deprecation") GCMBlockCipher blockCipher = new GCMBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()); blockCipher.init(false, new AEADParameters(this.secretKey, 128, iv, null)); return process(blockCipher, encryptedBytes); diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java index e4e2b88f1d..ff7505b289 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java @@ -113,13 +113,13 @@ public final class Encryptors { * environments where working with plain text strings is desired for simplicity. */ public static TextEncryptor noOpText() { - return NO_OP_TEXT_INSTANCE; + return NoOpTextEncryptor.INSTANCE; } - private static final TextEncryptor NO_OP_TEXT_INSTANCE = new NoOpTextEncryptor(); - private static final class NoOpTextEncryptor implements TextEncryptor { + static final TextEncryptor INSTANCE = new NoOpTextEncryptor(); + @Override public String encrypt(String text) { return text; diff --git a/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java b/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java index a3f995d23d..eb27d4a058 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java +++ b/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java @@ -78,7 +78,6 @@ public final class PasswordEncoderFactories { new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256")); encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder()); encoders.put("argon2", new Argon2PasswordEncoder()); - return new DelegatingPasswordEncoder(encodingId, encoders); } diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/SecureRandomBytesKeyGenerator.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/SecureRandomBytesKeyGenerator.java index b50bbe3bf5..e3cb10adfe 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/keygen/SecureRandomBytesKeyGenerator.java +++ b/crypto/src/main/java/org/springframework/security/crypto/keygen/SecureRandomBytesKeyGenerator.java @@ -28,6 +28,8 @@ import java.security.SecureRandom; */ final class SecureRandomBytesKeyGenerator implements BytesKeyGenerator { + private static final int DEFAULT_KEY_LENGTH = 8; + private final SecureRandom random; private final int keyLength; @@ -59,6 +61,4 @@ final class SecureRandomBytesKeyGenerator implements BytesKeyGenerator { return bytes; } - private static final int DEFAULT_KEY_LENGTH = 8; - } diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java index 808e908c05..eb9687c0fc 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java @@ -75,11 +75,9 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { if (salt == null) { return hash; } - byte[] hashAndSalt = new byte[hash.length + salt.length]; System.arraycopy(hash, 0, hashAndSalt, 0, hash.length); System.arraycopy(salt, 0, hashAndSalt, hash.length, salt.length); - return hashAndSalt; } @@ -98,42 +96,39 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { } private String encode(CharSequence rawPassword, byte[] salt) { - MessageDigest sha; + MessageDigest sha = getSha(rawPassword); + if (salt != null) { + sha.update(salt); + } + byte[] hash = combineHashAndSalt(sha.digest(), salt); + String prefix = getPrefix(salt); + return prefix + Utf8.decode(Base64.getEncoder().encode(hash)); + } + private MessageDigest getSha(CharSequence rawPassword) { try { - sha = MessageDigest.getInstance("SHA"); + MessageDigest sha = MessageDigest.getInstance("SHA"); sha.update(Utf8.encode(rawPassword)); + return sha; } catch (java.security.NoSuchAlgorithmException ex) { throw new IllegalStateException("No SHA implementation available!"); } + } - if (salt != null) { - sha.update(salt); - } - - byte[] hash = combineHashAndSalt(sha.digest(), salt); - - String prefix; - + private String getPrefix(byte[] salt) { if (salt == null || salt.length == 0) { - prefix = this.forceLowerCasePrefix ? SHA_PREFIX_LC : SHA_PREFIX; + return this.forceLowerCasePrefix ? SHA_PREFIX_LC : SHA_PREFIX; } - else { - prefix = this.forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX; - } - - return prefix + Utf8.decode(Base64.getEncoder().encode(hash)); + return this.forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX; } private byte[] extractSalt(String encPass) { String encPassNoLabel = encPass.substring(6); - byte[] hashAndSalt = Base64.getDecoder().decode(encPassNoLabel.getBytes()); int saltLength = hashAndSalt.length - SHA_LENGTH; byte[] salt = new byte[saltLength]; System.arraycopy(hashAndSalt, SHA_LENGTH, salt, 0, saltLength); - return salt; } @@ -151,28 +146,24 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { private boolean matches(String rawPassword, String encodedPassword) { String prefix = extractPrefix(encodedPassword); - if (prefix == null) { return PasswordEncoderUtils.equals(encodedPassword, rawPassword); } + byte[] salt = getSalt(encodedPassword, prefix); + int startOfHash = prefix.length(); + String encodedRawPass = encode(rawPassword, salt).substring(startOfHash); + return PasswordEncoderUtils.equals(encodedRawPass, encodedPassword.substring(startOfHash)); + } - byte[] salt; + private byte[] getSalt(String encodedPassword, String prefix) { if (prefix.equals(SSHA_PREFIX) || prefix.equals(SSHA_PREFIX_LC)) { - salt = extractSalt(encodedPassword); + return extractSalt(encodedPassword); } - else if (!prefix.equals(SHA_PREFIX) && !prefix.equals(SHA_PREFIX_LC)) { + if (!prefix.equals(SHA_PREFIX) && !prefix.equals(SHA_PREFIX_LC)) { throw new IllegalArgumentException("Unsupported password prefix '" + prefix + "'"); } - else { - // Standard SHA - salt = null; - } - - int startOfHash = prefix.length(); - - String encodedRawPass = encode(rawPassword, salt).substring(startOfHash); - - return PasswordEncoderUtils.equals(encodedRawPass, encodedPassword.substring(startOfHash)); + // Standard SHA + return null; } /** @@ -182,13 +173,10 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { if (!encPass.startsWith("{")) { return null; } - int secondBrace = encPass.lastIndexOf('}'); - if (secondBrace < 0) { throw new IllegalArgumentException("Couldn't find closing brace for SHA prefix"); } - return encPass.substring(0, secondBrace + 1); } diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/Md4.java b/crypto/src/main/java/org/springframework/security/crypto/password/Md4.java index 29511f2358..d201531ed0 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/Md4.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/Md4.java @@ -76,16 +76,13 @@ class Md4 { update(this.buffer, 0); this.bufferOffset = 0; } - while (this.bufferOffset < C) { this.buffer[this.bufferOffset++] = (byte) 0x00; } - long bitCount = this.byteCount * 8; for (int i = 0; i < 64; i += 8) { this.buffer[this.bufferOffset++] = (byte) (bitCount >>> (i)); } - update(this.buffer, 0); digest(buffer, offset); } diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java index 035d132709..f02583ae7f 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java @@ -111,10 +111,8 @@ public class Md4PasswordEncoder implements PasswordEncoder { } String saltedPassword = rawPassword + salt; byte[] saltedPasswordBytes = Utf8.encode(saltedPassword); - Md4 md4 = new Md4(); md4.update(saltedPasswordBytes, 0, saltedPasswordBytes.length); - byte[] digest = md4.digest(); String encoded = encode(digest); return salt + encoded; @@ -124,9 +122,7 @@ public class Md4PasswordEncoder implements PasswordEncoder { if (this.encodeHashAsBase64) { return Utf8.decode(Base64.getEncoder().encode(digest)); } - else { - return new String(Hex.encode(digest)); - } + return new String(Hex.encode(digest)); } /** diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/MessageDigestPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/MessageDigestPasswordEncoder.java index 606e5b6923..cdc6032677 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/MessageDigestPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/MessageDigestPasswordEncoder.java @@ -123,7 +123,6 @@ public class MessageDigestPasswordEncoder implements PasswordEncoder { private String digest(String salt, CharSequence rawPassword) { String saltedPassword = rawPassword + salt; - byte[] digest = this.digester.digest(Utf8.encode(saltedPassword)); String encoded = encode(digest); return salt + encoded; @@ -133,9 +132,7 @@ public class MessageDigestPasswordEncoder implements PasswordEncoder { if (this.encodeHashAsBase64) { return Utf8.decode(Base64.getEncoder().encode(digest)); } - else { - return new String(Hex.encode(digest)); - } + return new String(Hex.encode(digest)); } /** diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java index 05feed195e..d092acf3ee 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java @@ -33,6 +33,11 @@ package org.springframework.security.crypto.password; @Deprecated public final class NoOpPasswordEncoder implements PasswordEncoder { + private static final PasswordEncoder INSTANCE = new NoOpPasswordEncoder(); + + private NoOpPasswordEncoder() { + } + @Override public String encode(CharSequence rawPassword) { return rawPassword.toString(); @@ -50,9 +55,4 @@ public final class NoOpPasswordEncoder implements PasswordEncoder { return INSTANCE; } - private static final PasswordEncoder INSTANCE = new NoOpPasswordEncoder(); - - private NoOpPasswordEncoder() { - } - } 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 bcd0decacd..4e1a69a949 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 @@ -39,17 +39,13 @@ final class PasswordEncoderUtils { static boolean equals(String expected, String actual) { byte[] expectedBytes = bytesUtf8(expected); byte[] actualBytes = bytesUtf8(actual); - return MessageDigest.isEqual(expectedBytes, actualBytes); } private static byte[] bytesUtf8(String s) { - if (s == null) { - return null; - } // need to check if Utf8.encode() runs in constant time (probably not). // This may leak length of string. - return Utf8.encode(s); + return (s != null) ? Utf8.encode(s) : null; } } 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 12854c95c7..73e660417c 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 @@ -112,11 +112,11 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder { String algorithmName = secretKeyFactoryAlgorithm.name(); try { SecretKeyFactory.getInstance(algorithmName); + this.algorithm = algorithmName; } catch (NoSuchAlgorithmException ex) { throw new IllegalArgumentException("Invalid algorithm '" + algorithmName + "'.", ex); } - this.algorithm = algorithmName; } /** 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 b93b775f94..e19e445ce2 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 @@ -50,6 +50,8 @@ import org.springframework.security.crypto.util.EncodingUtils; @Deprecated public final class StandardPasswordEncoder implements PasswordEncoder { + private static final int DEFAULT_ITERATIONS = 1024; + private final Digester digester; private final byte[] secret; @@ -104,6 +106,4 @@ public final class StandardPasswordEncoder implements PasswordEncoder { return Hex.decode(encodedPassword); } - 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 01d1410130..98bafd4be2 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 @@ -109,7 +109,6 @@ public class SCryptPasswordEncoder implements PasswordEncoder { if (saltLength < 1 || saltLength > Integer.MAX_VALUE) { throw new IllegalArgumentException("Salt length must be >= 1 and <= " + Integer.MAX_VALUE); } - this.cpuCost = cpuCost; this.memoryCost = memoryCost; this.parallelization = parallelization; @@ -136,56 +135,43 @@ public class SCryptPasswordEncoder implements PasswordEncoder { if (encodedPassword == null || encodedPassword.isEmpty()) { return false; } - String[] parts = encodedPassword.split("\\$"); - if (parts.length != 4) { throw new IllegalArgumentException("Encoded password does not look like SCrypt: " + encodedPassword); } - long params = Long.parseLong(parts[1], 16); - int cpuCost = (int) Math.pow(2, params >> 16 & 0xffff); int memoryCost = (int) params >> 8 & 0xff; int parallelization = (int) params & 0xff; - return cpuCost < this.cpuCost || memoryCost < this.memoryCost || parallelization < this.parallelization; } private boolean decodeAndCheckMatches(CharSequence rawPassword, String encodedPassword) { String[] parts = encodedPassword.split("\\$"); - if (parts.length != 4) { return false; } - long params = Long.parseLong(parts[1], 16); byte[] salt = decodePart(parts[2]); byte[] derived = decodePart(parts[3]); - int cpuCost = (int) Math.pow(2, params >> 16 & 0xffff); int memoryCost = (int) params >> 8 & 0xff; int parallelization = (int) params & 0xff; - byte[] generated = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization, this.keyLength); - return MessageDigest.isEqual(derived, generated); } private String digest(CharSequence rawPassword, byte[] salt) { byte[] derived = SCrypt.generate(Utf8.encode(rawPassword), salt, this.cpuCost, this.memoryCost, this.parallelization, this.keyLength); - String params = Long.toString( ((int) (Math.log(this.cpuCost) / Math.log(2)) << 16L) | this.memoryCost << 8 | this.parallelization, 16); - StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2); sb.append("$").append(params).append('$'); sb.append(encodePart(salt)).append('$'); sb.append(encodePart(derived)); - return sb.toString(); }