From fe8f3afbafebc8579c0a113676e4e3953db24d8b Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 24 Oct 2017 08:34:30 -0500 Subject: [PATCH] Pbkdf2PasswordEncoder allows custom SecretKeyFactory Fixes gh-2742 --- .../password/Pbkdf2PasswordEncoder.java | 39 +++++++++++++++++-- .../password/Pbkdf2PasswordEncoderTests.java | 19 ++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) 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 698c6b2e7a..3ee6b011c6 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 @@ -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 + * SecretKeyFactory Algorithms + * @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); } } -} \ No newline at end of file + + /** + * The Algorithm used for creating the {@link SecretKeyFactory} + */ + public enum SecretKeyFactoryAlgorithm { + PBKDF2WithHmacSHA1, + PBKDF2WithHmacSHA256, + PBKDF2WithHmacSHA512 + } +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoderTests.java b/crypto/src/test/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoderTests.java index d641452141..1261a0dd36 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoderTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoderTests.java @@ -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); } -} \ No newline at end of file +}