MegaManSec
09-28-2012, 10:00 PM
This is a 'howto' for using bcrypt for your password hashs, instead of the default vBulletin one, which is highly insecure.
Remember, backup your database before doing this!!
bcrypt is a key derivation function for passwords designed by Niels Provos and David Mazi?res, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.
More information about BCrypt can be found here: http://codahale.com/how-to-safely-store-a-password/ - http://phpmaster.com/why-you-should-use-bcrypt-to-hash-stored-passwords/
tl;dr: if you want to be moar secure, use bcrypt.
" How much slower is bcrypt than, say, MD5? Depends on the work factor. Using a work factor of 12, bcrypt hashes the password 'password' in about 0.3 seconds on my laptop. MD5, on the other hand, takes less than a nanosecond."
BEFORE YOU DO THIS, PLEASE CREATE A .PHP FILE WITH THIS IN IT
<?php
if (defined("CRYPT_BLOWFISH") && CRYPT_BLOWFISH) {
echo "CRYPT_BLOWFISH is enabled!";
}
else {
echo "CRYPT_BLOWFISH is not available";
}
If it is not available, please contact your host.
/includes/functions.php
Add this to the end, just before the footer message.
/**
*
* Hash 'password' using the crypt() function w/ bcrypt
* Use the first 21 characters of the MD5(strrev($salt)) as our bcrypt salt
* Return the MD5 return of this crypt() call, to maintain database functionality. The main part of our security is kept(making hashing, thus cracking, longer).
* This should always be called like hash_password_bcrypt(md5(md5($password) . $salt), $salt)
**/
function hash_password_bcrypt($password, $salt) {
//You may set this to your liking. A higher cost means it will take longer for the password to hash. 15 seems to be a good value.
$cost = 15; // must be in range 04 - 31
return md5(crypt($password, '$2y$' . $cost . '$' . substr(md5(strrev($salt)),0,21) . '$'));
}
includes/class_dm_user.php
Now..
Find this:
if ($password == md5(md5($this->fetch_field('username')) . $salt))
and replace it with this:
if ($password == $this->hash_password($this->fetch_field('username'), $salt))
(Note to self.. Why does the original code use this implicit hashing rather than the hash_password function? hash_password takes cares of md5 stuff already if it's not already md5)
Then, on the same file, replace this:
return md5($password . $salt);
with this
//No need to md5($password), since it is already md5'd above.
return hash_password_bcrypt(md5($password . $salt), $salt);
includes/functions_login.php
Find this:
$vbulletin->userinfo['password'] != iif($password AND !$md5password, md5(md5($password) . $vbulletin->userinfo['salt']), '') AND
$vbulletin->userinfo['password'] != iif($md5password, md5($md5password . $vbulletin->userinfo['salt']), '') AND
$vbulletin->userinfo['password'] != iif($md5password_utf, md5($md5password_utf . $vbulletin->userinfo['salt']), '')
And replace it with this:
$vbulletin->userinfo['password'] != iif($password AND !$md5password, hash_password_bcrypt(md5(md5($password) . $vbulletin->userinfo['salt']), $vbulletin->userinfo['salt']), '') AND
$vbulletin->userinfo['password'] != iif($md5password, hash_password_bcrypt(md5($md5password . $vbulletin->userinfo['salt']), $vbulletin->userinfo['salt']), '') AND
$vbulletin->userinfo['password'] != iif($md5password_utf, hash_password_bcrypt(md5($md5password_utf. $vbulletin->userinfo['salt']), $vbulletin->userinfo['salt']), '')
So effectively, we are hashing the password using the normal vBulletin way of
md5(md5($password) . $vbulletin->userinfo['salt'])
however after doing that, we then run hash_password_bcrypt() around it.
By doing it this way, we can now convert our old hashes to the new bcrypt method.
Create a file called "convert.php", with the contents:
<?php
require("./global.php");
set_time_limit(0);
ini_set('max_execution_time',0);
$q = $db->query_read("select userid, username, password, salt from user WHERE password != ''");
echo "Updating " . $db->num_rows($q) . " accounts.<br />\n";
while($r = $db->fetch_array($q)){
$db->query_write("UPDATE user SET password = '" . hash_password_bcrypt($r['password'], $r['salt']) . "' WHERE userid = '" . $r['userid'] . "'");
echo "Updated password for " . htmlspecialchars($r['username']) . "<br />\n";
}
echo "Finished.<br />\n";
?>
I recommend running the script in a terminal, however you may be able to run it in a browser. If you run it in the browser, it may time out!
Remember, backup your database before doing this!!
bcrypt is a key derivation function for passwords designed by Niels Provos and David Mazi?res, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.
More information about BCrypt can be found here: http://codahale.com/how-to-safely-store-a-password/ - http://phpmaster.com/why-you-should-use-bcrypt-to-hash-stored-passwords/
tl;dr: if you want to be moar secure, use bcrypt.
" How much slower is bcrypt than, say, MD5? Depends on the work factor. Using a work factor of 12, bcrypt hashes the password 'password' in about 0.3 seconds on my laptop. MD5, on the other hand, takes less than a nanosecond."
BEFORE YOU DO THIS, PLEASE CREATE A .PHP FILE WITH THIS IN IT
<?php
if (defined("CRYPT_BLOWFISH") && CRYPT_BLOWFISH) {
echo "CRYPT_BLOWFISH is enabled!";
}
else {
echo "CRYPT_BLOWFISH is not available";
}
If it is not available, please contact your host.
/includes/functions.php
Add this to the end, just before the footer message.
/**
*
* Hash 'password' using the crypt() function w/ bcrypt
* Use the first 21 characters of the MD5(strrev($salt)) as our bcrypt salt
* Return the MD5 return of this crypt() call, to maintain database functionality. The main part of our security is kept(making hashing, thus cracking, longer).
* This should always be called like hash_password_bcrypt(md5(md5($password) . $salt), $salt)
**/
function hash_password_bcrypt($password, $salt) {
//You may set this to your liking. A higher cost means it will take longer for the password to hash. 15 seems to be a good value.
$cost = 15; // must be in range 04 - 31
return md5(crypt($password, '$2y$' . $cost . '$' . substr(md5(strrev($salt)),0,21) . '$'));
}
includes/class_dm_user.php
Now..
Find this:
if ($password == md5(md5($this->fetch_field('username')) . $salt))
and replace it with this:
if ($password == $this->hash_password($this->fetch_field('username'), $salt))
(Note to self.. Why does the original code use this implicit hashing rather than the hash_password function? hash_password takes cares of md5 stuff already if it's not already md5)
Then, on the same file, replace this:
return md5($password . $salt);
with this
//No need to md5($password), since it is already md5'd above.
return hash_password_bcrypt(md5($password . $salt), $salt);
includes/functions_login.php
Find this:
$vbulletin->userinfo['password'] != iif($password AND !$md5password, md5(md5($password) . $vbulletin->userinfo['salt']), '') AND
$vbulletin->userinfo['password'] != iif($md5password, md5($md5password . $vbulletin->userinfo['salt']), '') AND
$vbulletin->userinfo['password'] != iif($md5password_utf, md5($md5password_utf . $vbulletin->userinfo['salt']), '')
And replace it with this:
$vbulletin->userinfo['password'] != iif($password AND !$md5password, hash_password_bcrypt(md5(md5($password) . $vbulletin->userinfo['salt']), $vbulletin->userinfo['salt']), '') AND
$vbulletin->userinfo['password'] != iif($md5password, hash_password_bcrypt(md5($md5password . $vbulletin->userinfo['salt']), $vbulletin->userinfo['salt']), '') AND
$vbulletin->userinfo['password'] != iif($md5password_utf, hash_password_bcrypt(md5($md5password_utf. $vbulletin->userinfo['salt']), $vbulletin->userinfo['salt']), '')
So effectively, we are hashing the password using the normal vBulletin way of
md5(md5($password) . $vbulletin->userinfo['salt'])
however after doing that, we then run hash_password_bcrypt() around it.
By doing it this way, we can now convert our old hashes to the new bcrypt method.
Create a file called "convert.php", with the contents:
<?php
require("./global.php");
set_time_limit(0);
ini_set('max_execution_time',0);
$q = $db->query_read("select userid, username, password, salt from user WHERE password != ''");
echo "Updating " . $db->num_rows($q) . " accounts.<br />\n";
while($r = $db->fetch_array($q)){
$db->query_write("UPDATE user SET password = '" . hash_password_bcrypt($r['password'], $r['salt']) . "' WHERE userid = '" . $r['userid'] . "'");
echo "Updated password for " . htmlspecialchars($r['username']) . "<br />\n";
}
echo "Finished.<br />\n";
?>
I recommend running the script in a terminal, however you may be able to run it in a browser. If you run it in the browser, it may time out!