通过散列令牌查找用户

Find an user by their hashed token

我想实现一个基于令牌的身份验证系统,该系统对内部攻击具有很强的抵抗力。也就是说,对代码和数据库具有只读访问权限的人不应该能够使用其他人的令牌。这意味着 JWT 不是可行的方法,因为如果有人知道令牌签名的密钥(以纯文本形式存储在服务器上),他们就可以破坏每个人的令牌(前提是他们知道用户的有效负载).此外,我想要比 OpenID/OAuth.

更容易实现的东西

因此,我考虑将每个用户的访问令牌存储在数据库中。他们每个人都有 1 天的生存时间。但是如果你知道用户的令牌,1 天就足以用它做一些令人讨厌的事情,比如删除帐户。因此,我想添加另一个安全层并对数据库中的令牌进行哈希处理。

问题是,PHP 的 password_hash() 函数使用盐,所以它不是确定性的。这意味着我不能那样做:

// Insert a new user
$token = generateToken(size=32);
$tokenHash = password_hash($token, PASSWORD_DEFAULT);
$sql = "
INSERT INTO `User` (username, password_hash, token_hash)
VALUES (:username, :password_hash, :token_hash)
";
$stmt->prepare($sql);
$stmt->execute([
  ":username" => $username,
  ":password_hash" => $paswordHash
  ":token_hash" => $tokenHash
]);

// Find an user with their token
$tokenHash = password_hash($token, PASSWORD_DEFAULT);
$sql = "SELECT * FROM `User` WHERE token_hash = :token_hash";
$stmt = $db->prepare($sql);
$stmt->execute([":token_hash" => $tokenHash]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

事实上,$tokenHash 实际上永远不会等于数据库中存储的值,进行此比较的正确函数是 password_verify()。但是检索数据库的所有用户并为每个用户使用 password_verify() 效率太低了。

在他的列中使用散列值查找用户(或更一般地说是数据库记录)的正确方法是什么?

感谢您的帮助。

我看到 2 个解决方案:

1.使用没有 salt

的哈希函数

使用PHP hash() 函数,结果将始终相同,因为没有随机生成的盐。这样您就可以像这样比较 2 个令牌哈希值:

// Insert a new user
$token = generateToken(size=32);
$tokenHash = hash("sha256", $token); // Use hash() instead of password_hash()
$sql = "
INSERT INTO `User` (username, password_hash, token_hash)
VALUES (:username, :password_hash, :token_hash)
";
$stmt->prepare($sql);
$stmt->execute([
  ":username" => $username,
  ":password_hash" => $paswordHash
  ":token_hash" => $tokenHash
]);

// Find an user with their token
$tokenHash = hash("sha256", $token); // Same here
$sql = "SELECT * FROM `User` WHERE token_hash = :token_hash";
$stmt = $db->prepare($sql);
$stmt->execute([":token_hash" => $tokenHash]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

2。在 SQL

中创建您自己的 password_verify()

你可以在数据库中编写自己的bcrypt验证函数,在第二次查询中使用:

// Find an user with their token
$sql = "SELECT * FROM `User` WHERE password_verify(:token, token_hash)";
$stmt = $db->prepare($sql);
$stmt->execute([":token" => $token]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

如您所知,不能使用盐来创建可搜索的哈希值。不过,使用像 SHA-256 这样的快速未加盐算法进行散列是非常好的,只要你的令牌非常强大。这意味着令牌应包含至少 24 个字母表中的随机字符 0..9, a..z, A..Z.

加盐和密钥拉伸是针对弱用户密码的措施,可防止字典攻击并使暴力攻击变得不切实际。可以在没有这种预防措施的情况下散列强随机标记。

我写了一些example code如何生成这样的令牌。