How should 'raw binary data' hashes be stored in MySQL? - php

I'm wanting to store hashed passwords in MySQL, I'm using PHP:
<?php
$salt = '!£$%^&*()#';
$username = 'abc';
$password = '123';
$hash = hash('sha1', $username . $salt . $password, true);
?>
The true parameter in hash() will return the value as raw binary data. But I don't understand what this means exactly. How should it be correctly stored in MySQL?

I found the solution.
Normal (hexed) hashes of sha1() are always CHAR(40) in length. When you return a hash as raw binary data in php, it will return a string as CHAR(20), saving 50% database space yet representing the exact same value. This is because 2 characters of hex can be compressed into 1 character, thus halving it the space needed.
So store the password as CHAR(20) and use the *_bin collation.

The last parameter of the hash() function indicates how the hash value is returned:
Either as raw binary data. This way
you get the direct output of the
specific hash function you're using, in this case sha1.
Or as a string containing a hexadecimal representation of the same raw binary data.
They are both the same and differ only in their representation. Unless you have a good reason, I would suggest that you use the hexadecimal representation and store it as a string in your database. This way it is much easier to debug problems, since you could easily print out the hexadecimal hash value.

If you do want to store a raw binary string in MySQL, declare the column BINARY(16) if it's of a known fixed length of 16 bytes, VARBINARY(32) if it's of variable length up to 32 bytes, or one of the BLOB types for binary fields that potentially get very long (e.g., BLOB up to 64K, LONGBLOB up to 4G).

The "normal" way of doing this, AFAIK, is to use the addslashes() function.
e.g.:
$hash = hash('sha1', $username . $salt . $password, true);
$query_safe_hash = addslashes($hash);
$query_safe_username = addslashes($username);
$query = "INSERT INTO DBTable(username, password) VALUES ('$query_safe_username', '$query_safe_hash')";
mysql_query($query) or die("Failed to store credentials!");
Side note: from a crypto best practices standpoint, the salt should be a known length, should be generated randomly, and prepended to your hash before being stored to the database. Something like
$salt = generate_random_salt();
$query_safe_hash = addslashes($salt) . addslashes(hash('sha1', $salt . $username . $password, true);
Then to verify the user's credentials, you retrieve the stored hash, remove the slashes, and strip the known salt length from the beginning of the stored hash and use the same salt to generate a hash of the provided credentials, then compare. This helps harden your hash algo against various cryptanalysis attacks (in particular, differential cryptanalysis).

Related

Check password stored with PBKDF2 with php

I have a big problem, I have to write a php login page using an db where password are stored as PBKDF2 (with another perl script). When I get the password with a query I read this:
sha256:1000:2SeBDP88w4bqKbJaCJNpNuRHQhUM96X1:jgh/SZtmRWH5iDIwtXyFLtuuDf7YE+7HQEJZ4KFFNAg= (I know this password but I cannot regenerate it in php).
I tried with this script (get from php.net):
$password = "qqqqq";
$iterations = 1000;
$salt = "2SeBDP88w4bqKbJaCJNpNuRHQhUM96X1";
$hash = hash_pbkdf2("sha256", $password, $salt, $iterations, 20);
echo $hash // result a2ba3349194c38f828af
but the pass generate is a2ba3349194c38f828af and not jgh/SZtmRWH5iDIwtXyFLtuuDf7YE+7HQEJZ4KFFNAg=
who wrote the perl script that store these password told me "The passwords are getting encoded though one-way hashing scheme named 'PBKDF2'"
Some ideas? Someone know where I'm wrong?
jgh/SZtmRWH5iDIwtXyFLtuuDf7YE+7HQEJZ4KFFNAg= is Base64 (the = on the end is a dead giveaway, though Base64 exists without a trailing =).
Converted to hex, the value is 8E087F499B664561F9883230B57C852EDBAE0DFED813EEC7404259E0A1453408
This is still not your answer, but now we can easily see it is 64 hex characters => 32 bytes.
You asked for 20 bytes.
It also looks like your salt input is base64, but the function you're passing it to expects ... whatever the output of base64_decode is.
So, you need to consistently process the base64-encoded data. And then you'll need to make sure that your hash algorithm, iteration count, and output byte count all match what the perl script says.
try this
$hash = strtoupper(bin2hex($hash));

value of salt changing on storing value in mysql database

I'm using the code below to create salt in PHP.
$length=100;
$bool=true;
$salt=openssl_random_pseudo_bytes ( $length, $bool );
when I echo the value it display this value (one instance)
×YOEú ßPŽžJZýr³€žYM½N±~ÄŽ¼D‚ÝÆFÕ`O$I$îÇF üKøäƒþ¥_5ûù„Ð… Ïq®ùä. ³æ¤ljî¬}Të‚´ù­B#3U96
but on storing in the database it gets changed to
Ñx€gþ‚΀³€Œ¯´N§Å·.:gºÈá•ïjÇÖ…áf6uIùYbx}û€·iÀ0èFšDö¼6¥qzMéÁi‡±
The field is using latin1_swedish_ci encoding.
signup script
script containing hashing function, salt generator
table structure
openssl_random_pseudo_bytes returns a raw byte stream, as the name suggests. It doesn't return "characters" which are intended to be readable. How those bytes are being displayed as characters depends entirely on the interpreting party; here quite apparently MySQL is interpreting the bytes in a different encoding than the browser/console where you're displaying it from PHP.
If you're storing those bytes in MySQL, you should be storing them in a BLOB type column, because they're a meaningless blob.
If you want to treat them as characters, you need to encode them. The best is probably a simple bin2hex($salt), which encodes the binary data to hex. Alternatively use base64_encode.
Secondarily, the second parameter to openssl_random_pseudo_bytes is not supposed to be passed as a boolean. It's a pass-by-reference variable which allows you to check after the fact whether the bytes were generated with strong security:
$salt = openssl_random_pseudo_bytes($length, $strong);
if ($strong) {
// $salt is strong
} else {
// $salt is weak
}

Hash a password and save it and the salt to mysql pdo utf8 encoding

I have this question, excuse me if it is not appropriate, tell me in the comments and I will drop it. The thing is as follows:
I generate a salt with this code:
$salt = mcrypt_create_iv(32);
And the password this way:
$password = hash('sha256', $_POST['password'] . $salt);
and save $salt and $password to the MYSQL database, there is no problem so far, if I connect this way:
$pdo = new PDO(
'mysql:host=hostname;dbname=defaultDbName',
'username',
'password'
);
But if I connect this other way:
$pdo = new PDO(
'mysql:host=hostname;dbname=defaultDbName;charset=utf8',
'username',
'password'
);
the salt does not save into the database or save with a wrong value and length; unless I do this:
$salt = utf8_encode( mcrypt_create_iv(32););
But I think it is not right, I mean why I should encode that, what is the problem with mcrypt_create_iv(32) and utf-8 ?
Your solution will work:
mcrypt_create_iv returns a bunch of bytes, which are conveniently put in a string. But because they can have any value, they might result in an incorrect UTF-8 sequence. utf8_encode fixes that, and will work fine as long as you also remember to use utf8_decode after you read it back from the database.
If your connection isn't utf8, then each byte will be treated as a single (whole) ANSI character, and you won't need anything to encode that.
Best solution: keep it binary:
Since this data is actually binary data that is not really a string, a binary field (BINARY, VARBINARY or BLOB) would be a better choice for storing it to prevent any problems due to encoding or decoding.
BINARY is probably the best choice, since this data is neither variable in length, nor large.
See http://dev.mysql.com/doc/refman/5.0/en/binary-varbinary.html
Alternative: store as actual text, without fancy characters:
Another solution is using bin2hex to convert the binary data to a hexadecimal representation. The string will be twice as long, but it contains only letters and numbers, and is safe to story in about any type of string field. You can convert it back using hex2bin.
As the other posters have mentioned mcrypt_create_iv can generate characters the do not fit in utf8. Try something like this:
function generate_random($length=16, $simple=false){
$result='';
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!##$%^&*()+=-_?~';
if($simple)
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
for ($i = 0; $i < $length; $i++)
$result .= $chars[mt_rand(0, strlen($chars)-1)];
return $result;
}
16 characters with a character set of 78 yields 1.877 * 10^30 combinations. That plus the password length should be more than sufficient for a one-way hash like sha256.

PHP version of ASP.net/VB.net Login

I am creating a PHP-version of a login script that is in ASP.net/VB.net. The database where the usernames and passwords are stored is in MSSQL and that will not change. Passwords are stored as binary data. Here's a part of the code in VB.net that compares the password to the one saved in the database.
Dim sha1 As sha1 = sha1.Create()
Dim password As Byte() = sha1.ComputeHash(Encoding.Unicode.GetBytes(Me.txtPwd.Text))
CustData = .GetCustomerByEmail(Me.txtUser.Text, password)
I found out that it's part of a library.
I am encountering problems after hashing the password in PHP's sha1. I don't know how to convert the hashed string to a binary that I could use to compare it to the database.
As an example, the password "Test100" in the MSSQL database has a value of (after unpack('H*', $binaryPassword)) 7397001ce5259b79c436a369b9d3a8c7bc2a85385fdec57a. I am not sure how to get that string above from the hashed password I am getting using PHP.
Here's what I have in PHP so far and where I am stuck in.
$password = sha1(mb_convert_encoding($_POST['password'], 'utf-16le'), false); // $_POST['password'] = 'Test100'
The output for that is 8415ec8cc9287a10f81db5a77341709d304bfa92.
So I am thinking there's one more step to change $password to $binaryPassword or the hexadecimal representation of the binary password.
From this page, it's saying Byte is an 8-bit unsigned integer, but not really sure how to get that from a PHP string.
Thanks in advance!
What happens if you try to run the sh1 on the static text "Test100"?

Comparing passwords with crypt() in PHP

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?

Categories