Skip to content

Commit b6cda4f

Browse files
committed
Derive hash length for Pbkdf2PasswordEncoder
Closes gh-10489
1 parent a173a71 commit b6cda4f

File tree

4 files changed

+74
-16
lines changed

4 files changed

+74
-16
lines changed

crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public static PasswordEncoder createDelegatingPasswordEncoder() {
7979
pbkdf2PasswordEncoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1);
8080
encoders.put("pbkdf2", pbkdf2PasswordEncoder);
8181

82+
encoders.put("pbkdf2-sha256", new Pbkdf2PasswordEncoder());
8283
encoders.put("scrypt", new SCryptPasswordEncoder());
8384
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
8485
encoders.put("SHA-256",

crypto/src/main/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoder.java

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@
3636
* <li>a configurable random salt value length (default is {@value #DEFAULT_SALT_LENGTH}
3737
* bytes)</li>
3838
* <li>a configurable number of iterations (default is {@value #DEFAULT_ITERATIONS})</li>
39-
* <li>a configurable output hash width (default is {@value #DEFAULT_HASH_WIDTH}
40-
* bits)</li>
4139
* <li>a configurable key derivation function (see {@link SecretKeyFactoryAlgorithm})</li>
4240
* <li>a configurable secret appended to the random salt (default is empty)</li>
4341
* </ul>
@@ -52,28 +50,43 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
5250

5351
private static final int DEFAULT_SALT_LENGTH = 16;
5452

55-
private static final int DEFAULT_HASH_WIDTH = 256;
56-
5753
private static final int DEFAULT_ITERATIONS = 310000;
5854

5955
private final BytesKeyGenerator saltGenerator;
6056

6157
private final byte[] secret;
6258

63-
private final int hashWidth;
64-
6559
private final int iterations;
6660

6761
private String algorithm = SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256.name();
6862

63+
private int hashWidth = 256; // SHA-256
64+
65+
// @formatter:off
66+
/*
67+
The length of the hash should be derived from the hashing algorithm.
68+
69+
For example:
70+
SHA-1 - 160 bits (20 bytes)
71+
SHA-256 - 256 bits (32 bytes)
72+
SHA-512 - 512 bits (64 bytes)
73+
74+
However, the original configuration for PBKDF2 was hashWidth=256 and algorithm=SHA-1, which is incorrect.
75+
The default configuration has been updated to hashWidth=256 and algorithm=SHA-256 (see gh-10506).
76+
In order to preserve backwards compatibility, the variable 'overrideHashWidth' has been introduced
77+
to indicate usage of the deprecated constructors that allow (and honor) a hashWidth parameter.
78+
*/
79+
// @formatter:on
80+
private boolean overrideHashWidth = true;
81+
6982
private boolean encodeHashAsBase64;
7083

7184
/**
7285
* Constructs a PBKDF2 password encoder with no additional secret value. There will be
7386
* a salt length of {@value #DEFAULT_SALT_LENGTH} bytes, {@value #DEFAULT_ITERATIONS}
74-
* iterations and a hash width of {@value #DEFAULT_HASH_WIDTH} bits. The default is
75-
* based upon aiming for .5 seconds to validate the password when this class was
76-
* added. Users should tune password verification to their own systems.
87+
* iterations and SHA-256 algorithm. The default is based upon aiming for .5 seconds
88+
* to validate the password when this class was added. Users should tune password
89+
* verification to their own systems.
7790
*/
7891
public Pbkdf2PasswordEncoder() {
7992
this("");
@@ -82,24 +95,22 @@ public Pbkdf2PasswordEncoder() {
8295
/**
8396
* Constructs a standard password encoder with a secret value which is also included
8497
* in the password hash. There will be a salt length of {@value #DEFAULT_SALT_LENGTH}
85-
* bytes, {@value #DEFAULT_ITERATIONS} iterations and a hash width of
86-
* {@value #DEFAULT_HASH_WIDTH} bits.
98+
* bytes, {@value #DEFAULT_ITERATIONS} iterations and SHA-256 algorithm.
8799
* @param secret the secret key used in the encoding process (should not be shared)
88100
*/
89101
public Pbkdf2PasswordEncoder(CharSequence secret) {
90-
this(secret, DEFAULT_SALT_LENGTH, DEFAULT_ITERATIONS, DEFAULT_HASH_WIDTH);
102+
this(secret, DEFAULT_SALT_LENGTH);
91103
}
92104

93105
/**
94106
* Constructs a standard password encoder with a secret value as well as salt length.
95-
* There will be {@value #DEFAULT_ITERATIONS} iterations and a hash width of
96-
* {@value #DEFAULT_HASH_WIDTH} bits.
107+
* There will be {@value #DEFAULT_ITERATIONS} iterations and SHA-256 algorithm.
97108
* @param secret the secret
98109
* @param saltLength the salt length (in bytes)
99110
* @since 5.5
100111
*/
101112
public Pbkdf2PasswordEncoder(CharSequence secret, int saltLength) {
102-
this(secret, saltLength, DEFAULT_ITERATIONS, DEFAULT_HASH_WIDTH);
113+
this(secret, saltLength, DEFAULT_ITERATIONS, SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256);
103114
}
104115

105116
/**
@@ -109,7 +120,11 @@ public Pbkdf2PasswordEncoder(CharSequence secret, int saltLength) {
109120
* @param iterations the number of iterations. Users should aim for taking about .5
110121
* seconds on their own system.
111122
* @param hashWidth the size of the hash (in bits)
123+
* @deprecated Use
124+
* {@link #Pbkdf2PasswordEncoder(CharSequence, int, int, SecretKeyFactoryAlgorithm)}
125+
* instead
112126
*/
127+
@Deprecated
113128
public Pbkdf2PasswordEncoder(CharSequence secret, int iterations, int hashWidth) {
114129
this(secret, DEFAULT_SALT_LENGTH, iterations, hashWidth);
115130
}
@@ -123,12 +138,36 @@ public Pbkdf2PasswordEncoder(CharSequence secret, int iterations, int hashWidth)
123138
* seconds on their own system.
124139
* @param hashWidth the size of the hash (in bits)
125140
* @since 5.5
141+
* @deprecated Use
142+
* {@link #Pbkdf2PasswordEncoder(CharSequence, int, int, SecretKeyFactoryAlgorithm)}
143+
* instead
126144
*/
145+
@Deprecated
127146
public Pbkdf2PasswordEncoder(CharSequence secret, int saltLength, int iterations, int hashWidth) {
128147
this.secret = Utf8.encode(secret);
129148
this.saltGenerator = KeyGenerators.secureRandom(saltLength);
130149
this.iterations = iterations;
131150
this.hashWidth = hashWidth;
151+
this.overrideHashWidth = false; // Honor 'hashWidth' to preserve backwards
152+
// compatibility
153+
}
154+
155+
/**
156+
* Constructs a standard password encoder with a secret value as well as salt length,
157+
* iterations and algorithm.
158+
* @param secret the secret
159+
* @param saltLength the salt length (in bytes)
160+
* @param iterations the number of iterations. Users should aim for taking about .5
161+
* seconds on their own system.
162+
* @param secretKeyFactoryAlgorithm the algorithm to use
163+
* @since 6.0
164+
*/
165+
public Pbkdf2PasswordEncoder(CharSequence secret, int saltLength, int iterations,
166+
SecretKeyFactoryAlgorithm secretKeyFactoryAlgorithm) {
167+
this.secret = Utf8.encode(secret);
168+
this.saltGenerator = KeyGenerators.secureRandom(saltLength);
169+
this.iterations = iterations;
170+
setAlgorithm(secretKeyFactoryAlgorithm);
132171
}
133172

134173
/**
@@ -153,6 +192,10 @@ public void setAlgorithm(SecretKeyFactoryAlgorithm secretKeyFactoryAlgorithm) {
153192
catch (NoSuchAlgorithmException ex) {
154193
throw new IllegalArgumentException("Invalid algorithm '" + algorithmName + "'.", ex);
155194
}
195+
if (this.overrideHashWidth) {
196+
this.hashWidth = SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.equals(secretKeyFactoryAlgorithm) ? 160
197+
: SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256.equals(secretKeyFactoryAlgorithm) ? 256 : 512;
198+
}
156199
}
157200

158201
/**

crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -75,6 +75,12 @@ public void matchesWhenPbkdf2ThenWorks() {
7575
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
7676
}
7777

78+
@Test
79+
public void matchesWhenPbkdf2Sha256ThenWorks() {
80+
String encodedPassword = "{pbkdf2-sha256}fefe5120467e5d4ccff442dbb2fa86d276262d97435c0c54e5eebced51ffd144fcb05eb53fea2677216c4f3250010006";
81+
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
82+
}
83+
7884
@Test
7985
public void matchesWhenSCryptThenWorks() {
8086
String encodedPassword = "{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=";

crypto/src/test/java/org/springframework/security/crypto/password/Pbkdf2PasswordEncoderTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ public void matches() {
6363
assertThat(this.encoder.matches("password", result)).isTrue();
6464
}
6565

66+
@Test
67+
public void matchesWhenDefaultsThenSuccess() {
68+
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
69+
String rawPassword = "password";
70+
String encodedPassword = "fefe5120467e5d4ccff442dbb2fa86d276262d97435c0c54e5eebced51ffd144fcb05eb53fea2677216c4f3250010006";
71+
assertThat(encoder.matches(rawPassword, encodedPassword)).isTrue();
72+
}
73+
6674
@Test
6775
public void matchesWhenCustomSaltLengthThenSuccess() {
6876
String result = this.encoderSalt16.encode("password");

0 commit comments

Comments
 (0)