KeyCloak 为了兼容不同系统的密码,所以分两部分保存密码。
加密算法
{"hashIterations": 27500,"algorithm": "pbkdf2-sha256","additionalParameters": {}}
密码数据
{"value": "OqOPgWw74I/A7gDezLrgc83F1qXvcncpzNP6e0kHdiHbxoW5hQjRZvPu0HnoCiRVnVIJNvhla3LhDOEUzeMkhQ==","salt": "im++HV5qPCegfHNLeYKu9A==","additionalParameters": {}}
通过加密算法的 Json 文件,可以知道使用了pbkdf2-sha256
,循环次数是27500
,这里隐含了一个变量keyLength
,通过代码可以知道是512
。
通过密码数据的 Json 文件中保存了一个salt
的数值。
Spring 是把加密的salt
等信息,拼接到密码中。
@Testpublic 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: cdcdd429011201a57a40409f6c54af620c38bbbd446b7b30cb1927f93f9d0ae6c5fcd61a1fe99ceapbk2: c459cd76f004bba98499edcfb3037b6e261f1dd8900219f9cb17d5acc93a2c008e505d2dc101a1efpbk1 password:truepbk2 password:true---------------------bcr1: $2a$10$tDF0EIVhljZPL5uwRduBwe4PCxoxMSPnDZ/1F/DsouplqvnPIV7aebcr2: $2a$10$3f4CPcAHddrFSE.rmhDxF.AjSXOYGZRTThd3D1V.BAq19J5rqDyoebcr1 password:truebcr2 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
解析出来,然后进行匹配
@Overridepublic 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 的长度从开始一定要确定,不然解析不出来。
如果将密码从 Spring 转到 KeyCloak 之间互转,可以吗?
假设 pbkdf2PasswordEncoder 加密,从 Spring->KeyCloak 的转换,可以分为以下几步:
1、读 Spring 的源代码,得到具体的参数:salt 、密钥、循环次数、Key 长度。
2、截取 Spring 的密码,并分解成 keyCloak 的 Json 文件就可以了。
从 KeyCloak 转到 Spring 就相当的复杂了
1、要单独追加一个密钥解析器。
基本上两个系统的密码互换,会是一个非常痛苦的事情。特别是这个系统中,有很多的密码规范。但也不是不可能的。
做个笔记:Spring 的 Pbkdf2PasswordEncoder,做如下定义:
salt_lenght 8keyLength 256iterations 185000
keyCloak 的定义如下
salt_lenght 16keyLength 512iterations 27500
Spring 与 KeyCloak 的算法,到底应该选择哪个呢? 如果在 CAS 的基础上,可以选择 Spring 的加密方法。Spring 的代码写的相对好一点。 KeyCloak 的代码,写的很乱。
Spring Security 5.0 的 DelegatingPasswordEncoder 详解,为了兼容其他系统的密码。
@Testpublic 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 插件的安装配置和使用