关于最佳算法,有很多讨论 - 但如果您已经在生产中,如何升级而无需重置用户?
编辑/免责声明:尽管我最初想要一个“快速修复”解决方案并选择了orip的回答,但必须承认,如果您的应用程序中的安全性足够重要以至于要处理此问题,则快速修复是错误的心态,他提出的解决方案可能是不充分的。
关于最佳算法,有很多讨论 - 但如果您已经在生产中,如何升级而无需重置用户?
编辑/免责声明:尽管我最初想要一个“快速修复”解决方案并选择了orip的回答,但必须承认,如果您的应用程序中的安全性足够重要以至于要处理此问题,则快速修复是错误的心态,他提出的解决方案可能是不充分的。
一种选择是在存储的哈希值中加入算法版本号 - 因此你可以从算法0(例如MD5)开始存储。
0:ab0123fe
然后当你升级到SHA-1时,你将版本号提高到1:
1:babababa192df1312
不,我知道这些长度可能不正确。
这样你就可以始终知道要检查哪个版本来验证密码。你可以通过清除以该版本号开头的存储散列来使旧的算法失效。
如果您已经在生产中使用哈希而没有版本号,只需选择一种方案,使得您可以轻松地识别未经版本化的哈希 - 例如,使用上述带有冒号的方案,任何不包含冒号的哈希必定是版本控制之前的,因此可以被推断为版本0(或其他版本号)。
PBKDF2( MD5( password ) )
您的数据库中已经有MD5,所以您需要对其应用PBKDF2。
这种方法之所以有效是因为MD5相对于其他哈希函数(例如SHA-*)的弱点并不影响密码使用。例如,它的碰撞漏洞对数字签名来说是毁灭性的,但对密码哈希没有影响。与更长的哈希值相比,MD5的128位输出会略微减少哈希搜索空间,但相对于密码搜索空间本身而言,这是微不足道的。
使密码哈希强大的因素是通过放慢迭代次数(在PBKDF2中实现)和添加随机、足够长的盐来实现的 - 初始MD5并不会对它们造成负面影响。
顺便说一句,在密码中添加一个版本字段也是一个好主意。
编辑:加密StackExchange上有一个有趣的讨论关于这个方法。
等待用户登录(这样你就可以得到未加密的密码), 然后使用新算法将其进行哈希处理并保存在数据库中。
一种方法是:
这样,渐渐地,你将只有使用新哈希值的密码。
你可以尝试这样做:
首先,在你的成员表或存储密码的任何表中添加一个新列。
ALTER TABLE members ADD is_pass_upgraded tinyint(1) default 0;
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$auth_success = authenticateUser($username, $password);
if (!$auth_success) {
/**
* They entered the wrong username/password. Redirect them back
* to the login page.
*/
} else {
/**
* Check to see if the member's password has been upgraded yet
*/
$username = mysql_real_escape_string($username);
$sql = "SELECT id FROM members WHERE username = '$username' AND is_pass_upgraded = 0 LIMIT 1";
$results = mysql_query($sql);
/**
* Getting any results from the query means their password hasn't been
* upgraded yet. We will upgrade it now.
*/
if (mysql_num_rows($results) > 0) {
/**
* Generate a new password hash using your new algorithm. That's
* what the generateNewPasswordHash() function does.
*/
$password = generateNewPasswordHash($password);
$password = mysql_real_escape_string($password);
/**
* Now that we have a new password hash, we'll update the member table
* with the new password hash, and change the is_pass_upgraded flag.
*/
$sql = "UPDATE members SET password = '$password', is_pass_upgraded = 1 WHERE username = '$username' LIMIT 1";
mysql_query($sql);
}
}
您的authenticateUser()函数需要更改为类似于以下内容:
<?php
function authenticateUser($username, $password)
{
$username = mysql_real_escape_string($username);
/**
* We need password hashes using your old system (md5 for example)
* and your new system.
*/
$old_password_hashed = md5($password);
$new_password_hashed = generateBetterPasswordHash($password);
$old_password_hashed = mysql_real_escape_string($old_password_hashed);
$new_password_hashed = mysql_real_escape_string($new_password_hashed);
$sql = "SELECT *
FROM members
WHERE username = '$username'
AND
(
(is_pass_upgraded = 0 AND password = '$old_password_hashed')
OR
(is_pass_upgraded = 1 AND password = '$new_password_hashed')
)
LIMIT 1";
$results = mysql_query($sql);
if (mysql_num_rows($results) > 0) {
$row = mysql_fetch_assoc($results);
startUserSession($row);
return true;
} else {
return false;
}
}
这种方法有优点和缺点。优点是,个人成员登录后,其密码变得更加安全。缺点是,每个人的密码都没有得到保护。
我只会这样做大约两周时间。我会给所有成员发送电子邮件,并告诉他们由于网站升级,他们有2周时间登录他们的帐户。如果他们在2周内未能登录,则需要使用密码恢复系统重置密码。
当他们下一次进行身份验证时,只需重新散列原始文本。同时使用基于256的盐(完整字节)和大小为256字节的SHA-256。