The PasswordEncoder interface from Spring Security is used to perform one-way translation of the password to ensure that it is stored securely. Because PasswordEncoder provides one-way conversion, it is not intended for cases where password conversion must be two-way (i.e., storing credentials used for database authentication). Typically PasswordEncoder is used to store a password that needs to be compared with the user's password during authentication.

History of Saved Passwords

The standard password storage mechanism has evolved over the years . In the beginning, passwords were stored in plain text. The passwords were assumed to be secure because credentials were required to access the data store in which the passwords were stored. However, attackers have been able to find ways to obtain large "data dumps" of usernames and passwords using SQL Injection attacks. As more and more user credentials were leaked into the public domain, it became clear to security experts that it was necessary to strengthen the protection of user passwords.

Developers were advised to store passwords after passing them through a one-way hashing algorithm, for example SHA-256. When a user attempted to authenticate, the hashed password was compared to the hash of the password that user entered. This meant that the system only needed to store a one-way hash of the password. If hacked, only one-way password hashes would be exposed. Since the hashes were one-way, and guessing a password from a hash was computationally difficult, there was no point in spending the effort to guess every password in the system. To break into this new system, attackers decided to create lookup tables known as rainbow tables. Instead of guessing every password every time, they calculated the password once and stored it in a lookup table.

To reduce the effectiveness of rainbow tables, developers were encouraged to use salted passwords. Instead of exclusively using the password as input to the hashing function, random bytes (known as a salt) were generated for each user's password. The user's salt and password were run through a hashing function, resulting in a unique hash. The salt was stored along with the user's password in clear text. Then, if a user attempted to authenticate, the hashed password was compared with the hash of the stored salt and the password that user entered. The unique salt meant that rainbow tables were no longer effective because the hash was different for each salt and password combination.

In modern times, we understand that cryptographic hashes (such as the SHA-256 algorithm) are no longer secure . The reason is that with modern hardware it is possible to perform billions of hash calculations per second. This means that each password can be easily cracked individually.

Developers are now encouraged to use adaptive computationally irreversible functions to store the password. Validating passwords using adaptive, computationally irreversible functions is inherently resource-intensive (i.e., CPU, memory, etc.). An adaptive, computationally irreversible function allows you to configure a “labor-cost ratio” that can grow as hardware improves. It is recommended to adjust the "effort and cost ratio" so that it takes about 1 second to verify a password on your system. The trade-off is to make it more difficult for attackers to crack your password, but not put an undue burden on your own system due to huge resource costs. Spring Security attempts to provide an adequate starting point for the "Effort to Cost Ratio", but users are advised to tailor the "Effort to Cost Ratio" to their own systems as performance will vary greatly from system to system. Examples of adaptive computationally irreversible functions that should be used are bcrypt, PBKDF2, scrypt, and argon2.

Because adaptive computationally irreversible functions themselves require large computational resources, validating the username and password for each request significantly reduces application performance. Spring Security (or any other library) cannot in any way affect the speedup of password validation, since the appropriate level of security is achieved precisely due to the fact that validation is a resource-intensive task. Users are encouraged to exchange long-term credentials (i.e. username and password) for short-term credentials (i.e. session, OAuth token, etc.). Short-term credentials can be quickly validated without compromising security.

DelegatingPasswordEncoder

Prior to Spring Security 5.0, the default was NoOpPasswordEncoder, which required plain text passwords . Based on the Password History section, you would expect the default to be something like BCryptPasswordEncoder. However, this ignores three real problems:

  • There are many applications that use old password encodings that are difficult to migrate.

  • Best practice password storage will change again

  • Being a framework, Spring Security cannot make breaking changes frequently

Instead, Spring Security introduces DelegatingPasswordEncoder, which solves all these problems:

  • Providing password encodings using current password storage best practices

  • Allowing validation of passwords in modern and legacy formats

  • Allowing future encoding updates

Create an instance of DelegatingPasswordEncoder you can easily use PasswordEncoderFactories.

Creating a default DelegatingPasswordEncoder
Java

PasswordEncoder passwordEncoder =
    PasswordEncoderFactories.createDelegatingPasswordEncoder();
Kotlin
 val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()

In addition, you can create your own custom instance. For example:

Creating a custom DelegatingPasswordEncoder
Java

String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder =
    new DelegatingPasswordEncoder(idForEncode, encoders);
Kotlin

val idForEncode = "bcrypt"
val encoders: MutableMap<String, PasswordEncoder> = mutableMapOf()
encoders[idForEncode] = BCryptPasswordEncoder()
encoders["noop"] = NoOpPasswordEncoder.getInstance()
encoders["pbkdf2"] = Pbkdf2PasswordEncoder()
encoders["scrypt"] = SCryptPasswordEncoder()
encoders["sha256"] = StandardPasswordEncoder()
val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)

Password storage format

General format password is as follows:

DelegatingPasswordEncoder storage format
{id}encodedPassword

For example, id – is the identifier used to find a suitable PasswordEncoder to use, and encodedPassword is the original encoded password for the selected PasswordEncoder. id must be at the beginning of the password, starting with { and ending with }. If id cannot be found, id will be null. For example, below is a possible list of passwords encoded using different ids. All initial passwords are "password".

Example of passwords encoded using DelegatingPasswordEncoder

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 
{noop}password 
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv 7BeL1QxwRpY5Pc= 
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cffafaf8410849f27605abcbc0
  1. The PasswordEncoder identifier for the first password will be bcrypt, and the encrypted password itself will be represented as $2a $10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG. If there is a match, it will be passed to BCryptPasswordEncoder
  2. The PasswordEncoder identifier for the second password will be noop, and the encrypted password itself will be presented in the form password. If there is a match, it will be passed to NoOpPasswordEncoder
  3. The PasswordEncoder identifier for the third password will be pbkdf2, and the encoded password itself will be presented in the form 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc. If there is a match, it will be passed to Pbkdf2PasswordEncoder
  4. The PasswordEncoder identifier for the fourth password will be scrypt, and the encrypted password itself will be presented in the form $e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwR pY5Pc=. If there is a match, it will be passed to SCryptPasswordEncoder
  5. The PasswordEncoder identifier for the resulting password will be sha256, and the encrypted password itself will be presented in the form 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cffafaf8410849f27605abcbc0. If there is a match, it will be passed to StandardPasswordEncoder

Some users may be concerned that the storage format is open for a potential hacker. There is no need to worry about this, since storing the password does not depend on whether the algorithm is private. In addition, an attacker can easily calculate most formats without a prefix. For example, BCrypt passwords often begin with $2a$.

Password encoding

Passed to the idForEncode constructor defines which PasswordEncoder will be used to encode passwords. In the case of the DelegatingPasswordEncoder we created above, this means that the encoding result of password will be passed to BCryptPasswordEncoder and prefixed with {bcrypt}. The final result will look like this:

Encoding example using DelegatingPasswordEncoder
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

Password guessing

Matching is done based on {id} and mapping id to PasswordEncoder, specified in the constructor. By default, the result of calling matches(CharSequence, String) with a password and an id that was not mapped (including a null id) will result in an IllegalArgumentException. This operating logic can be configured using DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder).

Using id, we can match any password encoding, but encode passwords using the most modern password encoding. This is important because, unlike encryption, password hashes are organized in such a way that it is extremely difficult to recover plain text. Since there is no way to recover plain text, this will make it difficult to transfer passwords. While users can easily port NoOpPasswordEncoder, we decided to add it by default to make it easier to get started.

Getting Started

If you're preparing a demo -version or sample, it will be a little problematic to spend time hashing your users' passwords. There are supporting mechanisms that make this task easier, but they are still not intended for production deployment.

withDefaultPasswordEncoder Example
Java

User user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
Kotlin

val user = User.withDefaultPasswordEncoder()
    .username("user")
    .password("password")
    .roles("user")
    .build()
println(user.password)
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

If you are creating multiple users, you can also reuse the constructor.

withDefaultPasswordEncoder, which reuses the constructor
Java

UserBuilder users = User.withDefaultPasswordEncoder();
User user = users
  .username("user")
  .password("password")
  .roles("USER")
  .build();
User admin = users
  .username("admin")
  .password("password")
  .roles("USER","ADMIN")
  .build();
Kotlin

val users = User.withDefaultPasswordEncoder()
val user = users
    .username("user")
    .password("password")
    .roles("USER")
    .build()
val admin = users
    .username("admin")
    .password("password")
    .roles("USER", "ADMIN")
    .build()

This allows the stored password to be hashed, but the passwords are still exposed in memory and in the compiled source code. Therefore, this method is still not considered safe in a production environment. In a production deployment, you should hash passwords externally.

Encoding using the Spring Boot CLI

The easiest way to properly encode a password is to use Spring Boot CLI.

For example, the following command encodes password password for use with DelegatingPasswordEncoder:

Example of using the encodepassword command via the Spring Boot CLI

spring encodepassword password
{bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6

Troubleshooting

The following error occurs if one of the stored passwords does not have an ID.

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
    at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)

The easiest way to fix the error is to switch to an explicit PasswordEncoder that encodes your passwords. The easiest way to resolve this issue is to figure out how your passwords are currently stored and explicitly specify the correct PasswordEncoder.

If you are upgrading from Spring Security 4.2.x, then You can return to the previous logic by opening the NoOpPasswordEncoder bean.

In addition, you can provide all passwords with the correct identifier and continue to use DelegatingPasswordEncoder. For example, if you are using BCrypt, you could transfer your password from something like:

$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

in

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

For a complete list of mappings, see the Javadoc at PasswordEncoderFactories.

BCryptPasswordEncoder

The BCryptPasswordEncoder implementation makes extensive use of supported bcrypt algorithm for hashing passwords. To make it more resistant to hacking, bcrypt is deliberately slowed down. Like other adaptive computationally irreversible functions, it should be finely tuned so that password verification on your system takes about 1 second. The default implementation of BCryptPasswordEncoder uses the security parameter with a value of 10, as specified in the Javadoc to BCryptPasswordEncoder. It is recommended that you configure and test the strength setting on your system so that password verification takes approximately 1 second.

BCryptPasswordEncoder
Java

// Create an encoder with the reliability parameter 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Kotlin

// Create an encoder with reliability parameter 16
val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))

Argon2PasswordEncoder

The implementation of Argon2PasswordEncoder uses the algorithm Argon2 for password hashing. Argon2 is the winner of the Password Hashing Competition. To combat password cracking on user hardware, Argon2 was deliberately slowed down and requires a large amount of memory. Like other adaptive computationally irreversible functions, it should be finely tuned so that password verification on your system takes about 1 second. The current implementation of Argon2PasswordEncoder requires BouncyCastle.

Argon2PasswordEncoder
Java

// Create an encoder with all default settings
Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
Kotlin

// Create an encoder with all default settings
val encoder = Argon2PasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))

Pbkdf2PasswordEncoder

The implementation of Pbkdf2PasswordEncoder uses the algorithm PBKDF2 for password hashing. To combat password cracking, PBKDF2 has been deliberately slowed down. Like other adaptive computationally irreversible functions, it should be finely tuned so that password verification on your system takes about 1 second. This algorithm is an excellent choice if FIPS certification is required.

Pbkdf2PasswordEncoder
Java

// Create an encoder with all default settings
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Kotlin

// Create an encoder with all default settings
val encoder = Pbkdf2PasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))

SCryptPasswordEncoder

The SCryptPasswordEncoder implementation uses the scrypt for hashing passwords. To combat password cracking on user equipment, this algorithm has been deliberately slowed down and requires a large amount of memory. Like other adaptive computationally irreversible functions, it should be finely tuned so that it takes about 1 second to verify a password on your system.

SCryptPasswordEncoder
Java

// Create an encoder with all default settings
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Kotlin

// Create an encoder with all default settings
val encoder = SCryptPasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))

Other PasswordEncoders

There are a significant number of other PasswordEncoder implementations that are intended solely for backward compatibility. All of them are outdated, which no longer allows them to be considered safe. However, there are no plans to remove them because migrating existing legacy systems is difficult.

Configuration for storing passwords

Spring Security uses DelegatingPasswordEncoder by default. However, this can be configured by opening PasswordEncoder as a Spring bean.

If you are upgrading from Spring Security 4.2.x, you can revert to the previous logic by opening the NoOpPasswordEncoder bean.

Reverting to NoOpPasswordEncoder is not considered safe. Instead, you should switch to using DelegatingPasswordEncoder to ensure secure password encoding.

NoOpPasswordEncoder
Java

@Bean
public static PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}
XML

<b:bean id="passwordEncoder"
    class="org.springframework.security .crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
Kotlin

@Bean
fun passwordEncoder(): PasswordEncoder {
    return NoOpPasswordEncoder.getInstance();
}

The XML configuration requires that the bean name NoOpPasswordEncoder was passwordEncoder.

Changing the password configuration

Most applications that allow the user to set a password also require an update feature this password.

Widely known URL for changing passwords denotes the mechanism by which password managers can discover the password update endpoint for a specific application.

You can configure Spring Security to pass this discovery endpoint. For example, if the password change endpoint in your application is - /change-password, then you can configure Spring Security as follows:

Default change password endpoint
Java

http
    .passwordManagement(Customizer.withDefaults())
XML
<sec:password-management/>
Kotlin

http {
    passwordManagement { }
}

Then when the password manager goes to /.well-known/change-password, Spring Security will forward your endpoint to /change-password.

Or if your endpoint is not /change-password, then you can set it as follows:

Change Password Endpoint
Java

http
    .passwordManagement((management) -> management
        .changePasswordPage("/update-password")
    )
XML
<sec:password-management change-password-page="/update-password"/>
Kotlin

http {
    passwordManagement {
        changePasswordPage = "/update-password"
    }
}

With the above configuration, when the password manager goes to /.well- known/change-password, Spring Security will redirect to /update-password.