57

As of Spring Security 3.1.4.RELEASE, the old org.springframework.security.authentication.encoding.PasswordEncoder has been deprecated in favour of org.springframework.security.crypto.password.PasswordEncoder. As my application has not been released to the public yet, I decided to move to the new, not deprecated API.

Until now, I had a ReflectionSaltSource that automatically used the user's registration date as per-user salt for password.

String encodedPassword = passwordEncoder.encodePassword(rawPassword, saltSource.getSalt(user));

During login process, Spring also used my beans to appropriate verify if the user can or can not sign in. I can't achieve this in the new password encoder, because the default implementation of SHA-1 - StandardPasswordEncoder has only ability to add a global secret salt during the encoder creation.

Is there any reasonable method of how to set it up with the non-deprecated API?

fracz
  • 18,175
  • 16
  • 93
  • 143

4 Answers4

58

If you haven't actually registered any users with your existing format then you would be best to switch to using the BCrypt password encoder instead.

It's a lot less hassle, as you don't have to worry about salt at all - the details are completely encapsulated within the encoder. Using BCrypt is stronger than using a plain hash algorithm and it's also a standard which is compatible with applications using other languages.

There's really no reason to choose any of the other options for a new application.

Community
  • 1
  • 1
Shaun the Sheep
  • 21,010
  • 1
  • 63
  • 91
  • 5
    So what if you actually do have registered users? I assume that the Passwordencoder will be removed at some point. How to migrate? – Marc Nov 20 '13 at 16:58
  • 8
    Migrating accounts usually requires that you re-hash the password when a user successfully logs in. You would also have to support multiple algorithms for the migration period. Beyond that you could require a password reset or eventually lock or delete unused accounts which hadn't been used for an additional period. It depends on your system and requirements. I'm sure you can find discussions on it if you do some searching, since it's a common problem and becoming more relevant as the number of password db compromises increases. At least you aren't using [plaintext](http://ow.ly/qZQh0) :-). – Shaun the Sheep Nov 21 '13 at 13:50
  • 2
    I have an application that is live and is using the old PasswordEncoder with salt. Is there any examples out there of how to migrate to the new PasswordEncoder? – rubens May 01 '14 at 02:41
  • Code-wise it isn't too complicated - see [this answer](http://stackoverflow.com/a/17348888/241990) for example. It's more about the process involved, based on things like how often your users log in and how long you want the migration process to take, possibly requiring users to change passwords or locking accounts after a period of inactivity, that kind of thing. – Shaun the Sheep May 01 '14 at 11:40
18

Here is the implementation of BCrypt which is working for me.

in spring-security.xml

<authentication-manager >
    <authentication-provider ref="authProvider"></authentication-provider>  
    </authentication-manager>
<beans:bean id="authProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
  <beans:property name="userDetailsService" ref="userDetailsServiceImpl" />
  <beans:property name="passwordEncoder" ref="encoder" />
</beans:bean>
<!-- For hashing and salting user passwords -->
    <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

In java class

PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String hashedPassword = passwordEncoder.encode(yourpassword);

For more detailed example of spring security Click Here

Hope this will help.

Thanks

Community
  • 1
  • 1
Ravi Kant
  • 4,189
  • 2
  • 22
  • 23
5

I had a similar issue. I needed to keep the legacy encrypted passwords (Base64/SHA-1/Random salt Encoded) as users will not want to change their passwords or re-register. However I wanted to use the BCrypt encoder moving forward too.

My solution was to write a bespoke decoder that checks to see which encryption method was used first before matching (BCrypted ones start with $).

To get around the salt issue, I pass into the decoder a concatenated String of salt + encrypted password via my modified user object.

Decoder

@Component
public class LegacyEncoder implements PasswordEncoder {

    private static final String BCRYP_TYPE = "$";
    private static final PasswordEncoder BCRYPT = new BCryptPasswordEncoder();

    @Override
    public String encode(CharSequence rawPassword) {

    return BCRYPT.encode(rawPassword);
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {

    if (encodedPassword.startsWith(BCRYP_TYPE)) {
        return BCRYPT.matches(rawPassword, encodedPassword);
    }

    return sha1SaltMatch(rawPassword, encodedPassword);
    }

    @SneakyThrows
    private boolean sha1SaltMatch(CharSequence rawPassword, String encodedPassword) {

    String[] saltHash = encodedPassword.split(User.SPLIT_CHAR);

    // Legacy code from old system   
    byte[] b64salt = Base64.getDecoder().decode(saltHash[0].getBytes());
    byte[] validHash = Base64.getDecoder().decode(saltHash[1]);
    byte[] checkHash = Utility.getHash(5, rawPassword.toString(), b64salt);

    return Arrays.equals(checkHash, validHash);
    }

}

User Object

public class User implements UserDetails {

    public static final String SPLIT_CHAR = ":";

    @Id
    @Column(name = "user_id", nullable = false)
    private Integer userId;

    @Column(nullable = false, length = 60)
    private String password;

    @Column(nullable = true, length = 32)
    private String salt;

.
.

    @PostLoad
    private void init() {

    username = emailAddress; //To comply with UserDetails
    password = salt == null ? password : salt + SPLIT_CHAR + password;
    }        

You can also add a hook to re-encode the password in the new BCrypt format and replace it. Thus phasing out the old method.

Simon Jenkins
  • 630
  • 8
  • 11
3

Having just gone round the internet to read up on this and the options in Spring I'd second Luke's answer, use BCrypt (it's mentioned in the source code at Spring).

The best resource I found to explain why to hash/salt and why use BCrypt is a good choice is here: Salted Password Hashing - Doing it Right.

  • While using bcrypt is a good idea, the article you linked to has numerous horrible ideas, such as using fast hashing. See the reddit comments for details http://www.reddit.com/r/programming/comments/1yrnbo/salted_password_hashing_doing_it_right/cfnaqkl – Will Sargent Jan 16 '15 at 21:54