Configuring your app to secure passwords during registration

Saving secured passwords is somewhat more involved than authenticating against them. Part of the reason is that we'd like to use the same PasswordEncoder instance during registration that we used during login, partly just because it's "cleaner", and relatedly, because it allows us to avoid having to coordinate two separate PasswordEncoder instances should we decide to switch from SHA-1 to MD5, or add salt, or whatever. Basically it boils down to our being good adherents to the DRY principle.

Unfortunately, the namespace configuration for authentication-provider doesn't accept an externally-defined PasswordEncoder bean; you have to use the password-encoder namespace element inside of authentication-provider. So instead of using the namespace configuration, we're going to drop back to good, old-fashioned bean configuration.

Listing 1. Configuring a PasswordEncoder and DaoAuthenticationProvider
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">

    <-- 1 -->
    <beans:bean id="accountDao" class="example.HbnAccountDao" />
    
    <-- 2 -- >
    <beans:bean id="userDetailsService"
        class="example.UserDetailsServiceImpl"
        p:userDao="accountDao" />

    <-- 3 -- >
    <beans:bean id="passwordEncoder"
        class="org.springframework.security.providers.encoding.ShaPasswordEncoder" />

    <-- 4 -- >
    <beans:bean
        class="org.springframework.security.providers.dao.DaoAuthenticationProvider"
        p:userDetailsService-ref="userDetailsService"
        p:passwordEncoder-ref="passwordEncoder">
        <custom-authentication-provider />
    </beans:bean>
</beans:beans>
Authentication
Figure 1. Authentication in Spring Security 2

In the code above, we begin by creating a data access object for user accounts 1. Next we create an implementation of Spring's UserDetailsService interface, which we've called UserDetailsServiceImpl 2. UserDetailsService is essentially a service provider interface for the DaoAuthenticationProvider; it allows the latter to obtain UserDetails instances for authentication purposes. Third we create the PasswordEncoder itself 3; in this case we're using ShaPasswordEncoder, which hashes passwords using SHA (the default strength is SHA-1). Finally, we're creating a DaoAuthenticationProvider 4 and injecting it with the userDetailsService and passwordEncoder we created previously. We also include a custom-authentication-provider element to register our authentication provider with an AuthenticationManager hiding in the background.

For the curious, the AuthenticationManager in question is ProviderManager, a provider-based implementation of the AuthenticationManager interface, and it is automatically created by the http element unless you've already defined one explicitly. See figure 1 for a class diagram.

That takes care of configuration. Let's see what we need to do in order to save registrations with a hashed password.

Update AccountServiceImpl to hash the password

There are two updates you'll need to make to your AccountServiceImpl bean. The first one is that you'll need to provide a setter for a PasswordEncoder. And once you create that setter, go back to your application context configuration and make sure you're actually injecting a PasswordEncoder into the AccountServiceImpl bean.

The second update is to modify your registerAccount() method in AccountServiceImpl so that it hashes passwords:

Listing 2. Update your account service to hash passwords
public void registerAccount(Account account) {
    accountDao.save(account); // 1
    String encPassword =
        passwordEncoder.encodePassword(account.getPassword(), null); // 2
    account.setPassword(encPassword); // 3
    accountDao.save(account); // 4
}

First we save the account with the plaintext password 1. There's a reason for saving the account before actually hashing the password, but we'll have to wait until later in the recipe for the reason why. Then we hash the password 2, update it on the account 3, and save the account with the new password 4. The account now has a hashed password in the database.

Good job! You're now saving secured passwords into your database, and you're able to authenticate against them.

But I'm afraid that it's time for some bad news.

Meet the dictionary attack

Our new password storage scheme is off to a good start. It certainly prevents casual observers from accidentally seeing user passwords. But it does little to thwart the efforts of a semi-determined attacker. Let's talk about the dictionary attack.

To understand how it works, recall that hashing different strings generally results in different hashes. We can use that fact to create a big lookup table for a dictionary of potential passwords. The lookup table takes a hash and then returns the string that generated it.

As luck would have it, we don't even have to create our own lookup table. Helpful folks on the Internet have already done it for us. For instance, go to

http://gdataonline.com/seekhash.php

and enter the MD5 hash we presented earlier; namely,

3af00c6cad11f7ab5db4467b66ce503e

Voilà! You've unhashed a hash. This is called a dictionary attack.

If an attacker were to acquire a list of hashed passwords, he could make quick work of it using a dictionary of the sort just described. In some contexts that might be very bad.