There is an application written in PHP which I am converting to Ruby. When encrypting passwords the PHP app uses the following code:
if($method == 2 && CRYPT_BLOWFISH) return crypt($pass, '$2a$07$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxx$');
I'm assuming this is using a Blowfish implementation. The x's here are all a-zA-Z0-9 characters.
The Blowfish implementation in Ruby uses the following syntax (taken from http://crypt.rubyforge.org/blowfish.html):
blowfish = Crypt::Blowfish.new("A key up to 56 bytes long")
plainBlock = "ABCD1234"
encryptedBlock = blowfish.encrypt_block(plainBlock)
I don't have a 56 or fewer byte long string and it's not clear what that should be from the PHP version. So how can I write a Ruby function which will encrypt passwords to give the same result as the PHP one?
The PHP code is hashing $pass with the salt $2a$07$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxx$ if CRYPT_BLOWFISH is set (CRYPT_BLOWFISH == 1). The salt has to follow the format indicated in the PHP documentation ("$2a$", a two digit cost parameter, "$", and 22 digits from the alphabet "./0-9A-Za-z").
I'm not sure if you can do it with the library you are referring, but you can use bcrypt-ruby instead.
For your code it would be something like this, I'm using the same data from the PHP example ( http://php.net/manual/en/function.crypt.php ), I only take the 29 first characters of the salt because beyond that PHP ignores it:
require 'bcrypt-ruby'
pass = "rasmuslerdorf" # Here you should put the $pass from your PHP code
salt = '$2a$07$usesomesillystringfors' # Notice no $ at the end. Here goes your salt
hashed_password = BCrypt::Engine.hash_secret(pass,salt) # => "$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi"
This gives you the same output as on the PHP example. If your salt is too long take the first 29 characters ($2a$07$ plus the next 22 extra characters).
I tested the behavior of PHP, if the salt is too long (beyond 29 characters in total) the rest is ignored, if the salt is too short it will return 0. E.g in PHP:
<?php
crypt('rasmuslerdorf', '$2a$07$usesomesillystringforsalt$')
// returns $2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi
crypt('rasmuslerdorf', '$2a$07$usesomesillystringfors')
// returns $2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi
crypt('rasmuslerdorf', '$2a$07$usesomesilly')
// returns 0 because the salt is not long enough
?>
Related
The following code snippet should not emit 'MATCHED' because the password 'testtest' does not match 'testtesttest', but does on PHP 7.4.3 for me. Am I doing something wrong?
<?php
$sPass = 'testtesttest';
$sSalt = hash('sha256','this is my salt');
$sShadow = password_hash($sSalt . $sPass,PASSWORD_BCRYPT);
echo (password_verify($sSalt . 'testtest',$sShadow) ? 'MATCHED' : 'nomatch');
Note, if you remove the salt references above, the code works fine. It's like the password_hash and password_verify functions of PHP have a size limitation where they no longer become accurate if the string is longer than so many characters.
So, I'm thinking this is a bug.
BCrypt can only handle 72 characters, your salt takes up 64 characters, so only 8 characters of your password are considered.
The input to the bcrypt function is the password string (up to 72 bytes), a numeric cost, and a 16-byte (128-bit) salt value.
Use the binary form of your salt to not "waste" as many characters or just don't use one at all, as password_hash will generate one anyway.
Before we begin: Yes I am aware that I should use PHP's password_hash function when actually storing passwords. This is a question about the internals of PHP's hashing system.
So I was messing around with PHP's crypt function the other day, and I noticed some odd behavior with bcrypt.
$password = "totallyagoodpassword";
$salt = "hereisa22charactersalt";
$parameter = '$2y$10$' . $salt;
echo $parameter . PHP_EOL;
echo crypt($password, $parameter);
According to PHP's manual, this code should hash "totallyagoodpassword" using bcrypt, salting it with "hereisa22charactersalt." The output of this hash should be the scheme ("$2y$10$"), followed by the 22 characters of the salt, then followed by 31 characters of hash. Therefore, I should expect "$2y$10$hereisa22charactersalt" and then 31 characters of random base64 characters.
So I run the code:
$2y$10$hereisa22charactersalt
$2y$10$hereisa22charactersalev7uylkfHc.RuyCP9EG4my7WwDMKGRvG
And I can't help but notice how the salt I passed into crypt and the salt that came out aren't the same; specifically, the very last character magically became an "e." After running this with different salts, I still get this same quirk where the last and only last character of the output hash is different.
I'm not a developer for PHP, so I'm sure there is some logic behind this behaviour. But I'm curious.
The docs do not state that the output will include the entire 22 bytes of salt. Also the example on the crypt documentation shows a final "$" on the salt.
crypt('rasmuslerdorf', '$2a$07$usesomesillystringforsalt$')
Producing:
$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi
Using the password "testtest" and the hash "KFtIFW1vulG5nUH3a0Mv" with the following code results in different hashes depending on the PHP version (as shown in the image below):
$salt = "KFtIFW1vulG5nUH3a0Mv";
$password = "testtest";
$key = '$2a$07$';
$key = $key.$salt."$";
echo crypt($password, $key);
Output 1 (PHP v.5.3.0 - 5.4.41, 5.5.21 - 5.5.25, 5.6.5 - 5.6.9): $2a$07$KFtIFW1vulG5nUH3a0Mv$.0imhrNa/laTsN0Ioj5m357/a8AxxF2q
Output 2 (PHP v.5.5.0 - 5.5.20, 5.6.0 - 5.6.4): $2a$07$KFtIFW1vulG5nUH3a0Mv$e0imhrNa/laTsN0Ioj5m357/a8AxxF2q
Here is an example of the problem:
http://3v4l.org/dikci
This is a massive issue if crypt is being used to hash passwords for a login, as depending on PHP version the hash will be different. Anybody understand what this issue is from and how to deal with it?
This is not so much an issue as you may think.
First you should note, that your code is not absolutely correct, BCrypt requires a 22 character salt, but you provided a 20 character salt. This means that the terminating '$' (which is not necessary btw) will be seen as part of the salt, as well as the first letter of your password. The $ is not a valid character for a BCrypt salt though.
Another thing to consider is that not all bits of character 22 are used, this is due to the encoding, ircmaxell gave a good explanation about this. So different salts can result in the same hash, you can see this well in this answer. How different implementations handle this last bits of the character 22 could theoretically change. As long as the crypt function can verify the password with both hashes there is no problem.
The generation of the salt with its pitfalls is one of the reasons, why the functions password_hash() and password_verify() where written, they make the password handling much easier.
We have a website built in Ruby on Rails.
User registration is processed through the site in Ruby and the password is hashed using SHA1.HexDigest function of Ruby with a different salt for every user.
What I need to do is that - create a webservice in PHP which will login the user already registered on the website.
For that I will need to produce a same hash from the user input.
As I have almost zero knowledge of Ruby, so I did a lot of research on how we can reproduce the same with PHP. I went through the following link, but to no avail.
How to generate the password in PHP as it did by Devise Gem in Ruby on Rails
Ruby also processes/hashes the input for a number of times (i.e. stretches, as you may call it in Ruby).
The hash saved in the database is 128 characters in length.
Salt length is 20 characters.
Don't know if some sort of pepper is used as well.
Example,
user input = 123456
salt = g0i3A51x0xa7wrfWCMbG
database password (128 char hash) = 5374f9853f96eaa5b3c1124f9eb1dbbb63fb5c5ce40abb41ec88c745ec3455328685f3046cac8c356a4d81dbd315fd09173c54dc94a4208e5bc091776b02eb77
If someone can replicate the same hash with PHP, using the above given user-input and salt, then please share the code.
Please help.
It'll be very helpful of urs.
Thanks
class Sha1 < Base
# Gererates a default password digest based on stretches, salt, pepper and the
# incoming password.
def self.digest(password, stretches, salt, pepper)
digest = pepper
stretches.times { digest = self.secure_digest(salt, digest, password, pepper) }
digest
end
private
# Generate a SHA1 digest joining args. Generated token is something like
# --arg1--arg2--arg3--argN--
def self.secure_digest(*tokens)
::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--')
end
I would start with a simpler hashing routine on both sides, stretching and salt are great, but start without those and do a simple hash on both sides to see if you can get them to line up. Then you can go add salt/stretching later.
On the ruby side start with something like:
require 'digest/sha1'
Digest::SHA1.hexdigest(string)
When you hash you cant decrypt, you have to hash on both sides and compare the result.
If you really want to encrypt something and then decrypt, try this:
http://jnylund.typepad.com/joels_blog/2007/11/cross-language-.html.
A straightforward translation of the above Ruby code is:
function hashPassword($password, $stretches, $salt, $pepper) {
$digest = $pepper;
for ($i = 0; $i < $stretches; $i++) {
$digest = sha1("--$salt--$digest--$password--$pepper--");
}
return $digest;
}
To use SHA-512 (128 hex digit output) instead of SHA-1 (40 hex digit output), replace sha1(...) with hash('sha512', ...).
I cannot check this code against the hash you posted because the "pepper" is a secret configuration setting I do not know. If it is not configured, try the empty string.
I need to get the basics of this function. The php.net documentation states, for the blowfish algorithm, that:
Blowfish hashing with a salt as follows: "$2a$", a two digit cost parameter, "$", and 22 base 64 digits from the alphabet "./0-9A-Za-z". Using characters outside of this range in the salt will cause crypt() to return a zero-length string
So this, by definition, should not work:
echo crypt('rasmuslerdorf', '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringforsalt$');
However, it spits out:
$2a$07$usesomadasdsadsadsadaeMTUHlZEItvtV00u0.kb7qhDlC0Kou9e
Where it seems that crypt() has cut the salt itself to a length of 22. Could somebody please explain this?
Another aspect of this function I can't get my head around is when they use crypt() to compare passwords. http://php.net/manual/en/function.crypt.php (look at ex. #1). Does this mean that if I use the same salt for all encrypting all my passwords, I have to crypt it first? ie:
$salt = "usesomadasdsadsadsadae";
$salt_crypt = crypt($salt);
if (crypt($user_input, $salt) == $password) {
// FAIL WONT WORK
}
if (crypt($user_input, $salt_crypt) == $password) {
// I HAVE TO DO THIS?
}
Thanks for your time
Following code example may answer your questions.
To generate hashed password using Blowfish, you first need to generate a salt, which starts with $2a$ followed by iteration count and 22 characters of Base64 string.
$salt = '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringfors';
$digest = crypt('rasmuslerdorf', $salt);
Store the whole $digest in database, it has both the salt and digest.
When comparing password, just do this,
if (crypt($user_input, $digest) == $digest)
You are reusing the digest as salt. crypt knows how long is the salt from the algorithm identifier.
New salt for every password
$password = 'p#ssw0rd';
$salt = uniqid('', true);
$algo = '6'; // CRYPT_SHA512
$rounds = '5042';
$cryptSalt = '$'.$algo.'$rounds='.$rounds.'$'.$salt;
$hashedPassword = crypt($password, $cryptSalt);
// Store complete $hashedPassword in DB
echo "<hr>$password<hr>$algo<hr>$rounds<hr>$cryptSalt<hr>$hashedPassword";
Authentication
if (crypt($passwordFromPost, $hashedPasswordInDb) == $hashedPasswordInDb) {
// Authenticated
Quoting from the manual
CRYPT_BLOWFISH - Blowfish hashing with
a salt as follows: "$2a$", a two digit
cost parameter, "$", and 22 base 64
digits from the alphabet
Note: 22 base 64 digits
BCrypt uses 128 bits for salt, so 22 bytes Base64, with only two bits of the last byte being used.
The hash is computed using the salt and the password. When you pass the crypted password, the algorithm reads the strength, the salt (ignoring everything beyond it), and the password you gave, and computes the hash, appending it. If you have PostgreSQL and pg_crypto handy, SELECT gen_salt('bf'); will show you what of $salt is being read.
Here's a code sample for salt generation, from my .NET implementation's test-vector-gen.php, alternatively:
$salt = sprintf('$2a$%02d$%s', [strength goes here],
strtr(str_replace(
'=', '', base64_encode(openssl_random_pseudo_bytes(16))
),
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
'./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));
There is no reason to use the same salt for all of your passwords. The salt is part of the output anyway so you gain nothing in convenience... though I grant PHP ought to have a built-in gen_salt function.
First question:
So this, by definition, should not work:
echo crypt('rasmuslerdorf', '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringforsalt$');
Where it seems that crypt() has cut
the salt itself to a length of 22.
Could somebody please explain this?
There isn't a problem with having too many characters... the phrase Using characters outside of this range in the salt will cause crypt() to return a zero-length string referse to outside the range of base 64 not the range of 22 characters. Try putting an illegal character in the salt string, and you should find that you get an empty output (or if you put < 22 characters in, resulting in illegal empty bytes).
Second question:
You pass in the encrypted stored password as salt because the salt string always appears (by design) in the encrypted string, and this way you ensure that you have the same salt for both encryption of stored and user-entered password.
This question is in relation to my response to ZZ Coder's answer. Basically my question is regarding storing the crypt() result in the database. Am I supposed to store the entire output in the database, so that my database looks like this:
--------------------------------------------------------------------------------
| ID | Username | Password |
--------------------------------------------------------------------------------
| 32 | testuser | $2a$07$usesomadasdsadsadsadaeMTUHlZEItvtV00u0.kb7qhDlC0Kou9e |
--------------------------------------------------------------------------------
If yes, then doesn't this kind of defy the purpose of using a salt in the first place? If someone gains access to the db, they can clearly see the salt used for the encryption?
Bonus question: Is it secure to use the same salt for every password?