有人可以解释BCrypt如何验证散列吗?

时间:2011-03-22 15:41:17

标签: c# bcrypt bcrypt.net

我正在使用C#和BCrypt.Net来哈希我的密码。

例如:

string salt = BCrypt.Net.BCrypt.GenerateSalt(6);
var hashedPassword = BCrypt.Net.BCrypt.HashPassword("password", salt);

//This evaluates to True. How? I'm not telling it the salt anywhere, nor
//is it a member of a BCrypt instance because there IS NO BCRYPT INSTANCE.
Console.WriteLine(BCrypt.Net.BCrypt.Verify("password", hashedPassword));
Console.WriteLine(hashedPassword);

如果没有在任何地方保存盐,BCrypt如何使用哈希验证密码。我唯一的想法是,它以某种方式在哈希的末尾附加盐。

这是正确的假设吗?

2 个答案:

答案 0 :(得分:84)

BCrypt哈希 字符串 如下所示:

$2a$10$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm
$==$==$======================-------------------------------

其中

  • 2a:算法标识符(BCrypt,UTF8编码密码,空终止)
  • 10:成本因素(2 10 = 1,024轮)
  • Ro0CUfOqk6cXEKf3dyaM7O:OpenBSD-Base64编码的盐(22个字符,16个字节)
  • hSCvnwM9s4wIX9JeLapehKK5YdLxKcm:OpenBSD-Base64编码哈希(31个字符,24个字节)
  

编辑:我刚刚注意到这些词语完全符合。我不得不分享:

$2a$10$TwentytwocharactersaltThirtyonecharacterspasswordhash
$==$==$======================-------------------------------

BCrypt 使用16字节盐创建一个24字节的二进制哈希。您可以随意存储二进制哈希和盐;什么都没有说你到base-64将它编码成一个字符串。

BCrypt 是由正在使用OpenBSD的人创建的。 OpenBSD 已经为其密码文件定义了一种格式:

<强> $ [HashAlgorithmIdentifier] $ [AlgorithmSpecificData]

这意味着“bcrypt规范”与OpenBSD密码文件格式密不可分。每当有人创建“bcrypt hash”时,他们总是将其转换为ISO-8859-1格式的字符串:

<强> $ 2a $ [Cost] $ [Base64Salt][Base64Hash]

一些要点:

  • 2a是算法标识符
    • 1:MD5
    • 2:早期的bcrypt,它对编码密码所处的位置感到困惑(过时)
    • 2a:当前的bcrypt,它将密码指定为UTF-8编码
  • 成本是计算哈希时使用的成本因素。 “当前”值为10,表示内部密钥设置经过1,024轮
    • 10:2 10 = 1,024次迭代
    • 11:2 11 = 2,048次迭代
    • 12:2 12 = 4,096次迭代
  • OpenBSD密码文件使用的base64算法与其他人使用的Base64编码不同;他们有自己的:

    Regular Base64 Alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
        BSD Base64 Alphabet: ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
    

    所以bcrypt的任何实现都不能使用任何内置或标准的base64库


有了这些知识,您现在可以针对保存的哈希验证密码correctbatteryhorsestapler

$2a$12$mACnM5lzNigHMaf7O1py1O3vlf6.BA8k8x3IoJ.Tq3IB/2e7g61Km

答案 1 :(得分:26)

  

如果没有将盐保存在任何地方,BCrypt如何用哈希验证密码?

显然它没有做任何这样的事情。盐必须保存在某个地方。

让我们在维基百科上查找密码加密方案。来自http://en.wikipedia.org/wiki/Crypt_(Unix)

  

函数的输出不仅仅是散列:它是一个文本字符串,它也对salt进行编码并标识所使用的散列算法。

或者,关于此主题的previous question答案包含指向source code的链接。 不是要求互联网为您阅读源代码,您可以随时选择自己阅读。这可能会让您的答案更快。源代码的相关部分是:

    StringBuilder rs = new StringBuilder();
    rs.Append("$2");
    if (minor >= 'a') {
        rs.Append(minor);
    }
    rs.Append('$');
    if (rounds < 10) {
        rs.Append('0');
    }
    rs.Append(rounds);
    rs.Append('$');
    rs.Append(EncodeBase64(saltBytes, saltBytes.Length));
    rs.Append(EncodeBase64(hashed,(bf_crypt_ciphertext.Length * 4) - 1));
    return rs.ToString();

显然返回的字符串是版本信息,然后是使用的轮数,接着是编码为base64的salt,接着是编码为base64的哈希值。