【问题标题】:Can someone explain how BCrypt verifies a hash?有人可以解释 BCrypt 如何验证哈希吗?
【发布时间】:2011-07-20 14:40:03
【问题描述】:

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

这是一个正确的假设吗?

【问题讨论】:

    标签: c# bcrypt bcrypt.net


    【解决方案1】:

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

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

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

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

    或者,在此主题上对您的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编码的hash。

    【讨论】:

      【解决方案2】:

      BCrypt 哈希 字符串 看起来像:

      $2a$10$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm
      \__/\/ \____________________/\_____________________________/
       |   |        Salt                     Hash
       |  Cost
      Version
      

      在哪里

      • 2a:算法标识符(BCrypt,UTF8 编码密码,空终止)
      • 10:成本因素(210 = 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: 210 = 1,024 次迭代
        • 11: 211 = 2,048 次迭代
        • 12: 212 = 4,096 次迭代
      • OpenBSD 密码文件使用的 base64 算法与其他人使用的 Base64 编码不同;他们有自己的:

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

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


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

      $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 位设置的字符。

      他们建议系统管理员更新他们现有的密码数据库,将$2a$ 替换为$2x$,以表明这些哈希值不正确(并且需要使用旧的损坏算法)。他们还提出了让 crypt_blowfish 为固定算法生成的哈希值发出 $2y$ 的想法。没有其他人,包括规范的 OpenBSD,采用了2x/2y 的想法。 This version marker was was limited to crypt_blowfish?.

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

      $2b$ (2014 年 2 月)

      在 BCrypt 的 OpenBSD 实现中发现了一个错误。他们用一种不支持字符串的语言编写了他们的实现——所以他们用一个长度前缀、一个指向字符的指针来伪造它,然后用[]对该指针进行索引。不幸的是,他们将字符串的长度存储在unsigned char 中。如果密码超过 255 个字符,它将溢出并在 255 处换行。 BCrypt 是为 OpenBSD 创建的。当他们在 他们的 库中出现错误时,他们决定升级版本是可以的。这意味着如果您想保持最新的“他们的”规范,其他所有人都需要效仿。


      2a2x2y2b之间没有区别。如果你正确地编写了你的​​实现,它们都会输出相同的结果。

      • 如果您从一开始就做正确的事情(将字符串存储在 utf8 中并同时散列空终止符),那么:22a 之间没有区别, 2x2y2b。如果你正确地编写了你的​​实现,它们都会输出相同的结果。
      • $2b$ 版本并不比 $2a$“更好”或“更强”。它是 BCrypt 的一个特殊错误实现的残余。但由于 BCrypt 规范地属于 OpenBSD,他们可以将版本标记更改为他们想要的任何内容。
      • $2x$$2y$ 版本并不比任何东西更好,甚至更可取。它们是有缺陷的实现的残余 - 应该立即被遗忘。

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

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

      【讨论】:

      • 作弊者:TwentyTwoCharacter vs ThirtyOneCharacters(复数)。幸运的是,Salt 以 s 开头,因此您可以争辩它在两个词之间共享。不过还是很厉害……
      • 很好的答案@Ian Boyd,一个问题,成本是指您想要迭代生成随机值的次数?我的意思是,如果成本为 10,它将执行 1,024 次迭代,直到生成 hast?
      • @Skizo-ozᴉʞS 从技术上讲,它花费了 2^n 次迭代来生成加密 "key"。然后使用该密钥对文本“OrpheanBeholderScryDoubt”进行 64 次加密。但是,是的,您可以认为它在生成哈希之前必须运行多少次迭代。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-29
      • 2010-12-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多