Pbkdf2PasswordEncoder allows custom SecretKeyFactory

Fixes gh-2742
This commit is contained in:
Rob Winch 2017-10-24 08:34:30 -05:00
parent b91aa19b35
commit fe8f3afbaf
2 changed files with 54 additions and 4 deletions

View File

@ -16,6 +16,7 @@
package org.springframework.security.crypto.password;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
@ -41,7 +42,7 @@ import static org.springframework.security.crypto.util.EncodingUtils.subArray;
* @since 4.1
*/
public class Pbkdf2PasswordEncoder implements PasswordEncoder {
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int DEFAULT_HASH_WIDTH = 256;
private static final int DEFAULT_ITERATIONS = 185000;
@ -50,6 +51,7 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
private final byte[] secret;
private final int hashWidth;
private final int iterations;
private String algorithm = SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name();
/**
* Constructs a PBKDF2 password encoder with no additional secret value. There will be
@ -86,6 +88,28 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
this.hashWidth = hashWidth;
}
/**
* Sets the algorithm to use. See
* <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecretKeyFactory">SecretKeyFactory Algorithms</a>
* @param secretKeyFactoryAlgorithm the algorithm to use (i.e.
* {@code Pbkdf2PasswordEncoder.PBKDF2_WITH_HMAC_SHA1},
* {@code Pbkdf2PasswordEncoder.PBKDF2_WITH_HMAC_SHA256},
* {@code Pbkdf2PasswordEncoder.PBKDF2_WITH_HMAC_SHA512})
*/
public void setAlgorithm(SecretKeyFactoryAlgorithm secretKeyFactoryAlgorithm) {
if(secretKeyFactoryAlgorithm == null) {
throw new IllegalArgumentException("secretKeyFactoryAlgorithm cannot be null");
}
String algorithmName = secretKeyFactoryAlgorithm.name();
try {
SecretKeyFactory.getInstance(algorithmName);
}
catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Invalid algorithm '" + algorithmName + "'.", e);
}
this.algorithm = algorithmName;
}
@Override
public String encode(CharSequence rawPassword) {
byte[] salt = this.saltGenerator.generateKey();
@ -119,11 +143,20 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
try {
PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(),
concatenate(salt, this.secret), this.iterations, this.hashWidth);
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
return concatenate(salt, skf.generateSecret(spec).getEncoded());
}
catch (GeneralSecurityException e) {
throw new IllegalStateException("Could not create hash", e);
}
}
}
/**
* The Algorithm used for creating the {@link SecretKeyFactory}
*/
public enum SecretKeyFactoryAlgorithm {
PBKDF2WithHmacSHA1,
PBKDF2WithHmacSHA256,
PBKDF2WithHmacSHA512
}
}

View File

@ -75,6 +75,23 @@ public class Pbkdf2PasswordEncoderTests {
assertThat(fixedHex).isEqualTo(encodedPassword);
}
@Test
public void encodeAndMatchWhenSha256ThenSuccess() {
this.encoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256);
String rawPassword = "password";
String encodedPassword = this.encoder.encode(rawPassword);
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
}
@Test
public void matchWhenSha256ThenSuccess() {
this.encoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256);
String rawPassword = "password";
String encodedPassword = "821447f994e2b04c5014e31fa9fca4ae1cc9f2188c4ed53d3ddb5ba7980982b51a0ecebfc0b81a79";
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
}
/**
* Used to find the iteration count that takes .5 seconds.
*/
@ -105,4 +122,4 @@ public class Pbkdf2PasswordEncoderTests {
}
System.out.println("Iterations " + iterations);
}
}
}