For the record, this was purely for learning purposes, I've been told don't even use this for a personal website that I don't expect anyone to visit, so this post is unfortunately the last I'll see of this code. However, I've done all the things I read about and it seems decent to me (but I'm no expert), so I'm just curious as to which parts could potentially be bad enough that the recommendation to everyone is to never ever use a custom solution?
Not naming anyone, but I'm pretty sure I've already done a miles better job than the free webhost that begins with a triple number haha, my plaintext password was stolen from there.
Here's the basic idea of what I did:
Salt and IV
Each salt is generated by a random but not cryptographically secure function in PHP (it's better than the default random though). It is then encrypted with blowfish using a key defined in the code, and an IV defined in an actual file (which is protected by htaccess). The encrypted value is stored, so it's very easy to change the IV or salt key to invalidate all passwords. Also if the database is compromised, the salts still can't be decrypted without getting access to the file system.
Hash
It uses key stretching and sha512. The amount of key stretching is defined by a modulus of a crc32 value of the password and salt, so each user has a different amount, and you can change the value to allow longer processing times. Each repeat hash uses a few hashes inside it in an attempt to keep it pretty random.
Here's the actual code I have, if you don't know PHP it's still fairly easy to see what's going on line by line:
$salt_key = 'íSgüý[Îaí²zÿCV™F÷œDDa©^fU©êm���“.Ù ®Kâ³ÃX}rÿ,¢\ˆÿ7L³¢QÆH’YeTsÙõŸ¥éÙí#W—c´VÆ';
function get_iv(){
// It stores it as a file, but there's no point showing that part so here's the actual code bit
return mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC), MCRYPT_RAND);
}
function generate_salt(){
global $salt_key;
$salt = substr(uniqid(mt_rand(), true), 0, 32);
$key = hash('sha256', $salt_key, true);
$iv = get_iv();
$encrypt = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $salt, MCRYPT_MODE_CFB, $iv);
return array($salt, $encrypt);
}
function decrypt_salt($encrypted){
global $salt_key;
$key = hash('sha256', $salt_key, true);
$iv = get_iv();
return mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encrypted, MCRYPT_MODE_CFB, $iv);
}
function hash_password($password, $salt){
#return hash('sha512', $password.$salt, true);
$hash = $salt;
for($i=0; $i<(crc32($salt.$password) % 129197); $i++){
$hash = hash('sha512', $hash.$password.$salt.hash('sha512', $salt.$hash.$password).md5($salt.md5($password.$hash)), true);
}
return $hash;
}
The part to check if the password is valid basically just checks the new hash against the old one so I left it out. The only drawback based on the things I've read a good algorithm should do, is it doesn't use a lot of memory.
bcrypt/PBKDF2
".mt_rad()
. The MT scheme is known to generate poor random numbers at the beginning and then generate better ones after some time. Therefore if you run this as plain PHP you will get predictable salts, yet if you use something likephp-fpm
then you will end with good state for the PRNG. That's bad because your scheme produces different security concerns depending on how it is used.