为密码创建散列+盐时,有没有办法避免将盐保存为数据库中的单独字段

When creating hash + salt for password is there a way to avoid saving the salt as a separate field in the database

我正在使用以下代码为用户密码生成哈希和盐:

        byte[] salt = new byte[128 / 8];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(salt);
        }

        string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
            password: password,
            salt: salt,
            prf: KeyDerivationPrf.HMACSHA1,
            iterationCount: 10000,
            numBytesRequested: 256 / 8));
    }

user.passwordHash = hashed;
user.passwordSalt = System.Text.Encoding.Default.GetString(salt);

然后为了验证我正在做以下事情:

string hash = Convert.ToBase64String(KeyDerivation.Pbkdf2(
    password: passwordToValidate,
    salt: Encoding.UTF8.GetBytes(passwordSalt),
    prf: KeyDerivationPrf.HMACSHA1,
    iterationCount: 10000,
    numBytesRequested: 256 / 8));

return passwordHash == hash;

我从 this section of the asp.net core documentation 那里得到了这个代码。

  1. 盐通常是按照我的方式存储在自己的字段中,还是有办法将其全部存储在一个字段中,然后在验证时将其分开?
  2. 我是否在保存到数据库时正确地将 salt 字节数组转换为字符串并在验证时正确地转换回字节数组?

1) Salt 通常存储在其自己的字段中,但这不是必需的 - 如果需要,您可以将密码和 salt 一起存储在一个字段中。例如,您的代码盐是固定大小(16 字节),您的散列也是固定大小(32 字节)。因此,您可以将它们一起存储在一个 48 字节的列中,并且始终可以毫无歧义地拆分它们。当然,您应该始终记住先发生的事情:salt 或 key。如果您将它们作为 base-64 存储在字符串列中,就像您现在所做的那样,您可以添加一个分隔符(不是必需的)——一些不能出现在 base64 中的字符,例如分号。然后你可以像"password_hash:salt"一样存储。不过,我建议存储在二进制列中。

2) 你转换错了。您将其视为 UTF-16 编码的字符串,但事实并非如此 - salt 是随机的字节集,而不是任何编码的字符串。所以使用 base-64 就像你已经为散列所做的那样(如果你坚持将其存储为字符串):

user.passwordSalt = Convert.ToBase64String(salt);