Pbkdf2PasswordEncoder allows custom SecretKeyFactory
Fixes gh-2742
This commit is contained in:
parent
b91aa19b35
commit
fe8f3afbaf
|
@ -16,6 +16,7 @@
|
||||||
package org.springframework.security.crypto.password;
|
package org.springframework.security.crypto.password;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
@ -41,7 +42,7 @@ import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
*/
|
*/
|
||||||
public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
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_HASH_WIDTH = 256;
|
||||||
private static final int DEFAULT_ITERATIONS = 185000;
|
private static final int DEFAULT_ITERATIONS = 185000;
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||||
private final byte[] secret;
|
private final byte[] secret;
|
||||||
private final int hashWidth;
|
private final int hashWidth;
|
||||||
private final int iterations;
|
private final int iterations;
|
||||||
|
private String algorithm = SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a PBKDF2 password encoder with no additional secret value. There will be
|
* 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;
|
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
|
@Override
|
||||||
public String encode(CharSequence rawPassword) {
|
public String encode(CharSequence rawPassword) {
|
||||||
byte[] salt = this.saltGenerator.generateKey();
|
byte[] salt = this.saltGenerator.generateKey();
|
||||||
|
@ -119,11 +143,20 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||||
try {
|
try {
|
||||||
PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(),
|
PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(),
|
||||||
concatenate(salt, this.secret), this.iterations, this.hashWidth);
|
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());
|
return concatenate(salt, skf.generateSecret(spec).getEncoded());
|
||||||
}
|
}
|
||||||
catch (GeneralSecurityException e) {
|
catch (GeneralSecurityException e) {
|
||||||
throw new IllegalStateException("Could not create hash", e);
|
throw new IllegalStateException("Could not create hash", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Algorithm used for creating the {@link SecretKeyFactory}
|
||||||
|
*/
|
||||||
|
public enum SecretKeyFactoryAlgorithm {
|
||||||
|
PBKDF2WithHmacSHA1,
|
||||||
|
PBKDF2WithHmacSHA256,
|
||||||
|
PBKDF2WithHmacSHA512
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -75,6 +75,23 @@ public class Pbkdf2PasswordEncoderTests {
|
||||||
assertThat(fixedHex).isEqualTo(encodedPassword);
|
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.
|
* Used to find the iteration count that takes .5 seconds.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue