密码设计

1. 格式分析

1.1 KeyCloak

KeyCloak 为了兼容不同系统的密码,所以分两部分保存密码。

加密算法

{
"hashIterations": 27500,
"algorithm": "pbkdf2-sha256",
"additionalParameters": {}
}

密码数据

{
"value": "OqOPgWw74I/A7gDezLrgc83F1qXvcncpzNP6e0kHdiHbxoW5hQjRZvPu0HnoCiRVnVIJNvhla3LhDOEUzeMkhQ==",
"salt": "im++HV5qPCegfHNLeYKu9A==",
"additionalParameters": {}
}

通过加密算法的 Json 文件,可以知道使用了pbkdf2-sha256,循环次数是27500,这里隐含了一个变量keyLength,通过代码可以知道是512

通过密码数据的 Json 文件中保存了一个salt的数值。

1.2 Spring

Spring 是把加密的salt等信息,拼接到密码中。

@Test
public void testPasswordEncoder(){
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder();
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String pbk1 = pbkdf2PasswordEncoder.encode("123456");
String pbk2 = pbkdf2PasswordEncoder.encode("123456");
String bcr1 = bCryptPasswordEncoder.encode("123456");
String bcr2 = bCryptPasswordEncoder.encode("123456");
System.out.println("pbk1: " + pbk1);
System.out.println("pbk2: " + pbk2);
System.out.println("pbk1 password:" + pbkdf2PasswordEncoder.matches("123456",pbk1));
System.out.println("pbk2 password:" + pbkdf2PasswordEncoder.matches("123456",pbk2));
System.out.println("---------------------");
System.out.println("bcr1: " + bcr1);
System.out.println("bcr2: " + bcr2);
System.out.println("bcr1 password:" + bCryptPasswordEncoder.matches("123456",bcr1));
System.out.println("bcr2 password:" + bCryptPasswordEncoder.matches("123456",bcr2));
}

输出

pbk1: cdcdd429011201a57a40409f6c54af620c38bbbd446b7b30cb1927f93f9d0ae6c5fcd61a1fe99cea
pbk2: c459cd76f004bba98499edcfb3037b6e261f1dd8900219f9cb17d5acc93a2c008e505d2dc101a1ef
pbk1 password:true
pbk2 password:true
---------------------
bcr1: $2a$10$tDF0EIVhljZPL5uwRduBwe4PCxoxMSPnDZ/1F/DsouplqvnPIV7ae
bcr2: $2a$10$3f4CPcAHddrFSE.rmhDxF.AjSXOYGZRTThd3D1V.BAq19J5rqDyoe
bcr1 password:true
bcr2 password:true

如果看 Spring 的源代码,会发现使用了EncodingUtils,将salt拼接到加密的内容中了。

private byte[] encode(CharSequence rawPassword, byte[] salt) {
try {
PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(),
EncodingUtils.concatenate(salt, this.secret), this.iterations, this.hashWidth);
SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
return EncodingUtils.concatenate(salt, skf.generateSecret(spec).getEncoded());
}
catch (GeneralSecurityException ex) {
throw new IllegalStateException("Could not create hash", ex);
}
}

解码的时候,将salt解析出来,然后进行匹配

@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
byte[] digested = decode(encodedPassword);
byte[] salt = EncodingUtils.subArray(digested, 0, this.saltGenerator.getKeyLength());
return MessageDigest.isEqual(digested, encode(rawPassword, salt));
}

SpringSecurity 有一个致命的缺点:salt 的长度从开始一定要确定,不然解析不出来。

1.3 密码互换

如果将密码从 Spring 转到 KeyCloak 之间互转,可以吗?

假设 pbkdf2PasswordEncoder 加密,从 Spring->KeyCloak 的转换,可以分为以下几步:

1、读 Spring 的源代码,得到具体的参数:salt 、密钥、循环次数、Key 长度。

2、截取 Spring 的密码,并分解成 keyCloak 的 Json 文件就可以了。

从 KeyCloak 转到 Spring 就相当的复杂了

1、要单独追加一个密钥解析器。

基本上两个系统的密码互换,会是一个非常痛苦的事情。特别是这个系统中,有很多的密码规范。但也不是不可能的。

做个笔记:Spring 的 Pbkdf2PasswordEncoder,做如下定义:

salt_lenght 8
keyLength 256
iterations 185000

keyCloak 的定义如下

salt_lenght 16
keyLength 512
iterations 27500

1.4 如何选择呢?

Spring 与 KeyCloak 的算法,到底应该选择哪个呢? 如果在 CAS 的基础上,可以选择 Spring 的加密方法。Spring 的代码写的相对好一点。 KeyCloak 的代码,写的很乱。

1.5 DelegatingPasswordEncoder

Spring Security 5.0 的 DelegatingPasswordEncoder 详解,为了兼容其他系统的密码。

1.6 PBKDF2WithHmacSHA256 例子

@Test
public void testBase64() throws Exception{
// test加密的内容
String expected="OqOPgWw74I/A7gDezLrgc83F1qXvcncpzNP6e0kHdiHbxoW5hQjRZvPu0HnoCiRVnVIJNvhla3LhDOEUzeMkhQ==";
String rawPassword="test";
String saltStr="im++HV5qPCegfHNLeYKu9A==";
int hashIterations=27500;
int keyLength=512;
//需要将byte[]转成字符串,并且由字符串再转回byte[]
java.util.Base64.Decoder decoder = java.util.Base64.getDecoder();
java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();
byte[] salt=decoder.decode(saltStr);
System.out.println("salt length:"+salt.length);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(
rawPassword.toCharArray(),
salt,
hashIterations
,keyLength
);
SecretKey secretKey = factory.generateSecret(spec);
byte[] hash = secretKey.getEncoded();
//需要将hash的数组转成字符串
String encodedBytes= encoder.encodeToString(hash);
assertEquals("判断是否相同",expected,encodedBytes);
}

IntelliJ IDEA lombok 插件的安装配置和使用

https://www.cnblogs.com/csyzlm/p/11683881.html

https://blog.csdn.net/anumbrella/category_7765386.html