Add a little salt to that hash

kevindooley
Photo credit: kevindooley

Ideally we'd like to thwart even the semi-determined attacker. To improve our scheme, we now introduce the idea of salt.

The strategy behind salt is to make it much more painful for the attacker to recover hashed passwords. Instead of allowing ourselves to be attacked by a single well-known dictionary, we are going to force the attacker to create a new and unique dictionary for every single password he wants to try to recover. Can he still do it? Sure. But it's just a lot more work now. We've effectively eliminated merely semi-determined attackers from the pool of attackers. That's nothing to scoff at since their numbers are large.

Here's how we do it. Instead of hashing passwords, we concatenate a string—called a salt—to the plaintext password, and then hash the concatenated string. This effectively breaks the standard dictionary attack, because now all of the hashes in your password store are "new."

What happens, though, if we use a single, common salt across all users? While this is better than using no salt at all, it's still not hard to overcome if the attacker knows the salt. The attacker simply has to create a single new dictionary, this time concatenating the salt to each individual dictionary word before hashing. Then it's the same as before.

We want to make things harder for him. Instead of using a common salt across all users, we want the salt to be different for each user. That way, the attacker has to create a new dictionary for every single user he wants to attack. Again, he can do it, but it's much more time-consuming.

One good way to create a salt is to use some property of the user. It is better to choose something that won't change, such as a numeric primary key, than it is to choose something that might change, such as an e-mail address. If you use (say) e-mail addresses for salt, and a user changes his e-mail address, he won't be able to log in anymore because the authentication system won't be able to create the correct hash.

Usernames are a reasonable choice as they don't usually change, but a numeric primary key is even better, given the specific scheme Spring Security uses to concatenate the password with the salt. Spring Security uses braces to delimit the salt, and hence disallows braces inside the salt itself. For example, if my password/salt is college/willie, then Spring Security will hash the string college{willie}. You may well want to allow braces in the username (it's common for gamers to include such characters in their usernames). You can avoid the whole issue—including the added complexity of disallowing braces in usernames—by using the user's numeric primary key, if the user schema supports that.

So let's do just that.

Update applicationContext-security.xml to handle salt

All we need to do to applicationContext-security.xml is add a SaltSource bean and inject it into the DaoAuthenticationProvider. We do this in listing 3 below.

Listing 3. Adding a salt source to your configuration
<beans:bean id="passwordEncoder"
    class="org.springframework.security.providers.encoding.ShaPasswordEncoder" />
<beans:bean id="saltSource"
    class="org.springframework.security.providers.dao.salt.ReflectionSaltSource"
    p:userPropertyToUse="id" />
<beans:bean
    class="org.springframework.security.providers.dao.DaoAuthenticationProvider"
    p:userDetailsService-ref="userDetailsService"
    p:passwordEncoder-ref="passwordEncoder"
    p:saltSource-ref="saltSource">
    <custom-authentication-provider />
</beans:bean>

We mentioned above that it's possible to use a global salt, but that it's not as good as using a salt that varies from user to user. (For a more detailed explanation, please see my article Storing Passwords Security.) ReflectionSaltSource allows us to use a property of our user (specifically, a property of a UserDetails instance; we'll see that shortly) to provide a salt. As with the PasswordEncoder, we've defined the SaltSource explicitly as a bean so we can inject it into AccountServiceImpl.

Update AccountServiceImpl to salt the password

The first step is to add a setter for a SaltSource instance to AccountServiceImpl, and wire it up in your application context file.

Next, we'll once again update registerAccount() in listing 4.

Listing 4. Update your registration method to handle salt
public void registerAccount(Account account) {
	accountDao.save(account);
	UserDetailsAdapter userDetails = new UserDetailsAdapter(account); // 1
	String password = userDetails.getPassword();
	Object salt = saltSource.getSalt(userDetails); // 2
	account.setPassword(passwordEncoder.encodePassword(password, salt)); // 3
	accountDao.save(account);
}

As before, we're saving the account with a plaintext password first. I promised earlier to explain why I'm doing that, so here's the explanation. In many cases (such as when using Hibernate), entities aren't assigned IDs until after they're persisted. Since we're basing our salt on the ID, we need to save the account before generating the salt and hash.

Next we wrap the account with a class we wrote to adapt our Account class to the UserDetails interface; namely, UserDetailsAdapter 1 (we'll see it momentarily).

We're using the SaltSource to get the salt from our UserDetailsAdapter 2. As we saw in the configuration, this salt source uses reflection to grab the account ID and present it to the PasswordEncoder as salt 3.

Listing 5 shows our UserDetailsAdapter class, which again is just an example of a UserDetails implementation. I'm extending the org.springframework.security.userdetails.User class for convenience, but note that that's a UserDetails implementation.

Listing 5. A sample UserDetails implementation
package examples;

import java.util.Set;

import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.User;

public class UserDetailsAdapter extends User { // 1
	private final Long id;
	
	public UserDetailsAdapter(Account acct) {
		super(acct.getUsername(), acct.getPassword(), acct.isEnabled(),
				true, true, true, toAuthorities(acct.getAuthorityNames()));
		this.id = acct.getId();
	}
	
	private static GrantedAuthority[] toAuthorities(Set<String> authNames) {
		GrantedAuthority[] auths = new GrantedAuthority[authNames.size()];
		int i = 0;
		for (String authName : authNames) {
			auths[i++] = new GrantedAuthorityImpl(authName);
		}
		return auths;
	}
	
	public Long getId() {
		return id;
	}
}

Again, we're just extending User for convenience 1; the important thing is that our UserDetailsAdapter implements UserDetails, which allows DaoAuthenticationProvider to use it to perform authentication.

There you have it—salted and hashed passwords. This won't stop the most determined attackers from unmasking your passwords, but it will stop most others.