2

I am developing on a mac and deploying to Linux. I am writing a Rails API on top of an existing PHP app, so I cannot change the passwords or the crypt, but I need authentication to work. I need to be able to auth against the existing password and then ultimately change the password to still be able to be read by the original PHP app. I have access to the exact crypt function. I need a solution in Ruby (can use a gem).

The code (salt changed to protect the innocent) in PHP is this:

$passwordSHA = crypt($_POST['pass'],'$6$rounds=21000$abcdefghijklmnopqrstuv$');

I'm trying to write a function to verify that the user supplied password matches the encrypted password in the database. I tried doing what this suggests with the correct salt, but it didn't work (which may just have something to do with rounds not being included correctly).

The password stored in the database looks (sorta, changed for security) like this. Note that the beginning is always the $6$rounds=21000$zxyabcdefghijklm$:

$6$rounds=21000$zxyabcdefghijklm$F/L4T80nbaaaaLMb7nPJ2OV5H/aaa.00v900000/Z5jlTLSa.XXXXXX./444444/p8b61UBz9z2Bj4qsABC4.

I've tried a few different things even with OpenSSL and BCrypt but I can't figure out how to even get something starting with $6.

UPDATE: Trying the first answer, I am not getting anything near the correct length. Maybe this information is helpful in figuring out why this doesn't work:

2.5.3 :001 > ruby_crypt = "foo".crypt('$6$rounds=21000$salt$')
 => "$6A86JNndVTdM"

UPDATE 2: Please note the salt length. It's longer than the max length of 16 that UnixCrypt allows.

UPDATE 3/ANSWER: The problem here was that the salt should be truncated past 16 characters. Then additionally PHP inserts the round count afterwards. The final answer is:

hashed_password = UnixCrypt::SHA512.build(password, salt, 5000).gsub('$6', '$6$rounds=21000')
Eric Lubow
  • 763
  • 2
  • 12
  • 30
  • 1
    `$6$` is the Modular Crypt Format Identifier Code for SHA512-crypt. It is most definitely *not* BCrypt, which uses `2` (i.e. `$2$`, `$2a$`, `$2x$`, `$2y$`, or `$2b$` depending on version and parameters.) – Jörg W Mittag Aug 16 '20 at 11:34

1 Answers1

4

Using UnixCrypt for convenience, this seems to work:

require 'unix_crypt'

PHP_CMD = %q|php -r 'echo crypt("foo", "\$6\$rounds=21000\$salt\$");'|
php_crypt = `#{PHP_CMD}`

puts "PHP:"
puts php_crypt

# This code is using native crypt and is platform dependent and not portable
# ruby_crypt = 'foo'.crypt('$6$rounds=21000$salt$')

# Because $6 means SHA512, we can use UnixCrypt instead for portability (thanks @matt)
ruby_crypt = UnixCrypt::SHA512.build('foo', 'salt', 21000)

puts "\nRUBY:"
puts ruby_crypt

# If doing password validation only, it is best to use the 'valid?' method,
# as it will handle modifications in the algorithm and salt automatically.
puts "\nValidate:"
p php_crypt == ruby_crypt
p UnixCrypt.valid?('foo', php_crypt)
p UnixCrypt.valid?('foo', ruby_crypt)

Output:

PHP:
$6$rounds=21000$salt$kvEIl.U0dy6F6y9dkTeTwUrpCOpz5eYK.jyTC2LeKo/gq9HYpDtD6.fMlHyLW.5h3fF4v.R19lX8W18KDvper1

RUBY:
$6$rounds=21000$salt$kvEIl.U0dy6F6y9dkTeTwUrpCOpz5eYK.jyTC2LeKo/gq9HYpDtD6.fMlHyLW.5h3fF4v.R19lX8W18KDvper1

Validate:
true
true
true
Casper
  • 33,403
  • 4
  • 84
  • 79
  • I've tried this every which way I can think of and I never seem to get anything longer than this from Ruby: `$6OAR9e31dlDg`. Is possible that it has something to do with the length of the salt? The output of your code for is this: ```2.5.3 :001 > ruby_crypt = "foo".crypt('$6$rounds=21000$salt$') => "$6A86JNndVTdM"``` – Eric Lubow Aug 16 '20 at 13:03
  • 3
    You’re not actually using the unix-crypt gem to generate the password here, but rather Ruby’s built in [crypt](https://ruby-doc.org/core-2.7.1/String.html#method-i-crypt) (which is platform dependent and is probably why @EricLubow isn’t getting the right results). You should change `ruby_crypt = "foo".crypt('$6$rounds=21000$salt$')` to `ruby_crypt = UnixCrypt::SHA512.build('foo', 'salt', 21000)`. (This will mean that the algorithm can’t be changed by just changing the salt string). – matt Aug 16 '20 at 13:44
  • @EricLubow If you only need to do verification in Ruby, and not generation, then running `UnixCrypt.valid?` is the most portable solution for you. – Casper Aug 16 '20 at 13:56
  • I actually will eventually need to do generation at some point. When I use UnixCrypt, I get the error: `UnixCrypt::SaltTooLongError (Salts longer than 16 characters are not permitted)` when I use the same salt that PHP uses. – Eric Lubow Aug 16 '20 at 14:25
  • 2
    @EricLubow 16 bytes is the max for SHA512Crypt. Ruby throws an exception if you try to use a longer one, PHP just truncates it to 16 bytes. Truncate the salt in Ruby to 16 bytes before hashing, you should get the same result. – matt Aug 16 '20 at 14:54
  • 1
    @matt I just tested this and you are correct. Truncating the salt works and produces an identical result. – Casper Aug 16 '20 at 15:02
  • Yes, it produces *almost* the identical result. The rounds count is somehow also in the database field. So I guess I will have to do an `s///` to add that in make it match. I was hoping it just could be added like in PHP. Thank you. – Eric Lubow Aug 16 '20 at 15:05