1
votes

I'm converting a Python project to Rails, both of them uses the same database and I'm trying to use Ruby to verify the passwords that are saved using Python library called passlib.hash.sha256_crypt. I want to use Ruby to verify the user input whether it is the valid password compared to the saved password using any Ruby library but couldn't find any suitable gem for that.

Example password saved in SHA256 using Python Passlib library: $5$rounds=110000$4QB3mR7z8SVQwVDt$1jT2mdn9XHRD.O8gsCJXoUTDWJSLrY09uD8KZp78ou6 which is password

The gems that I've found are:

  1. Ruby Digest library : not generating in the format of $5$.....
  2. Unix Crypt : generate digests in relevant format but unable to verify the result
  3. Gibberish : only advance in AES mode decryption, not generating in the format of $5$.....

Unix Crypt is the most relevant solution but the generated digest for the string "password" is different from the Python generated. I tried to remove the rounds from the saved digest and verify it but it failed, I think it's because they are using different rounds to generate the digest.

[7] pry(main)> UnixCrypt::SHA256.build("password")
=> "$5$qbpSpYyYoTCr0q4G$/jyjVRgg3Y4vQj39f9OWwUQhKOh70bTB50jH7WfsZl2"
[10] pry(main)> UnixCrypt.valid?("password", "$5$rounds=110000$4QB3mR7z8SVQwVDt$1jT2mdn9XHRD.O8gsCJXoUTDWJSLrY09uD8KZp78ou6")
=> false
[11] pry(main)> UnixCrypt.valid?("password", "$5$4QB3mR7z8SVQwVDt$1jT2mdn9XHRD.O8gsCJXoUTDWJSLrY09uD8KZp78ou6")
=> false

Based on @Niel Slater's comment, I used UnixCrypt's build to rehash "password", and it's still different from the Python hashed string.

[81] pry(main)> UnixCrypt::SHA256.build("password", "4QB3mR7z8SVQwVDt", 110000)
=> "$5$rounds=110000$4QB3mR7z8SVQwVDt$hF/KhMEITeydgv58fXf2brManFYwhhy.dMqGnrkrLb1"

Any workaround on this, or is there any simpler method to verify the user password in Ruby? I'm not familiar with encryption, any detailed explanation on rounds and digests are welcomed too.

1
The strings are different because they include a random salt (the value between the 3rd and 4th $ in the Python version), so you will never see the same hash. You can confirm this by re-hashing "password" in Python, and noticing it is different each time. The failure to validate is more of an issue, and means there is some other difference in the formats and/or algorithms between the python and Ruby versions.Neil Slater

1 Answers

0
votes

Your problem is that you think $5$rounds=110000$4QB3mR7z8SVQwVDt$1jT2mdn9XHRD.O8gsCJXoUTDWJSLrY09uD8KZp78ou6 is a hash of password for some reason. It is not.

In Python:

hash = sha256_crypt.encrypt("password", rounds=110000, salt='4QB3mR7z8SVQwVDt')
# '$5$rounds=110000$4QB3mR7z8SVQwVDt$hF/KhMEITeydgv58fXf2brManFYwhhy.dMqGnrkrLb1'

In Ruby:

UnixCrypt::SHA256.build("password",'4QB3mR7z8SVQwVDt', 110000 )
# "$5$rounds=110000$4QB3mR7z8SVQwVDt$hF/KhMEITeydgv58fXf2brManFYwhhy.dMqGnrkrLb1"

Also:

UnixCrypt.valid?("password", 
   "$5$rounds=110000$4QB3mR7z8SVQwVDt$hF/KhMEITeydgv58fXf2brManFYwhhy.dMqGnrkrLb1")
# => true

So the Ruby gem unix-crypt is the simplest choice for your problem, as it matches the Python library passlib with the least effort (no need to figure out the details, just use the same params and string values throughout).