Polish spring-security-crypto main code

Manually polish `spring-security-crypto` following the formatting
and checkstyle fixes.

Issue gh-8945
This commit is contained in:
Phillip Webb 2020-07-30 23:36:42 -07:00 committed by Rob Winch
parent 771ef0dadc
commit da22e97147
20 changed files with 105 additions and 193 deletions

View File

@ -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());
}

View File

@ -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];

View File

@ -106,33 +106,30 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
String salt;
if (this.random != null) {
salt = BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
}
else {
salt = BCrypt.gensalt(this.version.getVersion(), this.strength);
}
String salt = getSalt();
return BCrypt.hashpw(rawPassword.toString(), salt);
}
private String getSalt() {
if (this.random != null) {
return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
}
return BCrypt.gensalt(this.version.getVersion(), this.strength);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
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;

View File

@ -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");

View File

@ -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) {

View File

@ -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;
}
}
}

View File

@ -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));

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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));
}
/**

View File

@ -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));
}
/**

View File

@ -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() {
}
}

View File

@ -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;
}
}

View File

@ -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;
}
/**

View File

@ -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;
}

View File

@ -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();
}