为啥来自 Spring 的 BCryptPasswordEncoder 为相同的输入生成不同的输出?

Posted

技术标签:

【中文标题】为啥来自 Spring 的 BCryptPasswordEncoder 为相同的输入生成不同的输出?【英文标题】:Why BCryptPasswordEncoder from Spring generate different outputs for same input?为什么来自 Spring 的 BCryptPasswordEncoder 为相同的输入生成不同的输出? 【发布时间】:2014-11-08 18:22:25 【问题描述】:

我正在使用带有 Spring 安全性的 BCryptPasswordEncoder。我的期望是,对于相同的输入,我总是会得到相同的输出。但是对于相同的输入,我得到不同的输出。您可以使用下面的代码 sn-p 对其进行测试:

String password = "123456"; 
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 
String encodedPassword = passwordEncoder.encode(password);
System.out.print(encodedPassword);

输出:$2a$10$cYLM.qoXpeAzcZhJ3oXRLu9Slkb61LHyWW5qJ4QKvHEMhaxZ5qCPi

输出2:$2a$10$KEvYX9yjj0f1X3Wl8S.KPuWzSWGyGM9ubI71NOm3ZNbJcwWN6agvW

输出 3:$2a$10$nCmrPtUaOLn5EI73VZ4Ouu1TmkSWDUxxD4N6A.8hPBWg43Vl.RLDC

有人能解释一下,为什么 BCryptPasswordEncoder 会这样吗?

【问题讨论】:

为什么需要密码具有相同的哈希值? 这个问题的最佳答案在这里:How can bcrypt have built-in salts? 【参考方案1】:
public static void main(String[] args) 
  // spring 4.0.0
  org.springframework.security.crypto.password.PasswordEncoder encoder
   = new org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder();

   // $2a$10$lB6/PKg2/JC4XgdMDXyjs.dLC9jFNAuuNbFkL9udcXe/EBjxSyqxW
   // true
   // $2a$10$KbQiHKTa1WIsQFTQWQKCiujoTJJB7MCMSaSgG/imVkKRicMPwgN5i
   // true
   // $2a$10$5WfW4uxVb4SIdzcTJI9U7eU4ZwaocrvP.2CKkWJkBDKz1dmCh50J2
   // true
   // $2a$10$0wR/6uaPxU7kGyUIsx/JS.krbAA9429fwsuCyTlEFJG54HgdR10nK
   // true
   // $2a$10$gfmnyiTlf8MDmwG7oqKJG.W8rrag8jt6dNW.31ukgr0.quwGujUuO
   // true

    for (int i = 0; i < 5; i++) 
      // "123456" - plain text - user input from user interface
      String passwd = encoder.encode("123456");

      // passwd - password from database
      System.out.println(passwd); // print hash

      // true for all 5 iteration
      System.out.println(encoder.matches("123456", passwd));
    

【讨论】:

使用您的代码制作了一个简单的工具,可能会为某些人节省时间:github.com/raags/SpringPasswordEncoder 这个例子中的名字passwd 代表encodedPassword(哈希码),而不是rawPassword,这有点违反直觉。 问题是“有人能解释为什么 BCryptPasswordEncoder 会这样吗?” - 这个答案没有explain,只提供了没有explanation一个字的代码。【参考方案2】:

生成的密码是加盐的,因此不同。

请阅读encode() 方法的文档,其中明确说明密码已加盐。

【讨论】:

你能给我一个相同密码的例子吗? @Bhavesh 你想达到什么目的?为什么密码需要相同的哈希值? 我想使用 bcrpt 使用 spring security 执行简单的登录和注册操作。 @Bhavesh 为此存在方法matches()[docs.spring.io/spring-security/site/docs/3.2.5.RELEASE/apidocs/…。请阅读您要使用的类的文档。 你的措辞让我有点吃惊:编码器不生成密码,它生成的代码包括盐 + 散列密码 + 其他细节(见其他答案)。另外:文档(在撰写本文时)是从界面复制而来的,仅说明好的编码器使用盐。【参考方案3】:

第 3 个 $ 之后的 22 个字符表示盐值,请参见 https://en.wikipedia.org/wiki/Bcrypt#Description 。 “盐”是在哈希之前添加到密码中的一些随机数据,因此具有给定参数的给定哈希算法在大多数情况下会为相同的密码产生不同的哈希值(防止所谓的彩虹攻击)。

让我们剖析原始问题中显示的第一个输出: $2a$10$cYLM.qoXpeAzcZhJ3oXRLu9Slkb61LHyWW5qJ4QKvHEMhaxZ5qCPi

$2a : BCrypt 算法的标识符 $10 : 轮数参数,这里是 2^10 轮 cYLM.qoXpeAzcZhJ3oXRLu:盐(128 位) 9Slkb61LHyWW5qJ4QKvHEMhaxZ5qCPi:实际哈希值(184 位)

盐和哈希值都使用 Radix-64 编码。

【讨论】:

【参考方案4】:

这是完全正常的,因为BCryptPasswordEncoder 使用盐来生成密码。您可以阅读“加盐”密码here 和here 背后的想法。

这是encode 方法的文档所说的

对原始密码进行编码。通常,一个好的编码算法会应用 SHA-1 或更大的哈希值以及 8 字节或更大的随机生成的 salt。

【讨论】:

【参考方案5】:

BCrypt 输出为: $2a$10$cYLM.qoXpeAzcZhJ3oXRLu9Slkb61LHyWW5qJ4QKvHEMhaxZ5qCPi

$2a$ 表示哈希算法

10$ 是对数轮次

下面是盐和哈希密码

由于 Spring 每次都会生成不同的盐,所以你的输出是不一样的。 BCrypt 语法可以参考https://en.wikipedia.org/wiki/Bcrypt#Description

【讨论】:

以上是关于为啥来自 Spring 的 BCryptPasswordEncoder 为相同的输入生成不同的输出?的主要内容,如果未能解决你的问题,请参考以下文章

dubbo为啥用到了zookeeper

为啥有时会跳过 maven 依赖项中的版本号?

为啥不支持来自其他设备的表情符号?

为啥 WCF 服务能够处理来自不同进程的调用而不是来自线程的调用

为啥 Spring RestTemplate 在 Spring 中默认不是 Bean?

为啥来自 NSString 分配的 NSDecimalNumber 会失败?