15

Today I upgraded the spring security version of the application I'm working on from 3.1.3 to 3.1.4, and I noticed a deprecation warning on the org.springframework.security.authentication.encoding.ShaPasswordEncoder class.

So I switched to the new org.springframework.security.crypto.password.StandardPasswordEncoder implementation.

I had it working and I'm able to register a new user and login in my application, but, as I feared, I'm not able to login using passwords generated with the previous ShaPasswordEncoder and my custom salt.

Since I have a database with many users already registered, what should I do to switch implementation without invalidating the old encoded passwords? Is it even possible?

See also: How to use new PasswordEncoder from Spring Security

Community
  • 1
  • 1
Dario Zamuner
  • 991
  • 2
  • 14
  • 28

4 Answers4

19

If you want to switch to a more secure password encoding mechanism, then I would recommend you use BCrypt. I would use something like this to migrate your users:

// Implement the old PasswordEncoder interface
public class MigrateUsersPasswordEncoder implements PasswordEncoder {
    @Autowired
    ShaPasswordEncoder legacyEncoder;
    @Autowired
    JdbcTemplate template;

    BCryptPasswordEncoder bcryptEncoder = new BCryptPasswordEncoder();

    @Override
    public String encodePassword(String rawPass, Object salt) {
        return bcryptEncoder.encode(rawPass);
    }

    @Override
    public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
        if (legacyEncoder.isPasswordValid(encPass, rawPass, salt)) {
            template.update("update users set password = ? where password = ?", bcryptEncoder.encode(rawPass), encPass);
            return true;
        }
        return bcryptEncoder.matches(rawPass, encPass);
    }
}

You can check what proportion of users have been migrated by the format of the password field. BCrypt strings have a distinctive syntax beginning with a $ sign.

One of the other answers points out that this code could accidentally update multiple passwords at the same time. The question stated that a custom salt was being used, so the chance of collisions is negligible if the salt is randomly chosen, but this might not always be the case. If two passwords were updated, what would the problem be? It would then be possible to detect that accounts have the same password from the bcrypt hashes. That's the case anyway since it requires that the SHA hashes were the same for the update to occur. If you think it might be a problem (e.g. because of poor salt choice or even the use of unsalted hashes) it would be trivial to modify the SQL to detect this and perform multiple updates with separate BCrypt hash values.

Community
  • 1
  • 1
Shaun the Sheep
  • 22,353
  • 1
  • 72
  • 100
  • Thanks for the code. This is more or less what Spiff suggested. My goal here instead is to be able to upgrade to future releases of Spring Security without having two implementations at the same time, plus this solution will work only if every user will sign in. I hope it will be introduced an implementation compatible with the old ShaPasswordEncoder. – Dario Zamuner Jun 30 '13 at 14:45
  • If users never sign in then it might be worth deciding whether the accounts are worth maintaining (after a suitable review period). If they are inactive you could temporarily disable them and mail the users asking them to reset their password. The use of the old password encoders is discouraged because they are relatively insecure, error-prone and incompatible with other systems. The classes are deprecated to discourage their use in new apps but there's nothing to stop you using them if you really don't want to use a more secure option. – Shaun the Sheep Jun 30 '13 at 16:18
5

I tried to add a comment to the accepted answer, but alas, I don't have enough cred yet. :(

I believe the accepted answer's code snippet is potentially dangerous where it updates the password in the database. If the ShaPasswordEncoder produces the same results when encrypting (which is why the assumption is being made that the old password can be found, and I verified this is definitely true at least with a null salt on the ShaPasswordEncoder), you still cannot guarantee that the password is unique amongst all users. You could, by chance, share the same password as another user on the system, and that SQL code would end up changing all users that happen to have your password.

I think the safest strategy is to not update the user's password, and instead provide a migration strategy that plans for the eventual removal of the ShaPasswordEncoder.

  • Use the provided example code.
  • Remove the code that updates the database.
  • Add a feature like, "Forgot your password" or "Generate a new password" to handle the eventual case of users not having created a new password when the ShaPasswordEncoder is removed. Like either when you upgrade to Spring Security that has it removed, or choose to remove it yourself.
  • Update your documentation or make it clear that in the next major release version of the software, that users will have had to re-save their passwords or will have to use the before-mentioned password reset feature.
  • Give the user the grace period of a major version release cycle to transition (they probably won't do it and just get caught in the reset password).
tastle
  • 83
  • 1
  • 4
  • This is a good point. However, I don't think that it's really dangerous compared with the existing situation or that the updates should be postponed. It's also trivial to workaround. I updated my answer to add some extra thoughts. – Shaun the Sheep Nov 13 '15 at 17:07
2

That's an excellent question and am looking forward to reading some answers.

AFAIK it's impossible to do in a single mass update: you just can't retrieve the original string from a hash. You would have to check during a login attempt if the submitted password matches either strategy and convert it to the new strategy if necessary, but that means you would have to live with both encoding strategies until all users have logged in and hence all passwords have been converted. Not very convenient nor necessarily intuitive for new developers coming on board.

Spiff
  • 2,266
  • 23
  • 36
0

I move to implement class PasswordEncoder org.springframework.security.crypto.password.PasswordEncoder

This solution help me to fixed deprecated PasswordEncoder.

Code:

public class LegacySupportPasswordEncoder implements PasswordEncoder {
    private final BCryptPasswordEncoder bcryptEncoder;

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public LegacySupportPasswordEncoder(@Qualifier("passwordEncoder") final BCryptPasswordEncoder bcryptEncoder,
                                        final RoutingDataSource dataSource) {

        this.bcryptEncoder = bcryptEncoder;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public String encode(CharSequence rawPassword) {
        return this.bcryptEncoder.encode(rawPassword);
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        //Your code validate credential in database here
        return this.bcryptEncoder.matches(rawPassword, encodedPassword);
    }
}
SivLay Yi
  • 1
  • 2