Skip to content

Pbkdf2PasswordEncoder#

Pbkdf2PasswordEncoder#

  • Password-Based Key Derivation Function 2 (Pbkdf2) is a pretty easy slow-hashing function that performs an HMAC(Hashed Message Authentication Code) as many times as specified by an iterations argument.
  • The three parameters received by the last call are the values of key used for the encoding process, the number of iterations used to encode the password, and the size of the hash. The second and third parameters can influence the strength of the result.
  • You can choose more or fewer iterations as well as the length of the result. The longer the hash, the more powerful the password is.

1
2
3
PasswordEncoder p = new Pbkdf2PasswordEncoder();
PasswordEncoder p = new Pbkdf2PasswordEncoder("secret");
PasswordEncoder p = new Pbkdf2PasswordEncoder("secret", 185000, 256);
- The Pbkdf2 is a slow hashing mechanism so it will perform so many iterations and it will take enough measures to maintain that length of the hashing.

So this hashing is low compared to other encoders available in the spring security.

Pbkdf2PasswordEncoder.java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.crypto.password;

import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.security.crypto.util.EncodingUtils;

/**
 * A {@link PasswordEncoder} implementation that uses PBKDF2 with :
 * <ul>
 * <li>a configurable random salt value length (default is {@value #DEFAULT_SALT_LENGTH}
 * bytes)</li>
 * <li>a configurable number of iterations (default is {@value #DEFAULT_ITERATIONS})</li>
 * <li>a configurable output hash width (default is {@value #DEFAULT_HASH_WIDTH}
 * bits)</li>
 * <li>a configurable key derivation function (see {@link SecretKeyFactoryAlgorithm})</li>
 * <li>a configurable secret appended to the random salt (default is empty)</li>
 * </ul>
 * The algorithm is invoked on the concatenated bytes of the salt, secret and password.
 *
 * @author Rob Worsnop
 * @author Rob Winch
 * @author Loïc Guibert
 * @since 4.1
 */
public class Pbkdf2PasswordEncoder implements PasswordEncoder {

    private static final int DEFAULT_SALT_LENGTH = 8;

    private static final int DEFAULT_HASH_WIDTH = 256;

    private static final int DEFAULT_ITERATIONS = 185000;

    private final BytesKeyGenerator saltGenerator;

    private final byte[] secret;

    private final int hashWidth;

    private final int iterations;

    private String algorithm = SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name();

    private boolean encodeHashAsBase64;

    /**
     * Constructs a PBKDF2 password encoder with no additional secret value. There will be
     * a salt length of {@value #DEFAULT_SALT_LENGTH} bytes, {@value #DEFAULT_ITERATIONS}
     * iterations and a hash width of {@value #DEFAULT_HASH_WIDTH} bits. The default is
     * based upon aiming for .5 seconds to validate the password when this class was
     * added. Users should tune password verification to their own systems.
     */
    public Pbkdf2PasswordEncoder() {
        this("");
    }

    /**
     * Constructs a standard password encoder with a secret value which is also included
     * in the password hash. There will be a salt length of {@value #DEFAULT_SALT_LENGTH}
     * bytes, {@value #DEFAULT_ITERATIONS} iterations and a hash width of
     * {@value #DEFAULT_HASH_WIDTH} bits.
     * @param secret the secret key used in the encoding process (should not be shared)
     */
    public Pbkdf2PasswordEncoder(CharSequence secret) {
        this(secret, DEFAULT_SALT_LENGTH, DEFAULT_ITERATIONS, DEFAULT_HASH_WIDTH);
    }

    /**
     * Constructs a standard password encoder with a secret value as well as salt length.
     * There will be {@value #DEFAULT_ITERATIONS} iterations and a hash width of
     * {@value #DEFAULT_HASH_WIDTH} bits.
     * @param secret the secret
     * @param saltLength the salt length (in bytes)
     * @since 5.5
     */
    public Pbkdf2PasswordEncoder(CharSequence secret, int saltLength) {
        this(secret, saltLength, DEFAULT_ITERATIONS, DEFAULT_HASH_WIDTH);
    }

    /**
     * Constructs a standard password encoder with a secret value as well as iterations
     * and hash width. The salt length will be of {@value #DEFAULT_SALT_LENGTH} bytes.
     * @param secret the secret
     * @param iterations the number of iterations. Users should aim for taking about .5
     * seconds on their own system.
     * @param hashWidth the size of the hash (in bits)
     */
    public Pbkdf2PasswordEncoder(CharSequence secret, int iterations, int hashWidth) {
        this(secret, DEFAULT_SALT_LENGTH, iterations, hashWidth);
    }

    /**
     * Constructs a standard password encoder with a secret value as well as salt length,
     * iterations and hash width.
     * @param secret the secret
     * @param saltLength the salt length (in bytes)
     * @param iterations the number of iterations. Users should aim for taking about .5
     * seconds on their own system.
     * @param hashWidth the size of the hash (in bits)
     * @since 5.5
     */
    public Pbkdf2PasswordEncoder(CharSequence secret, int saltLength, int iterations, int hashWidth) {
        this.secret = Utf8.encode(secret);
        this.saltGenerator = KeyGenerators.secureRandom(saltLength);
        this.iterations = iterations;
        this.hashWidth = hashWidth;
    }

    /**
     * Sets the algorithm to use. See <a href=
     * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecretKeyFactory">SecretKeyFactory
     * Algorithms</a>
     * @param secretKeyFactoryAlgorithm the algorithm to use (i.e.
     * {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1},
     * {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256},
     * {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512})
     * @since 5.0
     */
    public void setAlgorithm(SecretKeyFactoryAlgorithm secretKeyFactoryAlgorithm) {
        if (secretKeyFactoryAlgorithm == null) {
            throw new IllegalArgumentException("secretKeyFactoryAlgorithm cannot be null");
        }
        String algorithmName = secretKeyFactoryAlgorithm.name();
        try {
            SecretKeyFactory.getInstance(algorithmName);
            this.algorithm = algorithmName;
        }
        catch (NoSuchAlgorithmException ex) {
            throw new IllegalArgumentException("Invalid algorithm '" + algorithmName + "'.", ex);
        }
    }

    /**
     * Sets if the resulting hash should be encoded as Base64. The default is false which
     * means it will be encoded in Hex.
     * @param encodeHashAsBase64 true if encode as Base64, false if should use Hex
     * (default)
     */
    public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
        this.encodeHashAsBase64 = encodeHashAsBase64;
    }

    @Override
    public String encode(CharSequence rawPassword) {
        byte[] salt = this.saltGenerator.generateKey();
        byte[] encoded = encode(rawPassword, salt);
        return encode(encoded);
    }

    private String encode(byte[] bytes) {
        if (this.encodeHashAsBase64) {
            return Base64.getEncoder().encodeToString(bytes);
        }
        return String.valueOf(Hex.encode(bytes));
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        byte[] digested = decode(encodedPassword);
        byte[] salt = EncodingUtils.subArray(digested, 0, this.saltGenerator.getKeyLength());
        return MessageDigest.isEqual(digested, encode(rawPassword, salt));
    }

    private byte[] decode(String encodedBytes) {
        if (this.encodeHashAsBase64) {
            return Base64.getDecoder().decode(encodedBytes);
        }
        return Hex.decode(encodedBytes);
    }

    private byte[] encode(CharSequence rawPassword, byte[] salt) {
        try {
            PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(),
                    EncodingUtils.concatenate(salt, this.secret), this.iterations, this.hashWidth);
            SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
            return EncodingUtils.concatenate(salt, skf.generateSecret(spec).getEncoded());
        }
        catch (GeneralSecurityException ex) {
            throw new IllegalStateException("Could not create hash", ex);
        }
    }

    /**
     * The Algorithm used for creating the {@link SecretKeyFactory}
     *
     * @since 5.0
     */
    public enum SecretKeyFactoryAlgorithm {

        PBKDF2WithHmacSHA1, PBKDF2WithHmacSHA256, PBKDF2WithHmacSHA512

    }

}
  • In the matches method, It will get the password from user, then hash it again with the salt that extracted from the password in the database. Then it will compare with the recently hashed password from the user with the one that got from the database.

See Also#

References#