Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 7 years ago.
Improve this question
I'm using the below functions to help me create a hashed and salted password encryption. I can't using password_hash because I'm running PHP 5.3.
My process:
Generate salt
Generate hash from user created password ie ('password') and salt
Store returned value of create_hash in database in password field. I could separate the salt and hash into two separate fields in the db, but I'm not sure there's a need security wise because if access to my database was gained, access to both the fields would be available and no more secure than storing the 4 component string returned by create_hash, correct?
When user tries to log in, compare the password they enter into the form to the value stored in the database using the validate_password function and if that passes generate a session
Prevent any kind of brute force attack by limiting login attempts to 10 per session? Is this necessary/good practice?
Code:
<?php
/*
* Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
* Copyright (c) 2013, Taylor Hornby
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// These constants may be changed without breaking existing hashes.
define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTE_SIZE", 24);
define("PBKDF2_HASH_BYTE_SIZE", 24);
define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);
function create_salt() {
return base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM));
}
function create_hash($password, $salt)
{
// format: algorithm:iterations:salt:hash
return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" .
base64_encode(pbkdf2(
PBKDF2_HASH_ALGORITHM,
$password,
$salt,
PBKDF2_ITERATIONS,
PBKDF2_HASH_BYTE_SIZE,
true
));
}
function validate_password($password, $correct_hash)
{
$params = explode(":", $correct_hash);
if(count($params) < HASH_SECTIONS)
return false;
$pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
return slow_equals(
$pbkdf2,
pbkdf2(
$params[HASH_ALGORITHM_INDEX],
$password,
$params[HASH_SALT_INDEX],
(int)$params[HASH_ITERATION_INDEX],
strlen($pbkdf2),
true
)
);
}
// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
$diff = strlen($a) ^ strlen($b);
for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
{
$diff |= ord($a[$i]) ^ ord($b[$i]);
}
return $diff === 0;
}
/*
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - The hash algorithm to use. Recommended: SHA256
* $password - The password.
* $salt - A salt that is unique to the password.
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
* $key_length - The length of the derived key in bytes.
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
* Returns: A $key_length-byte key derived from the password and salt.
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
*/
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
if($count <= 0 || $key_length <= 0)
trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);
if (function_exists("hash_pbkdf2")) {
// The output length is in NIBBLES (4-bits) if $raw_output is false!
if (!$raw_output) {
$key_length = $key_length * 2;
}
return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
}
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// first iteration
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
if($raw_output)
return substr($output, 0, $key_length);
else
return bin2hex(substr($output, 0, $key_length));
}
?>
I can't using password_hash because I'm running PHP 5.3
Yes, you can. https://github.com/ircmaxell/password_compat
Barring that, you can use the same algorithm - bcrypt. How do you use bcrypt for hashing passwords in PHP?
It should also be noted that PHP 5.3 was end-of-lifed 7 months ago, making it dangerous to run in production. Chances are it contains a number of security holes, and that'll only continue to get worse. EOL means time to upgrade.
Related
I am working on integrating an API to our web-application. On the initial request, the API returns a response that is encrypted using PBEWithMD5AndTripleDES encryption, and then base 64 encoded. I have an encryption password that is provided to me beforehand. Because of my lack of experience and PBEWithMD5AndTripleDES documentation, I am struggling to decrypt the response. I have tried using phpseclib without any luck.
This is my code with phpseclib
$res = $response->getBody()->getContents();
$res = base64_decode($res);
// this is provided by vendor
$password = self::PASSWORD;
// I tried this too.
//$password = md5(utf8_encode($password), true);
$tripleDes = new TripleDES(TripleDES::MODE_CBC);
$tripleDes->setKey($password);
$ddd = $tripleDes->decrypt($res);
// this is returning false
var_dump($ddd); die();
Can you please provide me some examples of how to use PBEWithMD5AndTripleDES in PHP or point me to some direction or documentation.
PBEWithMD5AndTripleDES uses an MD5 based algorithm for key / IV derivation, which expects a password, a salt and an iteration count as parameters. For encryption TripleDES in CBC mode (des-ede3-cbc) with a 24 bytes key is applied.
PBEWithMD5AndTripleDES is an Oracle proprietary extension of the password-based encryption defined in PKCS#5 (RFC 8018) to support longer keys, here. Because it is proprietary and because of the outdated algorithms like MD5 and the relatively slow TripleDES compared to AES, it should not be used for new implementations, but only for compatibility with legacy code.
I have not found any PHP library on the web that supports PBEWithMD5AndTripleDES out-of-the-box (only for the different PBEWithMD5AndDES, e.g. here). For a custom implementation you actually only need the derivation of the key / IV. So if you don't find an implementation either, but you have compelling reasons to use this algorithm: Here is a Java code that implements the derivation. A port to PHP could be:
function deriveKeyIV($key, $salt, $count){
$result = "";
for ($var = 0; $var < 4; $var++){
if($salt[$var] != $salt[$var + 4])
break;
}
if ($var == 4){
for ($var = 0; $var < 2; $var++){
$tmp = $salt[$var];
$salt[$var] = $salt[3 - $var];
$salt[3 - 1] = $tmp;
}
}
for ($var = 0; $var < 2; $var++){
$toBeHashed = substr($salt, $var * (strlen($salt) / 2), strlen($salt) / 2);
for ($var2 = 0; $var2 < $count; $var2++){
$toBeHashed = hash ("MD5", $toBeHashed . $key, TRUE);
}
$result = $result . $toBeHashed;
}
return $result;
}
The function returns 32 bytes, of which the first 24 bytes are the key and the last 8 bytes are the IV. With this key and IV the encryption with TripleDES in CBC mode can then be performed.
Example:
$keyIv = deriveKeyIV(hex2bin("01026161afaf0102fce2"), hex2bin("0788fe53cc663f55"), 65536);
$key = substr($keyIv, 0, 24);
$iv = substr($keyIv, 24, 8);
print(bin2hex($key) . "\n");
print(bin2hex($iv) . "\n");
print(openssl_encrypt("The quick brown fox jumps over the lazy dog", "des-ede3-cbc", $key, 0, $iv));
Output:
543650085edbbd6c26149c53a57cdd85871fd91c0f6d0be4
d7ffaa69502309ab
m4pye0texirKz1OeKqyKRJ5fSgWcpIPEhSok1SBDzgPthsw9XUuoiqXQBPdsVdUr
As reference I used a Java implementation, more precisely the implementation of PBEWithMD5AndTripleDES of the SunJCE provider, which gives the same result.
Note that the original implementation of PBEWithMD5AndTripleDES only allows a salt that is exactly 8 bytes in size (although the derivation function can handle larger salts), otherwise an exception is thrown (salt must be 8 bytes long). To add this constraint, the following can be added at the beginning of deriveKeyIV:
if (strlen($salt) != 8) {
throw new Exception('Salt must be 8 bytes long');
}
Given a N-bit hash (e.g. output of md5()), I have 2 situations I need solutions for:
Based on the hash, return an integer value in a given range.
Based on the hash, return an array value from a given array.
Same hash, should always return same number or array key within that range or from that same input array. If the input array changes but hash remains the same, then i would get a different selection.
So for example i would have code like this:
echo intFromHash(1, 100, 'abcd'); // 15
echo intFromHash(1, 100, 'defg'); // 90
echo arrayValueFromHash(['moe', 'joe', 'pike'], 'abcd'); // 'joe'
echo arrayValueFromHash(['pike', 'dolly']); // pike
You can write intFromHash() in 1 line of code using the crc32() PHP function:
function intFromHash($min, $max, $hash {
return $min + crc32($hash) % ($max - $min + 1);
}
Use abs(crc32($hash)) if you are running it on a 32-bit system (read the documentation for details).
Then you can use it to implement arrayValueFromHash() (in another line of code):
function arrayValueFromHash(array $array, $hash) {
return $array[intFromHash(0, count($array) - 1, $hash)];
}
Use return $array[array_keys($array)[intFromHash(...)]]; if $array is an associative array (the expression presented in the code works only for numerically indexed arrays, as those listed in the question.)
Figured it out I think. Here's the code for whoever needs it:
/**
* Return a key from an array based on a given 4-bit hash.
*
* #param array $array Array to return a key from.
* #param string $hash 4-bit hash. If hash is longer than 4-bit only first 4 bits will be used.
* #return mixed
*/
function getArrayValueByHash($array, $hash)
{
$arrayKeys = array_keys($array);
$index = getIntFromHash(0, sizeof($arrayKeys)-1, $hash);
return $array[$arrayKeys[$index]];
}
/**
* Return an integer in range, based on a hash.
*
* #param int $start
* #param int $end
* #param string $hash 4-bit hash. If hash is longer than 4-bit only first 4 bits will be used.
* #return int
*/
function getIntFromHash($start, $end, $hash)
{
$size = $end-$start;
$hash = str_split($hash);
$intHash = ord($hash[0]) * 16777216 + ord($hash[1]) * 65536 + ord($hash[2]) * 256 + ord($hash[3]);
$fits = $intHash / $size;
$decimals = $fits - floor($fits);
$index = floor($decimals * $size);
return $start+$index;
}
What is the "best" solution these today?
This seems a good option:
https://defuse.ca/php-pbkdf2.htm
But then how about upgrading to PHP5.5 and using this?
http://php.net/manual/en/function.hash-pbkdf2.php
Curious as to why the PHP site states:
Caution
The PBKDF2 method can be used for hashing passwords for storage (it is NIST approved for that use). However, it should be noted that CRYPT_BLOWFISH is better suited for password storage and should be used instead via crypt().
For PHP versions less that 5.5 would it be fair to use the defuse.ca solution, and then just switch it out after upgrading to PHP5.5?
/*
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - The hash algorithm to use. Recommended: SHA256
* $password - The password.
* $salt - A salt that is unique to the password.
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
* $key_length - The length of the derived key in bytes.
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
* Returns: A $key_length-byte key derived from the password and salt.
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
*/
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
die('PBKDF2 ERROR: Invalid hash algorithm.');
if($count <= 0 || $key_length <= 0)
die('PBKDF2 ERROR: Invalid parameters.');
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// first iteration
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
if($raw_output)
return substr($output, 0, $key_length);
else
return bin2hex(substr($output, 0, $key_length));
}
This is the current solution from defuse.ca, would it be fair to rename this function to hash_pbkdf2() and after upgrading to PHP5.5 transition would be nice and smooth?
The accepted best practice in PHP passwords, as of PHP 5.5, is password_hash. It presents a single, unified, built-in, future-compatible way to generate a secure password hash.
If you are using a security-updated version of 5.3.x or higher, you can use the password_compat library instead.
Under the covers, the current version makes calls to crypt with some predefined security options. Future versions may change the default options.
Please be sure to carefully read the section on the crypt page that talks about CRYPT_BLOWFISH versioning, as well as review the versioning notes on the password_compat page.
As noted clearly in the warning message, PBKDF2 is accepted by the NIST as an adequate way to store passwords. You can use implementations of it without significant concern, but you should only do so if you either need support for PHP versions prior to 5.3, or need to support PHP versions that have a broken CRYPT_BLOWFISH.
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
will this function be safe for password and email hash/crypt? EDIT: Cleary not!
$password = mysql_real_escape_string(htmlspecialchars(trim($_POST['password'])));
$hash_algo = "sha512";
$raw_output = false;
$hash = hash($hash_algo, $password, $raw_output);
$hash_20 = substr($hash, 0, 20);
$salt = substr($hash, -20);
$crypt = crypt ( $hash_20, $salt);
$crypt_20 = substr($crypt, 0, 20);
EDIT:
Here is the code I'm using now. I think this one is pretty safe. It's a PBKDF2 password hash function with a random salt generator.
So, here is the PBKDF2 function.
p is for password.
s is for salt.
c is for iteration
kl is for key lenght.
a is for hash algorithm.
function pbkdf2( $p, $s, $c, $kl, $a = 'sha256' )
{
$hl = strlen(hash($a, null, true)); # Hash length
$kb = ceil($kl / $hl); # Key blocks to compute
$dk = ''; # Derived key
# Create key
for ( $block = 1; $block <= $kb; $block ++ ) {
# Initial hash for this block
$ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true);
# Perform block iterations
for ( $i = 1; $i < $c; $i ++ )
# XOR each iterate
$ib ^= ($b = hash_hmac($a, $b, $p, true));
$dk .= $ib; # Append iterated block
}
# Return derived key of correct length
return substr($dk, 0, $kl);
}
Salt generator:
function salt( $length )
{
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$salt="";
$size = strlen( $chars );
for( $i = 0; $i < $length; $i++ )
{
$salt.= $chars[ rand( 0, $size - 1 ) ];
}
return $salt;
}
In use:
if(isset($_POST['submit']))
{
$Password = mysql_real_escape_string(htmlspecialchars(trim($_POST['Password'])));
//To make sure the salt has never more chars than the password.
$salt_length = strlen($Password);
$salt = salt($salt_length);
//Hash Password
$hash = base64_encode(pbkdf2($Password, $salt, 100000, 32));
//--------------//
}
Googling a bit find out that 100000 iterations is pretty safe but I guess 10000 will be enough tho.
Since you're hashing the input, you cannot simply reverse it to the original value. Assuming an attacker knows this algorithm, the question is how long does it take to brute force the password. For that, test how long one iteration of the algorithm takes. Then calculate how many tries an attacker would have to do to try all possible passwords on a high-end machine. Then you have your answer how "safe" the algorithm is. You are looking for an answer measured at least in millennia, but preferably big bangs.
That is, assuming there are no actual attacks against the algorithm an attacker could try that would shorten that time.
Since you are deriving the salt from the input itself, you're simply stretching the algorithm a bit. You're not using an actual salt, which is a random unique value that is independent of the input. As such, you are using an unsalted input with a not so complicated hashing algorithm. My bet would be that it's not very hard to brute force a whole database of passwords "secured" with this algorithm.
I have been trying to make my users passwords really secure using pbkdf2.
The password hash goes into the database fine, but the salt is not.
It seems the salt contains exotic characters that the mysql column doesnt like.
All columns in my 'users' table are UTF8_unicode_ci.
Here is my password hasher:
$size = mcrypt_get_iv_size(MCRYPT_CAST_256, MCRYPT_MODE_CFB);
$salt = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
$passHash = pbkdf2('SHA512', $pass, $salt, 8192, 256) ;
include("dbconnect.php") ;
$result = $dbh->prepare("INSERT INTO users (name, email, qq, password, salt)VALUES(?, ?, ?, ?, ?)") ;
$result->bindParam(1, $name, PDO::PARAM_STR) ;
$result->bindParam(2, $email, PDO::PARAM_STR) ;
$result->bindParam(3, $qq, PDO::PARAM_STR) ;
$result->bindParam(4, $passHash, PDO::PARAM_STR) ;
$result->bindParam(5, $salt, PDO::PARAM_STR) ;
$result->execute() ;
And the pbkdf2:
/*
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - The hash algorithm to use. Recommended: SHA256
* $password - The password.
* $salt - A salt that is unique to the password.
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
* $key_length - The length of the derived key in bytes.
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
* Returns: A $key_length-byte key derived from the password and salt.
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
*/
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false){
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
die('PBKDF2 ERROR: Invalid hash algorithm.');
if($count <= 0 || $key_length <= 0)
die('PBKDF2 ERROR: Invalid parameters.');
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// first iteration
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
if($raw_output)
return substr($output, 0, $key_length);
else
return bin2hex(substr($output, 0, $key_length));
}
Also, I have just noticed that it is storing totally different hashes for passwords that are the same.
Am I doing this right?
It's hard to give an exact answer to a question as broad as 'Am I doing this right?' I can say that the entire point of salting passwords before hashing is so that the resultant hash will be unique between users. So getting different output for the same input is a good thing. Look up 'dictionary attack' for more information on why.
As to your code, it sounds like what you really want to know is why your salt isn't getting stored to the database. Debugging steps I can think of, without more specific details
$salt could be false, mcrypt_create_iv returns false on error (unlikely because of the hash output differences you mentioned above, but worth checking.
Output characters are not recognized as you suspect. You could try converting the database column types to varbinary and using a string to binary or hex decoder before adding to your prepare.
Try experimenting with column types with different character encodings and see what column type your salts can go into. UTF-8 uses a variable number of bytes for each character, which makes me uncomfortable when dealing with things that are absolutes. A salt and a hash are generally considered to be fixed-with bit fields, often expressed in hex format for convenience.
I might be able to narrow down a problem if you provided your server environment, php version, mysql version etc. and a few samples of salts which aren't being stored correctly.
You should convert the result into base64 encoding before storing to a varchar column. Base64 encoding basically converts an array of bytes into something in the ASCII range of displayable (and therefore SQL storable) characters.