Learn how PasswordEncoder interface, one of the core interfaces in Spring security, helps to manage passwords in an application. Also, learn the PasswordEncoder contract, inbuilt implementations and how to customize its functionality.

1. PasswordEncoder in Spring Security Architecture

The below-given picture shows the main actors in the Spring Security architecture and the relationships among them. This architecture is the core concept of implementing authentication with Spring Security.

As shown in the image, AuthenticationProvider uses PasswordEncoder along with UserDetailsService to authenticate the user in the application. The primary purpose of PasswordEncoder is to match the user-supplied password with the password stored in UserDetails object stored in the SecurityContext.

It is important to understand that PasswordEncoder perform a one-way transformation of a password to allow it to be stored securely. We cannot decode an encoded previously encoded password using the PasswordEncoder interface.

The PasswordEncoder interface provides the following important methods:

  • String encode(rawPassword) : encodes the rawPassword supplied by the user.
  • boolean matches(rawPassword, encodedPassword) : verifies that the encodedPassword obtained from storage matches the user submitted rawPassword after it too is encoded. The stored encodedPassword itself is never decoded.
java.lang.String  encode(java.lang.CharSequence rawPassword)
boolean matches(java.lang.CharSequence rawPassword, java.lang.String encodedPassword)

2. Inbuilt Implementations of PasswordEncoder

We can find the complete list of supported encoders in spring security in the PasswordEncoderFactories class. If one of these matches our requirement, we don’t need to rewrite it.

  • noopNoOpPasswordEncoder : doesn’t encode the password but keeps it in cleartext. It can be used for unit testing only.
  • sha256StandardPasswordEncoder uses SHA-256 to hash the password that is not strong enough anymore. Its deprecated now.
  • scryptSCryptPasswordEncoder : uses a scrypt hashing function to encode the password.
  • bcryptBCryptPasswordEncoder : uses a bcrypt hashing function to encode the password.
  • ldapLdapShaPasswordEncoder : legacy purposes only and is not considered secure. It supports LDAP SHA and SSHA (salted-SHA) encodings.
  • pbkdf2Pbkdf2PasswordEncoder : uses PBKDF2 invoked on the concatenated bytes of the salt, secret and password. 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.
  • argon2Argon2PasswordEncoder : uses the Argon2 hashing function. It can accept the length of the salt, length of generated hash, a CPU cost parameter, a memory cost parameter and a parallelization parameter.
  • MD4Md4PasswordEncoder : is deprecated for not being secured.
  • MD5, SHA-1, SHA-256MessageDigestPasswordEncoder : is deprecated because digest-based password encoding is not considered secure.

Out of above given classes, using BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or SCryptPasswordEncoder is recommended. The best way to implement password matching is through DelegatingPasswordEncoder which supports password upgrades.

@Bean
public PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder(11);
}

3. Supporting Multiple Schemes using DelegatingPasswordEncoder

3.1. Storing Passwords of Different Schemes

We must realize that security parameters change over time. An algorithm, that is considered safe today, will not be safe tomorrow. So an application may have user credentials across the timespan and support the latest password hashing techniques over these times.

In this situation, DelegatingPasswordEncoder helps in production applications when the encoding algorithms are changed over time. The DelegatingPasswordEncoder is an implementation of the PasswordEncoder interface that, instead of implementing its encoding algorithm, delegates to another instance of an implementation of the same contract.

To identify the hashing scheme used for a password, a prefix naming the algorithm is used with the encoded hash. The DelegatingPasswordEncoder delegates to the correct implementation of the PasswordEncoder based on the prefix of the password.

For example, a plain text 'password' string can be stored in the following ways. In runtime, the NoOpPasswordEncoder is assigned to the key noop, while the BCryptPasswordEncoder implementation is assigned the key bcrypt.

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

3.2. Configuring DelegatingPasswordEncoder

We start by creating a collection of instances of our desired schemes and PasswordEncoder implementations, and we put these together in a DelegatingPasswordEncoder as follows:

@Bean
public PasswordEncoder passwordEncoder() {
  Map<String, PasswordEncoder> encoders = new HashMap<>();

  encoders.put("noop", NoOpPasswordEncoder.getInstance());
  encoders.put("bcrypt", new BCryptPasswordEncoder());
  encoders.put("scrypt", new SCryptPasswordEncoder());

  return new DelegatingPasswordEncoder("bcrypt", encoders);
}

The above bean definition creates an instance of DelegatingPasswordEncoder containing references to a NoOpPasswordEncoder, a BCryptPasswordEncoder, and an SCryptPasswordEncoder, and delegates the default to the BCryptPasswordEncoder implementation.

If there is no prefix in the stored password, the DelegatingPasswordEncoder uses the default encoder. Also, note that the curly braces are mandatory in the prefix.

To create the DelegatingPasswordEncoder that has a map to all the standard provided implementations, use PasswordEncoderFactories.

@Bean
public PasswordEncoder passwordEncoder() {
  return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

4. Creating Custom PasswordEncoder

If for some reason, we need to supply our own implementation of PasswordEncoder then we can easily do so by implementing the PasswordEncoder interface and overriding its two methods encode() and matches().

For example, Spring Security does not provide any scheme for hashing the passwords with SHA-512 algorithm. We can create our own implementation as follows. Feel free to modify the code as per your needs.

public class Sha512PasswordEncoder implements PasswordEncoder {

  @Override
  public String encode(CharSequence rawPassword) {
    return hashWithSHA512(rawPassword.toString());
  }

  @Override
  public boolean matches(
      CharSequence rawPassword, String encodedPassword) {
    String hashedPassword = encode(rawPassword);
    return encodedPassword.equals(hashedPassword);
  }

  private String hashWithSHA512(String input) {
    StringBuilder result = new StringBuilder();
    try {
      MessageDigest md = MessageDigest.getInstance("SHA-512");
      md.update("salt".getBytes());

      byte [] digested = md.digest(input.getBytes());
      for (int i = 0; i < digested.length; i++) {
        result.append(Integer.toHexString(0xFF & digested[i]));
      }
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("Bad algorithm");
    }
    return result.toString();
  }
}

We can now register it with DelegatingPasswordEncoder as follows:

@Bean
public PasswordEncoder passwordEncoder() {
  Map<String, PasswordEncoder> encoders = new HashMap<>();

  //other encoders

  encoders.put("sha512", new Sha512PasswordEncoder());

  return new DelegatingPasswordEncoder("bcrypt", encoders);
}

5. Conclusion

In this tutorial, we learned about the basic architecture of spring security-based authentication. We learned about the contract that spring security expects from PasswordEncoder interface.

We learned about the spring supplied hashing schemes and their implementation classes. We also learned to support multiple hashing strategies using the DelegatingPasswordEncoder, including creating and registering our own custom hashing scheme.

Happy Learning !!

Sourcecode on Github

Leave a Reply

0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions, and frequently asked interview questions.