HMAC then BCrypt Passwords for a little extra security
Published on 3/13/2015
HMAC then BCrypt (also known as Peppering) of user passwords can help in the following scenarios:
Scenario 1: Hacker steals your user database, but does not compromise your web server
Scenario 2: Hacker can run SQL Injection on your web server, but can otherwise not gain access to the web server process
Scenario 3: You can keep your HMAC key in a Hardware Security Module
How it works:
-
“Sign” user’s password using HMAC and a key known only to the web server (do NOT store this key in the database)
-
BCrypt the signed user’s password
This also has the advantage of allowing longer passwords (BCrypt has a limit of around 70 chars).
See https://blog.mozilla.org/webdev/2012/06/08/lets-talk-about-password-storage/
A HMAC then BCrypt password encoder for Spring Security:
public class PepperingPasswordEncoder implements PasswordEncoder {
private final PasswordEncoder actualEncoder;
private final Mac mac;
public PepperingPasswordEncoder(final PasswordEncoder actualEncoder, final String key) throws InvalidKeyException, NoSuchAlgorithmException {
this(actualEncoder, key, "HMacSha1");
}
public PepperingPasswordEncoder(final PasswordEncoder actualEncoder, final String key, final String algorithm) throws InvalidKeyException, NoSuchAlgorithmException {
this.actualEncoder = actualEncoder;
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
mac = Mac.getInstance(algorithm);
mac.init(keySpec);
}
@Override
public String encode(final CharSequence charSequence) {
return actualEncoder.encode(hmac(charSequence));
}
public String hmac(final CharSequence value) {
return hmac(value.toString());
}
public String hmac(final String value) {
return new String(Base64.encode(mac.doFinal(value.getBytes())));
}
@Override
public boolean matches(final CharSequence rawPassword, final String encodedPassword) {
return actualEncoder.matches(hmac(rawPassword), encodedPassword);
}
}
In applicationContext-security.xml:
<beans:bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bCryptPasswordEncoder" />
<beans:bean class="com.databasepatterns.spring.security.PepperingPasswordEncoder" id="passwordEncoder">
<beans:constructor-arg ref="bCryptPasswordEncoder"/>
<beans:constructor-arg value="mykey"/>
</beans:bean>
<authentication-manager>
<authentication-provider>
<password-encoder ref="passwordEncoder"/>
...
</authentication-provider>
</authentication-manager>
... views