密码学
最近我对密码学做了很多研究。它让我发现,我们不仅有盐,还有胡椒(是的,我真的只是刚刚发现了胡椒)。
散列密码
这一切都始于我继承了一个未对密码进行哈希处理的项目。即使作为n00b,我也知道这很愚蠢。
我对哈希有基本的了解,但我所知道的显然已经过时了。
原始解决方案
我最初的哈希和盐是通过以下方式实现的:
- 获取用户密码
- 将其与盐混合
- 散列它使用
MD5
- 将盐和哈希都存储在表中
这效果很好,但我倾向于使用更好的技术
新技术
我想尝试的新方法是使用 PHP 的password_hash和password_verify。
创建了一个快速测试后,我可以看到我可以password_verify
在给定的哈希上返回一个真值。令我困惑的一件事是盐是从哪里来的?
在文档中,我可以看到有一个选项可以在选项数组中指定盐,但从 PHP 7.0 开始不推荐使用。
我的尝试
我创建了一些代码(未经测试)只是为了演示。请参见下面的代码。
<?php
$pepper = "myPepper";
function register($username, $password)
{
// add the pepper
$password .= $pepper;
// hash the password
$hash = password_hash($password, PASSWORD_BCRYPT, ["cost" => 10]);
// insert into table
$query = $mysqli->prepare("INSERT INTO users(username, password) VALUES(?, ?)");
$query->bind_param("ss", $username, $hash);
// check the success
if ($query->execute())
return true;
else
return false;
}
function login($username, $password)
{
// create the query
$query = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$query->bind_param("s", $username);
// check if successful
if ($query->execute())
{
// get password from the database
$query->store_result();
$query->bind_result($hash);
$query->fetch();
// verify the passwords are the same
if (password_verify($password . $pepper, $hash))
return true;
else
return false;
}
}
?>
register
功能
这两个功能都是非常基本的,因为我只是想说明一点。
这个函数需要一些参数(在这种情况下username
,password
并将它们添加到数据库中。
现在,我知道如何将我的胡椒添加到密码中,因为这是一个简单的连接,但是盐是随机生成的,但从未返回,这意味着我不知道这是什么。
login
功能
同样,它的作用非常基本。
由于我以前没有使用password_verify
过,我不完全确定我知道获取用户密码的最佳方法。
使用我的旧登录脚本,它们看起来像这样:
function login($username, $password)
{
// create the query
$query = $mysqli->prepare("SELECT salt FROM users WHERE username = ? AND password = ?");
$pass = md5($password);
$query->bind_param("ss", $username, $password);
// check if successful
if ($query->execute())
{
$query->store_result(); // store result to gain access to num_rows
// verify if password and usernames match
if ($query->num_rows == 1)
return true;
else
return false;
}
}
我只是散列密码并将其作为参数传递给 SQL 查询。
使用 bcrypt 我必须从表中提取密码,然后在另一个查询中使用它。(至少这是我认为我目前必须做的)。
最后是问题部分
免责声明
如果我因缺乏知识和/或对我的知识的解释不佳而冒犯了任何人,我深表歉意。
你的帮助
我渴望知识。所以我不知道的,我想知道。(显然在某种程度上,我喜欢学习计算机、系统和编程等)。
如今,密码散列是必不可少的,正确使用它非常重要,这就是我写这篇论文问题的原因。
问题)
- 我对 bcrypt 的理解正确吗?
- 它真的不储存盐吗?
login
在我的and的基本示例中register
,我是否实现了password_hash
andpassword_verify
?
更新 1
在所有评论之后,我只想发布此更新。
如上所述,我喜欢学习,所以当我遇到这个功能时,我开始感到困惑,因为我不知道发生了什么。
我将发布一些示例,以尝试有效地将我的困惑传达给每个人。
让我们以这个脚本为例:
<?php
// everything below is hard coded for simplicity (would actually be extracted from a database)
$password = "myPassword"; // the password from the database
$salt = "mySalt"; // I have hard coded this for simplicity
$hash = md5($salt . $password);
// check login status
if (md5($salt . $_POST["password"]) == $hash)
return true;
else
return false;
?>
在这个例子中,我了解散列是如何工作的。
我用盐存储密码并对其进行哈希处理。然后我用盐检查发布的密码并对其进行哈希处理。如果两者匹配,则我登录成功,否则登录失败。
现在,让我们看下面的例子。
<?php
// everything below is hard coded for simplicity (would actually be extracted from a database)
$password = "myPassword"; // the password from the database
$hash = password_hash($password, PASSWORD_BCRYPT, ["cost" => 10]); // salt is taken care of
// check login status
// again I am keeping this so simple (it might not work per se but I just want to learn about the functions)
if (password_verify($_POST["password"], $hash))
return true;
else
return false;
?>
混乱
我在跑步时开始感到困惑password_verify
。
我知道默认情况下会处理 salt password_hash
,但在第一个示例中,我知道 salt,因此我可以使用发布的密码执行哈希,以检查它们是否匹配。
那么,为什么password_verify
在我没有给它加盐的情况下成功地验证了发布的密码呢?
当然,盐的设计目的是使每个密码都独一无二,但不知何故password_verify
会成功。我曾经var_dump
转储由它生成的哈希值password_hash
,它会在刷新时发生变化。这确实是混乱的来源。如果"test"
每次刷新的哈希值都可以更改为不同的哈希值,那么如何password_verify
知道发布的密码是否正确?
我知道我可以编写一个函数来验证用户的密码。我真正想知道的是 PHP 如何每次都password_verify
能够验证true
,尽管我的哈希值会随着每次刷新而改变。
注意:我知道我会将密码存储在数据库中,因此刷新不会成为问题,但我这样做是为了尝试了解该功能。
注释
@RiggsFolly 我知道盐已经为我做好了。问题是“验证函数如何知道哈希函数中创建的随机盐以验证为真?”
@RiggsFolly 我知道盐选项已被弃用。如果不是(并且我能够通过它传递盐),我想我会更多地理解这个功能。验证函数在不知道盐的情况下成功验证密码的整个想法实际上让我大吃一惊。
也许我只是愚蠢。
@Alex Howansky 盐是如何归还的?诸如此类的字符串$hash = $2y$10$Vaj4ZonpRJjE6kmfQffvOOeIVW3ZV31JJYVY79GtZ3GtioZKtDwku
没有任何意义,但以某种方式password_verify("test", $hash")
返回true
。
@Machavity 阅读链接后,我可以看到盐位于散列的开头,但是盐怎么会出现在最终散列中?我为我的困惑和明显的愚蠢道歉,但我只是想了解密码散列,以便为将来的使用做好更好的准备。
@Fred -ii- 这些自定义函数对于示例来说很简单(我不想链接整个代码页面)。话虽如此,我当前的用法是在一个名为 的自定义类User
中,其中我有一个private
名为的变量$conn
来存储mysqli
连接。然后我用它$this->conn->prepare("SELECT * FROM ...")
来访问数据库。
这对范围界定不利吗?在自定义类中存储连接的首选方法是什么?