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.
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.
<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
.
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.
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.
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.