java - 如何在 spring boot 中获得可重现的 Pbkdf2PasswordEncoder 输出?

标签 java spring-boot spring-security cryptography pbkdf2

当多次运行 spring security Pb​​kdf2PasswordEncoder 实例的 encode 方法时,该方法对相同的输入返回不同的结果。片段

String salt = "salt";
int iterations = 100000;
int hashWidth = 128;
    
String clearTextPassword = "secret_password";
    
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder(salt, iterations, hashWidth);
String derivedKey = pbkdf2PasswordEncoder.encode(clearTextPassword);
System.out.println("derivedKey: " + derivedKey);
    
String derivedKey2 = pbkdf2PasswordEncoder.encode(clearTextPassword);
System.out.println("derivedKey2: " + derivedKey2);

结果如下

derivedKey: b6eb7098ee52cbc4c99c4316be0343873575ed4fa4445144
derivedKey2: 2bef620cc0392f9a5064c0d07d182ca826b6c2b83ac648dc

两种推导的预期输出都是相同的值。此外,再次运行该应用程序时,输出将再次不同。对于具有相同输入的两个不同 Pbkdf2PasswordEncoder 实例,也会出现不同的输出行为。 encoding 方法的行为更像是一个随机数生成器。使用的spring boot版本是2.6.1,spring-security-core版本是5.6.0。

是否有任何明显的设置是我遗漏的? documentation没有给出额外的提示。是不是spring boot项目设置有概念性错误?

最佳答案

Is there any obvious setting that I am missing?

是的。您链接到的文档非常清楚,我想您错过了。您传递给 Pbkdf2PasswordEncoder 构造函数的字符串不是盐!

编码器会为您生成盐,每次您要求它编码时都会生成盐,这就是您应该做的事情1 . (返回的字符串在单个字符串中同时包含此随机生成的盐以及应用编码的结果)。因为每次调用 .encode 时都会生成新的盐,所以每次调用 .encode 时都会返回不同的值,即使您使用相同的输入调用它也是如此.

您传入的字符串只是“另一个 secret ”——有时这很有用(例如,如果您可以将此 secret 存储在安全区域中,或者它由另一个系统发送/在启动时输入并且从不存储在磁盘,那么如果有人跑掉了你的服务器,他们将无法检查密码。PBKDF 意味着如果他们确实有 secret ,检查将非常缓慢,但如果他们没有,他们就不能“甚至开始)。

这似乎是一个可靠的计划 - 否则人们就会开始做傻事。例如使用字符串 "salt" 作为所有编码的盐:)

真正的问题是:

The expected output would be the same values for both derivations

没有。你的期望破灭了。无论您编写的任何代码都做出了这个假设,都需要扔掉。例如,这就是您打算如何使用编码器:

  • 当用户创建新密码时,您使用 .encode 并将此方法返回的内容存储在数据库中。

  • 当用户登录时,您获取他们输入的内容,然后从数据库中获取字符串(.encode 发送给您的字符串)并调用 .matches.

听起来您想再次运行 .encode 并查看它是否匹配。不是您应该如何使用此代码。


脚注 1:原因

您还需要检查您的安全策略。你头脑中关于这些东西如何工作的想法被彻底打破了。想象一下它按您想要的方式工作,并且所有密码编码都使用一种盐。然后,如果你把你的数据库转储给我,我可以在大约 10 分钟内轻松破解大约 5% 的帐户!!

如何?好吧,我对所有散列字符串进行排序,然后计算出现次数。里面会有一堆重复的字符串。然后,我可以获取密码位于最常见哈希值前 10 名的所有用户,然后以他们的身份登录。因为他们的密码是iloveyou, welcome123, princess, dragon, 12345678, alexsawesomeservice! 等等 - 非常常用的密码。我怎么知道那是他们的密码?因为他们的密码与您系统上许多其他用户的密码相同。

此外,如果常用密码均无效,我可以判断这些很可能是同一用户的不同帐户。

这些都是我绝对不应该从原始数据中推导出来的东西。解决方案自然是为所有内容设置唯一的盐,然后将盐与哈希值一起存储在数据库中,以便在用户尝试登录时可以“重建”。这些工具试图让您的生活变得轻松通过为您完成工作。这是一个好主意,因为安全实现中的错误(例如忘记加盐,或对所有用户使用相同的盐)不可(轻松)进行单元测试,因此善意的开发人员编写代码,它似乎有效,随便看一眼密码散列似乎表明“它正在工作”(散列在肉眼看来足够随机),然后它被部署,安全问题等等。

关于java - 如何在 spring boot 中获得可重现的 Pbkdf2PasswordEncoder 输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70437963/

相关文章:

java - 我应该在 Mapbox 的 InfoWindow 方法中返回什么 View ?

java - 动态加载预编译类,无需反射

java - 绑定(bind)数据成员的 Hql 查询

spring-security - 使用多个数据库时,Grails 2 无法使用 spring security 登录

spring-boot - Spring security如何允许root url而不干扰安全 'below' root

java - 迁移到 Spring Security 4 时出现访问被拒绝页面

java - Java 中的代理具有动态接口(interface)

java - Spring Boot + AWS Linux + Oracle 数据库

java - Thymeleaf + Spring boot 如何避免使用 GET 请求创建实例

java - Spring Boot Swagger API 不工作