使用散列密码验证用户的流程?

Flow of validating a user with hashed password?

当我读到这部分时,我正在阅读这篇关于散列密码的文章article

验证密码

  1. 从数据库中检索用户的 salt 和 hash。
  2. 将盐添加到给定的密码并使用相同的方法对其进行哈希处理 哈希函数。
  3. 将给定密码的哈希值与来自 数据库。如果匹配,则密码正确。否则, 密码不正确。

但我对接下来的流程有点困惑,例如,假设我有一个数据库,其中有一个用户 table,具有 ID、名称、密码和电子邮件,以便登录某个应用程序我需要输入我的邮箱和密码。

按照上述步骤,我首先需要获取存储在数据库中的该用户的salt+hashed密码。

问题:

假设我使用的是一个简单的存储过程,那么唯一的方法就是这样做...

    CREATE PROCEDURE [dbo].[sp_validate_user]

@us_email VARCHAR (MAX)
AS
BEGIN
 -- SET NOCOUNT ON added to prevent extra result sets from
 -- interfering with SELECT statements.
 SET NOCOUNT ON;

    -- Insert statements for procedure here

SELECT  us_id,
        us_name,
        us_pass,
        us_email


 FROM  Users
 WHERE us_email = @us_email

END

然后进行第二步和第三步:

    public static bool ValidatePassword(string inputPassword, string storedPassword)
    {
        // Extract the parameters from the hash
        char[] delimiter = { ':' };
        string[] split = storedPassword.Split(delimiter);
        int iterations = Int32.Parse(split[ITERATION_INDEX]);
        byte[] salt = Convert.FromBase64String(split[SALT_INDEX]);
        byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]);

        byte[] testHash = PBKDF2(inputPassword, salt, iterations, hash.Length);
        return SlowEquals(hash, testHash);
    }

我担心的是,如果我使用从 table 中提取的数据创建对象,这是否会使内部信息变得易受攻击?

这是否也意味着使用此验证的唯一方法是仅基于 username/email 提取所有用户信息,只是在运行时检查输入密码和散列密码是否匹配,然后让我们说用户访问信息?

如果这听起来令人困惑,我很抱歉,但任何见解都会很棒。

本文涵盖了 ASP.NET (C#) 密码哈希代码,但您似乎想使用数据库?

你有三件事要担心;用户的唯一密钥(用户名),您选择的哈希算法并在密码尝试中添加盐(防止彩虹 table 攻击)。

要验证密码,您应该创建一个 sql 存储过程,该过程接受用户名和密码尝试作为参数。此数据为纯文本,已输入到 Web 表单,传递到 Web 服务器,并将通过存储过程传递到数据库服务器。

存储过程将执行以下操作;

  1. 根据用户名参数与用户名字段的匹配查找用户的数据行 select存储的盐场

  2. 将 (1) 中的盐附加到密码参数并散列结果

  3. 根据用户名参数与用户名字段的匹配查找用户的数据行 以及 (2) 的散列结果和散列密码字段。

  4. 如果没有找到行,则密码哈希值不匹配并且是错误的,因此 return suitable 错误代码

  5. 如果找到一行 return 有用的用户数据,即名字、地址

如果存储过程处理所有这些,那么 Web 服务器永远不需要知道盐是什么或散列算法。散列结果或盐绝不会从数据库服务器传输出去。

看来你可能在倒着想。在传递给散列函数之前,将盐添加到明文密码中。将最终结果存储在数据库中。

通常,salt 是用户名。每个用户都有独特的东西来阻止字典攻击。 (字典攻击通过破解一个密码然后寻找相同密码文本的其他实例来依赖于规模经济。它曾经在非常大的用户数据库(如拥有数百万用户的知名网站)上工作得特别好,但希望这些网站现在使用适当的加盐和密钥派生。

所以对于用户名u,密码p,假设 SHA2 是散列函数。连接 u + p 以获得加盐值,然后散列它。

hashtext = SHA2(u + p)     // in this case, + is concatenate

hashtext 是您存储在数据库中的内容。

对于登录,用户输入他的用户名u2和密码p2:

tryhash = SHA2(u2 + p2)

在数据库中查询匹配 u2 的用户记录,密码哈希文本为 tryhash

假设您有一个 MVC 操作接收 loginViewModel,其中填充了从页面输入的明文电子邮件或用户名以及明文密码:

var loginUser = new User(loginViewModel);
CalcHash(loginUser);

var realUser = users.Find(loginUser.username);
if(realUser.HashPassword == loginUser.HashPassword)
    // success

虽然也可以将散列密码作为第二个参数添加到您的数据访问方法中,即。 users.Find(username, hashPass),通常不会这样做,因为即使密码失败,您也需要访问用户记录,以增加密码失败计数并锁定帐户。

我想你没看错,这是通常的工作流程:

  1. 通过用户名 SELECT password_hash FROM user WHERE email=? 获取密码哈希。
  2. 从 password_hash 中提取盐,或从单独的字段中获取盐。
  3. 用提取的盐计算输入密码的哈希值并比较哈希值。

验证密码不能在单个查询中完成,因为您首先必须提取盐。数据库系统通常不支持适当的散列函数,如 PBKDF2BCryptSCrypt,因此您必须在您的代码中进行验证。除了盐之外,您还必须存储其他参数,如成本因素和算法(面向未来),因此将所有这些参数存储在同一个数据库字段中是个好主意。

salt 应该是至少 20 个字符的随机字符串,因此使用用户名作为 salt 或从其他信息中导出 salt 不安全