4

We're in the process of converting our site from an old PHP framework to Rails, and would really like for users to continue being able to login with their old password. On the old site, we're using password_hash and password_verify to hash and verify the passwords. However, on Rails I can't seem to get it to verify the old password.

Here is what we have in PHP:

Hash:

password_hash($user['salt'] . $password . $user['salt'], PASSWORD_DEFAULT);

Verify:

password_verify($user['salt'] . $password . $user['salt'], $user['password'])

On the new Rails framework we're using Devise and have built a custom migration script to move everything over and identify the correct password hashing method based on a password_version stored in the db, and this is what I'm using inside my User model:

def valid_password?(password)
  if password_version == 'legacy'
    hash = BCrypt::Password.new(encrypted_password)
    hash_str = password_salt+password+password_salt

    return hash.is_password? hash_str
  end

  super(password)
end

Any ideas would be greatly appreciated

jakeb
  • 43
  • 3
  • password_verify isn't very complicated. pass in the new pw and original pw hash. yank the crypt method + salt out of the hash, use those to re-hash the new pw, then compare resulting hash values. – Marc B Aug 16 '16 at 20:14
  • @Script47 Yep, it's supposed to be there. – jakeb Aug 16 '16 at 20:15
  • @MarcB Yep, that's essentially what I'm trying to do. It *should* be using bcrypt from what I understand of it, so the rails portion should work, I would have thought – jakeb Aug 16 '16 at 20:15
  • `password_hash` is supposed to work without needing a salt. Adding one just messes things up. Why are you doing that? – tadman Aug 16 '16 at 20:19
  • 1
    [Please don't add your own salt to password_hash](http://stackoverflow.com/questions/14992367/using-php-5-5s-password-hash-and-verify-function-am-i-doing-it-right). Let it add its own. You will get the salt stored in the hash – Machavity Aug 16 '16 at 20:23
  • @Machavity Totally agreed, but it looks like the damage is done here. – tadman Aug 16 '16 at 20:23
  • @Machavity Yes, I'm aware. I didn't make the current site, just trying to convert it, that provides nothing towards my solution. – jakeb Aug 16 '16 at 20:33

1 Answers1

5

The format of a PHP password_hash password looks roughly like this:

$2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a

The default Ruby Bcrypt method produces passwords of the form:

$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG

For a clean solution here you can always differentiate between the two by the $2y or $2a prefix. There's no need for a format column when it's already baked into the format.

For example:

case (encrypted_password[0,3])
when '$2y'
  # Legacy PHP password
  BCrypt::Password.new(encrypted_password.sub(/\A\$2y/, '$2a')).is_password?(salt + password + salt)
when '$2a'
  # Ruby BCrypt password
  BCrypt::Password.new(encrypted_password).is_password?(password) 
else
  # Unexpected type?
end

What you'll want to do on a successful verification of password is re-write the password to the database using Ruby's method to gradually replace all the old PHP formatted ones.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • Yep, as I said in the question, I already know which passwords are which, it's just a matter of (in ruby) verifying the password with the PHP hash. I'm wanting to keep the password_version as there is an even older legacy password version being used as the current site didn't replace these with the new format, it just allowed them to continue working. I've gotten that one working fine as it was just a hash with a salt – jakeb Aug 16 '16 at 20:29
  • I'm saying it *should* replace them with the new version. Do you have some test data here that can be used to illustrate the desired output? – tadman Aug 16 '16 at 20:32
  • It's going to replace them, the old version didn't so I'm converting two older password hashing methods to the new one using BCrypt – jakeb Aug 16 '16 at 20:33
  • @jakeb I've updated the answer with some example code. Your `valid_password?` method will need to take both the supplied password and your legacy `salt` value. – tadman Aug 16 '16 at 20:40
  • Yep! That did it, thanks. For some reason you mentioning that PHP's starting with $2y didn't even make me think to replace it. – jakeb Aug 16 '16 at 21:01
  • 1
    @jakeb I've had to do something similar to this when dealing with PHP passwords in Python and Node.js before. Glad it worked! – tadman Aug 16 '16 at 21:56