关于c#:有人可以解释BCrypt如何验证哈希吗?

Can someone explain how BCrypt verifies a hash?

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

例如:

1
2
3
4
5
6
7
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没有将盐保存在任何地方,那么BCrypt如何使用哈希验证密码。 我唯一的想法是,它以某种方式在哈希的末尾添加了盐。

这是正确的假设吗?


BCrypt哈希字符串如下所示:

好。

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

哪里

好。

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

    Edit: i just noticed these words fit exactly. i had to share:

    Ok.

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

    BCrypt确实使用16字节的salt创建了24字节的二进制哈希。您可以随意存储二进制哈希和盐。没什么说您必须将它以base-64编码为字符串。

    好。

    但是BCrypt是由从事OpenBSD的人创建的。 OpenBSD已经为其密码文件定义了一种格式:

    好。

    $ [HashAlgorithmIdentifier] $ [AlgorithmSpecificData]

    好。

    这意味着" bcrypt规范"与OpenBSD密码文件格式紧密相关。每当有人创建" bcrypt哈希"时,他们总是将其转换为以下格式的ISO-8859-1字符串:

    好。

    $ 2a $ [Cost] $ [Base64Salt][Base64Hash]

    好。

    一些要点:

    好。

  • 2a是算法标识符

  • 1:MD5
  • 2:早期的bcrypt,对使用哪种编码密码(过时)感到困惑
  • 2a:当前的bcrypt,它将密码指定为UTF-8编码
  • 好。

  • 成本是计算哈希值时使用的成本因素。"当前"值为10,表示内部密钥设置经过1,024回合

  • 10:210 = 1,024次迭代
  • 11:211 = 2,048次迭代
  • 12:212 = 4,096次迭代
  • 好。

  • OpenBSD密码文件使用的base64算法与其他所有人使用的Base64编码不同;他们有自己的:

    好。

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

    因此,bcrypt的任何实现都不能使用任何内置或标准的base64库

    好。

  • 好。

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

    好。

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

    BCrypt变体

    bcrypt版本之间存在很多混淆。

    好。

    2美元

    好。

    BCrypt是由OpenBSD人士设计的。它被设计为哈希密码以存储在OpenBSD密码文件中。哈希密码带有前缀以标识所使用的算法。 BCrypt获得了前缀$2$

    好。

    这与其他算法前缀相反:

    好。

  • $1$:MD5
  • $5$:SHA-256
  • $6$:SHA-512
  • 好。

    $ 2a $

    好。

    原始的BCrypt规范未定义如何处理非ASCII字符或如何处理空终止符。规范进行了修改,以指定在对字符串进行哈希处理时:

    好。

  • 字符串必须为UTF-8编码
  • 必须包含空终止符
  • 好。

    $ 2x $,$ 2y $(2011年6月)

    好。

    在BCrypt的PHP实现crypt_blowfish ??中发现了一个错误。设置了第8位的字符处理不当。

    好。

    他们建议系统管理员更新其现有的密码数据库,用$2x$替换$2a$,以指示这些哈希值不好(并且需要使用旧的破解算法)。他们还提出了让crypt_blowfish针对由固定算法生成的散列发出$2y$的想法。包括规范的OpenBSD在内的其他任何人都没有采用2x / 2y的想法。此版本标记仅限于crypt_blowfish ??。

    好。

    版本$ 2x $和$ 2y $不比$ 2a $"更好"或"更强"。它们是BCrypt的一种特殊的错误实现的残余。

    好。

    $ 2b $(2014年2月)

    好。

    在BCrypt的OpenBSD实现中发现了一个错误。他们将字符串的长度存储在unsigned char中。如果密码长度超过255个字符,它将溢出并以255换行。BCrypt是为OpenBSD创建的。当他们的库中有错误时,他们决定可以更改版本。这意味着如果您想保持最新的"他们的"规格,其他所有人都需要效仿。

    好。

  • http://undeadly.org/cgi?action=article&sid=20140224132743?
  • http://marc.info/?l=openbsd-misc&m=139320023202696 ??
  • 好。

    2a,2x,2y和2b之间没有区别。如果正确编写了实现,则它们都将输出相同的结果。

    好。

  • 如果您从一开始就做正确的事情(在utf8中存储字符串,还对空终止符进行哈希处理),则:2、2a,2x,2y和2b之间没有区别。如果正确编写了实现,则它们都将输出相同的结果。
  • 版本$ 2b $不比$ 2a $"更好"或"更强"。它是BCrypt的一种特殊的错误实现的残余。但是由于BCrypt通常属于OpenBSD,因此他们可以将版本标记更改为所需的任何版本。
  • $ 2x $和$ 2y $的版本对任何东西都不是更好,甚至更好。它们是实施错误的工具的残余物-应该立即被遗忘。
  • 好。

    唯一需要关心2x和2y的人就是您在2011年就一直在使用crypt_blowfish的人。并且唯一需要关心2b的人是那些可能在运行OpenBSD的人。

    好。

    所有其他正确的实现都是相同且正确的。

    好。

    好。


    How is BCrypt verifying the password with the hash if it's not saving the salt anywhere?

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

    让我们在Wikipedia上查找密码加密方案。 从http://en.wikipedia.org/wiki/Crypt_(Unix):

    The output of the function is not merely the hash: it is a text string which also encodes the salt and identifies the hash algorithm used.

    或者,对您先前关于此主题的问题的答案包括指向源代码的链接。 源代码的相关部分是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        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的哈希。