在PHP中更新旧的存储的md5密码以提高安全性
目前,我有一个存储md5密码的数据库,几年前,这被认为比现在更安全一些,并且密码需要更安全。
我在这里阅读了很多关于crypt
, md5
, hash
, bcrypt
等的帖子,并且已经开始考虑使用以下内容来比现在更好地“保护”密码。
我将使用hash("sha512"
和两种盐的组合,第一种盐将存储在诸如.htaccess之类的文件中,并且将为每个用户创建第二种盐。
这里是沿着什么我此刻的测试线的例子:
的.htaccess
SetEnv SITEWIDE_SALT NeZa5Edabex?26Y#j5pr7VASpu$8UheVaREj$yA*59t*A$EdRUqer_prazepreTr
使用example.php
$currentpassword = //get password
$pepper = getenv('SITEWIDE_SALT');
$salt = microtime().ip2long($_SERVER['REMOTE_ADDR']);
$saltpepper = $salt.$pepper;
$password = hash("sha512", md5($currentpassword).$saltpepper);
盐显然需要存储在一个单独的表中,以允许检查未来插入的登录密码,但用户永远不可能看到。 你认为这是一个足够的方法来解决这个问题吗?
好的,我们来看看这里的几点
你有$salt
是不是盐。 它是确定性的(意味着那里根本就没有随机性)。 如果你想要一个salt,可以使用mcrypt_create_iv($size, MCRYPT_DEV_URANDOM)
或其他一些实际的随机熵。 重点是它应该是唯一的和随机的。 请注意,它不需要随机加密安全......绝对最糟糕的是,我会这样做:
function getRandomBytes($length) {
$bytes = '';
for ($i = 0; $i < $length; $i++) {
$bytes .= chr(mt_rand(0, 255));
}
return $bytes;
}
正如@ Anony-Mousse指出的, 不要将一个散列函数的输出提供给另一个散列函数,而无需将原始数据重新附加到另一个函数。 相反,请使用适当的迭代算法,例如PBKDF2,PHPASS或CRYPT_BLOWFISH($ 2a $)。
我的建议是使用crypt
与河豚,因为它是PHP的最佳此时可用:
function createBlowfishHash($password) {
$salt = to64(getRandomBytes(16));
$salt = '$2a$10$' . $salt;
$result = crypt($password, $salt);
}
然后使用这样的方法验证:
function verifyBlowfishHash($password, $hash) {
return $hash == crypt($password, $hash);
}
(注意to64
是这里定义的一个好方法)。 你也可以使用str_replace('+', '.', base64_encode($salt));
...
我还建议你阅读以下两条:
编辑:回答移民问题
好的,所以我意识到我的回答没有解决原始问题的迁移方面。 所以这就是我将如何解决它。
首先,构建一个临时函数,从原始md5散列中创建一个新的blowfish散列,并使用随机salt和前缀,以便稍后检测它:
function migrateMD5Password($md5Hash) {
$salt = to64(getRandomBytes(16));
$salt = '$2a$10$' . $salt;
$hash = crypt($md5Hash, $salt);
return '$md5' . $hash;
}
现在,通过此函数运行所有现有的md5散列并将结果保存到数据库中。 我们将自己的前缀放入,以便我们可以检测到原始密码并添加额外的md5步骤。 所以现在我们都被迁移了。
接下来,创建另一个函数来验证密码,并在必要时使用新的散列更新数据库:
function checkAndMigrateHash($password, $hash) {
if (substr($hash, 0, 4) == '$md5') {
// Migrate!
$hash = substr($hash, 4);
if (!verifyBlowfishHash(md5($password), $hash) {
return false;
}
// valid hash, so let's generate a new one
$newHash = createBlowfishHash($password);
saveUpdatedPasswordHash($newHash);
return true;
} else {
return verifyBlowfishHash($password, $hash);
}
}
这是我建议的几个原因:
md5()
从数据库中散列出来。 回答评论:
盐不需要是随机的 - 我指导你到RFC 2898 - 基于密码的密码学。 即4.1节。 我引用:
如果不需要担心相同密钥(或该密钥的前缀)的多次使用与给定密码支持的基于密码的加密和认证技术之间的交互,那么盐可以随机生成并且不需要检查对于接收盐的一方的特定格式。 它至少应该是8个八位字节(64位)。
另外,
注意。 如果随机数生成器或伪随机生成器不可用,则生成盐(或其随机部分)的确定性替代方法是对密码和要处理的消息M应用基于密码的密钥导出函数。
一个伪随机生成器可用,为什么不使用它?
你的解决方案与bcrypt相同吗? 我无法找到关于bcrypt实际上的很多文档。 - 我假设你已经阅读了bcrypt维基百科文章,并试图更好地解释它。
BCrypt基于Blowfish块密码。 它从密码中获取密钥计划设置算法,并使用它来散列密码。 它的好处是,Blowfish的设置算法设计成非常昂贵(这是使得河豚如此强大的密码的一部分)。 基本过程如下:
通过用预定的静态值初始化阵列来设置18个元素的数组(称为P box,32位大小)和4个2维数组(称为S box,每个数组具有256个8位的条目)。 另外,64位状态被初始化为全0。
传入的密钥是XOred,所有18个P盒按顺序排列(如果密钥过短,则旋转密钥)。
然后使用P框来加密先前已初始化的状态。
步骤3产生的密文用于代替P1和P2(P阵列的前2个元素)。
重复步骤3,并将结果放入P3和P4中。 这一直持续到P17和P18被填充。
这是Blowfish密码的关键推导。 BCrypt将其修改为:
64位状态被初始化为盐的加密版本。
相同
然后P盒用于加密以前初始化的(状态xor的一部分盐)。
相同
相同
然后使用生成的设置将密码加密64次。 这就是BCrypt返回的结果。
重点很简单:这是一个非常昂贵的算法,需要大量的CPU时间。 这是它应该被使用的真正原因。
我希望能够解决问题。
新的更安全的密码存储的实现应该使用bcrypt或者PBKDF2,因为这是目前最好的解决方案。
不要嵌套东西,因为@ Anony-Mousse描述的碰撞导致你没有得到任何真正的安全。
您可能想要执行的操作是实施“过渡例行程序”,在此过程中,您的应用将用户从旧的基于MD5的系统转移到新的更安全的系统上,以便他们登录。当登录请求进入时,请查看用户是否在新的,更安全的系统。 如果是这样,bcrypt / PBKDF2的密码,比较,你很好去。 如果他们不是(首先没有人),请使用基于旧版MD5的系统进行检查。 如果匹配(密码正确),请执行密码的bcrypt / PBKDF2转换(因为您现在拥有它),将其存储在新系统中,并删除旧的MD5记录。 他们下次登录时,他们在新系统中有一个条目,所以你很好走。 一旦你实现了这个功能,所有的用户都登录后,你可以删除这个转换功能,只需要对新系统进行认证。
不要在你的sha512
哈希中嵌套md5
。 然后, md5
碰撞也意味着外部哈希中也存在哈希冲突(因为您正在哈希值相同!)
存储密码的常用方式是使用诸如
<method><separator><salt><separator><hash>
验证密码时,您从此字段中读取<method>
和<salt>
,将它们重新应用于密码,然后检查它是否生成相同的<hash>
。
检查你有可用的crypt
功能。 在现代Linux系统上, crypt
应该能够以一种理智的方式使用sha512
密码哈希:PHP crypt手册。 不要重新发明轮子 ,除非你是加密散列方面的专家,否则你可能会比md5
糟糕得多。 它甚至会照顾上面的方案:Linux标准是使用$
作为分隔符, $6$
是sha512
的方法ID,而$2a$
表示您想使用blowfish
。 所以你甚至可以在你的数据库中使用多个哈希。 md5
散列的前缀为$1$<salt>$
(除非您重新创建了md5散列,那么您的散列可能不兼容)。
严重的是,重用现有的crypt
函数。 它由专家进行了很好的检查,可扩展并在许多应用程序中兼容。
上一篇: Update old stored md5 passwords in PHP to increase security
下一篇: Crypt for password hashing. Blowfish produces weird output