This is being used for a personal website, so it's more a learning experience than being super secure. That said, I've been reading various things you're supposed to do, and to me at least, this seems pretty secure, despite perhaps the low memory usage.
However, everywhere I read says never try do it yourself, so obviously there will be some flaws with my way. Could anyone suggest any improvements? For the record I was trying to do it with libraries that were built into the free webhosts, since both PBKDF2
and bcrypt
weren't in.
The salt is generated via substr(uniqid(mt_rand(), true), 0, 32)
(substr to avoid it overflowing the database occasionally it ends up as 33 characters), and encrypted before being entered into the database. This is via a hash of a global key, so you'd also need the contents of a php file to crack it (I may switch it to 'global key + email' though).
The IV is just stored inside a folder, so even if you hack the database and get the contents of a php file, you still need to get past htaccess to read a file. This can be deleted, where the next run of the function will generate a new one, and therefore render all passwords invalid.
The actual password hash uses key stretching, but to a value that's a modulus of a crc32 value, so each user has a different amount of hashes that are done. Each repeat hash uses a few hashes inside it to try keep it unique.
$salt_key = 'seemingly random string of characters, numbers and symbols';
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;
}
There are a couple of other functions but they're mainly for checking the entered password against the one in the database. Are there some important parts I've missed, or for a small scale site would this probably be alright?
Also, quick question about key stretching, if I make it take half a second to calculate, doesn't that mean if the site suddenly gets 500 people trying to log in, someone will have to sit there for 250 seconds because the CPU is overloaded?
I just had an idea to store the max value of hashes (129197 in the above code) in the user table, so it can be changed if needed without invalidating the passwords.