我们的开发团队已获悉,由于早期版本的 Spring Security 中存在授权绕过漏洞,在我们从 Spring Security 4.x 升级到 Spring Security 5 之前,我们的应用程序将不再部署。我正在努力处理我们的身份验证代码,它使用 Spring Security 4 SHAPasswordEncoder,它已从 Spring Security 5 中删除。我有一个简单的应用程序,它将文本字符串编码为 SHA-1 编码字符串,并且我“成功”将该应用程序的代码迁移到 Spring Security 5,但在编码时我得到了一个非常不同的字符串。
旧应用程序:
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
/**
* Small java utility to take a single string and generate an SHA encoded string from
* the original string, for use as an EIS local password
*
* Usage:
* PwGen <string_to_encode>
*
*/
public class PwGen {
public static void main(String[] args) {
PwGen pwGen = new PwGen();
pwGen.generatePassword(args);
}
private void generatePassword(final String[] args) {
if (args.length != 1) {
usage();
return;
}
System.out.println("Generating password");
ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder();
String encodedPassword = passwordEncoder.encodePassword((String)args[0], "");
System.out.println(encodedPassword);
}
private void usage() {
System.out.println("Usage: pwGen <string_to_encode>");
System.out.println("Application will return an encoded string for use as a password");
}
}
新应用:
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Small java utility to take a single string and generate an SHA encoded string from
* the original string, for use as an EIS local password
*
* Usage:
* PwGen <string_to_encode>
*
*/
public class PwGen {
public static void main(String[] args) {
PwGen pwGen = new PwGen();
pwGen.generatePassword(args);
}
private void generatePassword(final String[] args) {
if (args.length != 1) {
usage();
return;
}
System.out.println("Generating password");
PasswordEncoder passwordEncoder = new MessageDigestPasswordEncoder("SHA-1");
String encodedPassword = passwordEncoder.encode(args[0]);
System.out.println(encodedPassword);
}
private void usage() {
System.out.println("Usage: pwGen <string_to_encode>");
System.out.println("Application will return an encoded string for use as a password");
}
}
使用旧应用程序,对字符串“password”进行编码生成以下编码字符串:5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
这是一个很容易预测的解决方案,因为我们的数据库包含完全相同的字符串。
每次运行新应用程序都会生成不同的字符串,因此我无法再为用户生成特定的密码。我非常清楚这整个过程并不安全,但应用程序已经过时并且在不久的将来会完全重写,因此管理层对资助中间重写并不感到兴奋。我如何保证 MessageDigestPasswordEncoder 带回与实际编码密码相同的字符串?
免责声明,下面写的代码是:
不应在任何应用程序中使用。
使用
SHA-1
存储密码不安全。 2005 年发现了已知的攻击,2011 年 NIST 正式弃用了 SHA-1,谷歌在 2017 年提出了第一次碰撞。请不要使用 SHA-1!shattered.ioGoogle 发现了有史以来第一次 SHA-1 碰撞
现在宣布免责声明时。
如果您查看实现,您会发现代码将生成一个随机盐,并将其添加到字符串中。这很可能是您每次都获得随机字符串的原因。
@Override
public String encode(CharSequence rawPassword) {
String salt = PREFIX + this.saltGenerator.generateKey() + SUFFIX;
return digest(salt, rawPassword);
}
有两种方法可以解决它。
您可以像文档建议的那样提供一个空字符串作为盐。
If the salt is an empty String, then only use "{}" like this:
"{}" + digest(password + "{}")
这将确定您正在传递一个空字符串作为盐。所以你的代码会是。
PasswordEncoder passwordEncoder = new MessageDigestPasswordEncoder("SHA-1");
String encodedPassword = passwordEncoder.encode(args[0] + "{}");
System.out.println(encodedPassword);
或者我们可以覆盖
MessageDigestPasswordEncoder#encode
函数并绕过盐生成。
public class SuperInsecureMessageDigestPasswordEncoder extends MessageDigestPasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return digest("", rawPassword);
}
}
这将消除盐的产生。