Spring Security delegating password encoder

The Spring Security PasswordEncoder interface exists to make it easy to safely encode passwords for storage in a database. Hashing the password using a secure algorithm with a heavy work factor will slow down an attacker even if they compromise the password database.

Since the interface was introduced, security recommendations have changed as CPUs / GPUs become more powerful and as vulnerabilities are discovered in legacy algorithms. The original StandardPasswordEncoder is now deprecated as the SHA-256 algorithm is considered insecure. Spring offers more secure implementations based on bcrypt, PBKDF2 and Argon2.

However, Spring no longer ties you to a single algorithm. The new DelegatingPasswordEncoder provides support for multiple PasswordEncoder implementations, many of which are available in Spring Boot applications with default configuration. This makes it possible to select an algorithm at run time and to have a database containing password hashes with different algorithms.

Storing hashes

The DelegatingPasswordEncoder uses a prefix on the password hash to identify the algorithm. For example, the following hashes all map to ‘password’:

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

The first is hashed with bcrypt (BCryptPasswordEncoder) and the second with SHA-256 (StandardPasswordEncoder). The third uses the NoOpPasswordEncoder which stores passwords in plain text. This is useful for testing but offers no security. In each case, the hash string is prefixed with an identifier for the algorithm.

Default mappings

You can build an instance of DelegatingPasswordEncoder by using its constructor:

String idForEncode = "bcrypt";
 Map<String,PasswordEncoder> 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);

Pass in all implementations you want to support.

Alternatively, use the static PasswordEncoderFactories.createDelegatingPasswordEncoder() to create a DelegatingPasswordEncoder with default implementations. At time of writing (spring-security version 5.6.2), these are:

  • bcrypt – BCryptPasswordEncoder
  • ldap – org.springframework.security.crypto.password.LdapShaPasswordEncoder
  • MD4 – org.springframework.security.crypto.password.Md4PasswordEncoder
  • MD5 – new MessageDigestPasswordEncoder(“MD5”)
  • noop – org.springframework.security.crypto.password.NoOpPasswordEncoder
  • pbkdf2 – Pbkdf2PasswordEncoder
  • scrypt – SCryptPasswordEncoder
  • SHA-1 – new MessageDigestPasswordEncoder(“SHA-1”)
  • SHA-256 – new MessageDigestPasswordEncoder(“SHA-256”)
  • sha256 – org.springframework.security.crypto.password.StandardPasswordEncoder
  • argon2 – Argon2PasswordEncoder

If you use Spring Boot with the spring-boot-starter-security, these are the implementations you’ll get by default.

Upgrading legacy password hashes

A great advantage of this mechanism is that it makes it easier to upgrade password hashes. The DelegatingPasswordEncoder will support a database that contains both legacy (SHA-256) and modern (Argon2 / bcrypt) hashes. The OWASP Password Storage Cheat Sheet provides guidance on how to manage the transition from one hash algorithm to another.

Which algorithm should I use?

One problem with this new flexibility is that it’s not clear which algorithm you should choose. My advice is to check the OWASP Password Storage Cheat Sheet for recommendations. At time of writing, it recommends Argon2 or bcrypt for most applications.

Note that if you want to use the Argon2PasswordEncoder, you’ll need to include the Bouncy Castle crypto provider. In Maven, add this dependency:

		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk15on</artifactId>
			<version>1.70</version>
		</dependency>

Leave a Reply

Your email address will not be published.