PHP PDO如何在注册时对密码进行哈希处理,然后在登录时“取消哈希”
Posted
技术标签:
【中文标题】PHP PDO如何在注册时对密码进行哈希处理,然后在登录时“取消哈希”【英文标题】:PHP PDO how to hash password upon registration and then 'unhash' during login 【发布时间】:2018-05-18 17:27:24 【问题描述】:我正在完成一个应用程序。我在脚本中进行了相当繁重的验证,但我想通过在将密码发送到数据库时对密码进行哈希处理来加强它,因为它不会以纯文本形式输入。我已经尝试了几种方法,但到目前为止都没有成功。
这里是代码(托管在 GitHub 的 Gist 程序上):
https://gist.github.com/anonymous/e5c4f6ab9443d9ce887266b79bc36a1a
正如您在脚本的密码验证部分中看到的那样,如果密码通过了所有其他验证,那么它就会被散列。但是当我检查我的数据库时它似乎不起作用。那是问题#1。最重要的是,我在上面链接中包含的 login.php 文件中找不到任何可以取消哈希密码的方法,以及如何在我的 login.php 文件中实现“取消哈希”技术。感谢你们提供的任何帮助或提示。非常感谢!
【问题讨论】:
你应该创建a minimal working example,而不仅仅是发布到 github gist 的链接。 我很抱歉,我觉得看到整个脚本会帮助人们更好地理解我想要实现的目标......我的错。 试着参与说明你想要达到的目标的部分。 哈希并不意味着“未哈希”;哈希函数是单向函数。哈希的目的是将它们与另一个哈希进行比较。在password_hash()
的情况下,它的补充功能是password_verify()
。阅读***上的cryptographic hash functions。
PS.:我忘了提一下,在散列的上下文中了解什么是盐也很重要,因为这将清楚为什么简单地比较来自 password_hash()
的散列密码, 不起作用:password_hash()
为每个密码创建一个唯一的盐(在其结果之前),password_verify()
再次使用这个盐来比较哈希值。今天晚些时候我可能会为你写一个更详细的答案。
【参考方案1】:
关于(加密)散列的一般信息
散列经常与加密混淆,但它们是不同的并且有不同的用例:
Encryption 用于对某些内容进行编码,以便可以再次破译(使用正确的密钥),使其成为双向过程。
另一方面,(Cryptographic) hashing 是一个单向 过程,用于对明确不 意味着可破译的内容进行编码。此外,散列函数是确定性的,这意味着给定相同的消息,它总是会产生相同的摘要。1
所以,这就是为什么要使用加密哈希来编码密码: 出于希望显而易见的安全原因,您不希望任何人能够破译散列密码(不是某些能够破坏您的数据库的对手,甚至是您,网络管理员,因为这将允许您破坏用户的帐户其他地方,如果他们在那里使用相同的密码),但您确实希望能够验证用户输入的密码与他们注册时使用的密码是否匹配。
由于如前所述,哈希函数是确定性的,我们可以通过比较身份验证过程(例如登录)产生的哈希和注册过程产生的哈希来实现这一点。
“但是等等……”我听到你在想,“在你的评论中你告诉我我无法简单地比较它们”。
如果提供给裸散列函数的消息是相同的,您可以,这就是散列函数的确定性方面的问题,在密码散列的上下文中:它们产生相同的输出。
这意味着如果 Alice、Bob 和 Charlie 使用相同的密码,他们都会产生相同的摘要:
id | username | pwdhash
----+----------+---------
17 | Alice | 9Wdm...
48 | Bob | 9Wdm...
57 | Charlie | 9Wdm...
这带来了两个突出的安全风险:
-
如果您知道 Alice 的密码,那么您也知道其他人的密码;
对手可以在知道您使用的散列函数的情况下,从理论上生成所谓的 rainbow table2 并将其散列与您的散列(如果您的数据库遭到破坏)进行比较,以推断与彩虹表中的密码匹配的实际密码。
因此,为了减轻这些风险,在散列之前向消息(密码)添加所谓的盐被认为是强制性做法。盐的意义在于足够独特,以保证两个相同的密码在输入相同的散列函数时会产生完全不同的摘要。
伪例子:
// produces same digests:
hash( 'password' ) -> bhZPmushb...
hash( 'password' ) -> bhZPmushb...
// produces unique digests:
salt message
hash( 'SeNib6' . 'password' ) -> qDHkfTW9m...
hash( '9pBq7y' . 'password' ) -> XH26YVUnA...
默认情况下,password_hash()
在后台自动为您执行此操作。3 然后,它会将使用过的盐添加到摘要中,以及有关实际算法的信息如果提供的密码与之前的哈希密码匹配,4password_verify()
将使用此附加信息和使用的 salt 再次生成相同的摘要。
因此,必须存储password_hash()
的完整输出,以便可以将其作为第二个参数输入password_verify()
。
那么,这一切对您的案例意味着什么?
由于password_hash()
会自动添加盐并因此为相同的密码生成两个不同的摘要,因此您不能简单地比较password_hash()
的输出。这也意味着你不能(很容易;如果有 some 方法让它工作,我不会感到惊讶)在数据库级别比较密码哈希,就像你想要的那样,password = :password
;您必须在 PHP 中比较哈希,这意味着您必须先获取用户的记录,然后验证密码哈希:
$query = "SELECT * FROM credentials WHERE username = :username";
$statement = $conn->prepare($query);
$statement->execute(array('username' => $_POST["userName"]));
// I believe PDOStatement::rowCount() doesn't work with mysql for SELECT statements
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
$count = count($rows);
if($count > 1) // this might be redundant in your setup
// this is not supposed to happen, usernames should be unique
// log or alert developer
else if($count == 1)
$row = $rows[0];
if(password_verify($_POST["userPass"], $row['password'))
// don't store $_POST["userName"] in $_SESSION["username"], but use the database value
$_SESSION["username"] = $row["username"];
header("Location: user-homepage.php");
// it's good practice to exit after a redirect, unless other code still needs to be executed
exit();
$message = '<label>Invalid Credentials</label>';
1) 散列函数的输出通常称为摘要或简称为散列。
2) 彩虹表是通过给目标散列函数提供大量可能的密码而生成的数据库,即通过蛮力生成一长串摘要以与受损的摘要集进行比较。这通常是一项相当耗时的操作。
3) 我实际上不确定password_hash()
是在密码中添加还是添加盐以生成摘要。
4) 有关format of the output(在有关密码哈希的常见问题中解释)algorithms(在password_hash()
中解释)和cost(在crypt()
中解释)的更多信息,请参阅文档。
【讨论】:
以上是关于PHP PDO如何在注册时对密码进行哈希处理,然后在登录时“取消哈希”的主要内容,如果未能解决你的问题,请参考以下文章
在 PHP 中匹配 128 个字符的密码哈希 - 使用 Ruby on Rails 加密
如何在保存到数据库之前对密码进行哈希处理以与护照模块兼容(本地护照)
使用 dcrypt 对密码进行哈希处理后,使用 MySQL 中的 NodeJs 登录用户