Spring Security provides multiple PasswordEncoder
implementations with BCRYPT
as the recommended implementation.
However, the use-case of sharing an authentication database with an external
application, Dovecot, is examined in this article.
Dovecot uses an MD5-CRYPT
algorithm.
Complete javadoc is provided.
Reference
The actual encryption algorithm is captured in the Dovecot source file
password-scheme-md5crypt.c
.
Implementation
The implementation extends
DelegatingPasswordEncoder
to provide
decryption services for the other Spring Security supported password types.
Two inner classes, each subclasses of PasswordEncoder
,
provide MD5-CRYPT
and PLAIN
implementations.
@Service
public class MD5CryptPasswordEncoder extends DelegatingPasswordEncoder {
...
private static final String MD5_CRYPT = "MD5-CRYPT";
private static final HashMap<String,PasswordEncoder> MAP = new HashMap<>();
static {
MAP.put(MD5_CRYPT, MD5Crypt.INSTANCE);
MAP.put("CLEAR", NoCrypt.INSTANCE);
MAP.put("CLEARTEXT", NoCrypt.INSTANCE);
MAP.put("PLAIN", NoCrypt.INSTANCE);
MAP.put("PLAINTEXT", NoCrypt.INSTANCE);
}
...
public MD5CryptPasswordEncoder() {
super(MD5_CRYPT, MAP);
setDefaultPasswordEncoderForMatches(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
private static class NoCrypt implements PasswordEncoder {
...
public static final NoCrypt INSTANCE = new NoCrypt();
...
}
private static class MD5Crypt extends NoCrypt {
...
public static final MD5Crypt INSTANCE = new MD5Crypt();
...
}
}
The
MD5Crypt
inner class implementation is straightforward:
private static class MD5Crypt extends NoCrypt {
private static final String MD5 = "md5";
private static final String MAGIC = "$1$";
private static final int SALT_LENGTH = 8;
public static final MD5Crypt INSTANCE = new MD5Crypt();
public MD5Crypt() { }
@Override
public String encode(CharSequence raw) {
return encode(raw.toString(), salt(SALT_LENGTH));
}
private String encode(String raw, String salt) {
if (salt.length() > SALT_LENGTH) {
salt = salt.substring(0, SALT_LENGTH);
}
return (MAGIC + salt + "$" + encode(raw.getBytes(UTF_8), salt.getBytes(UTF_8)));
}
private String encode(byte[] password, byte[] salt) {
/*
* See source and password-scheme-md5crypt.c.
*/
}
@Override
public boolean matches(CharSequence raw, String encoded) {
String salt = null;
if (encoded.startsWith(MAGIC)) {
salt = encoded.substring(MAGIC.length()).split("[$]")[0];
} else {
throw new IllegalArgumentException("Invalid format");
}
return encoded.equals(encode(raw.toString(), salt));
}
}
The
NoCrypt
implementation provides the methods for calculating salt and itoa64
conversion.
Spring Boot Application Integration
The PasswordEncoder
may be integrated with the
following @Configuration
:
package some.application;
import ball.spring.MD5CryptPasswordEncoder;
import ...
@Configuration
public class PasswordEncoderConfiguration {
...
@Bean
public PasswordEncoder passwordEncoder() {
return new MD5CryptPasswordEncoder();
}
}
and must be integrated with a UserDetailsService
in
a WebSecurityConfigurer
:
package some.application;
import ...
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfigurerImpl extends WebSecurityConfigurerAdapter {
...
@Autowired private UserDetailsService userDetailsService;
@Autowired private PasswordEncoder passwordEncoder;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
...
}