PBKDF2与Java中的bouncycastle
我试图安全地存储在数据库中的密码,并为此我select存储使用PBKDF2函数生成的散列。 我想使用弹性城堡库来做到这一点,但我不知道为什么我不能通过使用JCE接口使它工作…问题是生成3种不同的模式:
1.使用sun提供的PBKDF2WithHmacSHA1密钥工厂
2.直接使用充气城堡api
3.通过JCE使用充气城堡
结果有两个不同的值:前两个共同,第三个共同。
这是我的代码:
//Mode 1 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); KeySpec keyspec = new PBEKeySpec("password".toCharArray(), salt, 1000, 128); Key key = factory.generateSecret(keyspec); System.out.println(key.getClass().getName()); System.out.println(Arrays.toString(key.getEncoded())); //Mode 2 PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(); generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(("password").toCharArray()), salt, 1000); KeyParameter params = (KeyParameter)generator.generateDerivedParameters(128); System.out.println(Arrays.toString(params.getKey())); //Mode 3 SecretKeyFactory factorybc = SecretKeyFactory.getInstance("PBEWITHHMACSHA1", "BC"); KeySpec keyspecbc = new PBEKeySpec("password".toCharArray(), salt, 1000, 128); Key keybc = factorybc.generateSecret(keyspecbc); System.out.println(keybc.getClass().getName()); System.out.println(Arrays.toString(keybc.getEncoded())); System.out.println(keybc.getAlgorithm());
我知道PBKDF2是使用HMAC SHA1实现的,所以我select了最后一个方法中的algorithm,我从弹性城堡Java文档中select了“PBEWITHHMACSHA1”。
输出如下:
com.sun.crypto.provider.SunJCE_ae [-53, 29, 113, -110, -25, 76, 115, -127, -64, 74, -63, 102, 75, 81, -21, 74] [-53, 29, 113, -110, -25, 76, 115, -127, -64, 74, -63, 102, 75, 81, -21, 74] org.bouncycastle.jce.provider.JCEPBEKey [14, -47, -87, -16, -117, -31, 91, -121, 90, -68, -82, -31, -27, 5, -93, -67, 30, -34, -64, -40] PBEwithHmacSHA
有任何想法吗?
简而言之,不同的原因在于模式#1和#2中的PBKDF2algorithm使用PKCS#5 v2scheme2(PKCS5S2)进行迭代密钥生成,但模式#3中的“PBEWITHHMACSHA1”的BouncyCastle提供者使用PKCS# 12 v1(PKCS12)algorithm。 这些是完全不同的密钥生成algorithm,所以你会得到不同的结果。
下面将详细解释为什么会出现这种情况,以及为什么会得到不同大小的结果。
首先,在构buildJCE KeySpec时,keyLength参数仅向提供者表示“所需密钥大小”的“首选项”。 从API文档 :
注意:这用于指示可变密钥大小密码的密钥长度的优先级。 实际的密钥大小取决于每个提供者的实现。
Bouncy Castle提供者似乎并不尊重这个参数,从JCEPBEKey的来源来看 ,所以当使用JCE API时,您应该期望从任何使用SHA-1的BC提供者获得一个160位的密钥。
您可以通过以编程方式访问testing代码中返回的keybc
variables的getKeySize()
方法来确认:
Key keybc = factorybc.generateSecret(keyspecbc); // ... Method getKeySize = JCEPBEKey.class.getDeclaredMethod("getKeySize"); getKeySize.setAccessible(true); System.out.println(getKeySize.invoke(keybc)); // prints '160'
现在,要了解“PBEWITHHMACSHA1”提供程序对应的内容,可以在BouncyCastleProvider的源代码中find以下内容 :
put("SecretKeyFactory.PBEWITHHMACSHA1", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA");
JCESecretKeyFactory.PBEWithSHA的实现如下所示:
public static class PBEWithSHA extends PBEKeyFactory { public PBEWithSHA() { super("PBEwithHmacSHA", null, false, PKCS12, SHA1, 160, 0); } }
您可以在上面看到,此关键工厂使用PKCS#12 v1( PKCS12 )algorithm来生成迭代密钥。 但是,要用于密码散列的PBKDF2algorithm使用PKCS#5 v2 scheme 2( PKCS5S2 )来代替。 这就是为什么你得到不同的结果。
我快速浏览了在BouncyCastleProvider
注册的JCE提供者,但是根本看不到使用PKCS5S2的任何密钥生成algorithm,更不用说也使用HMAC-SHA-1的密钥生成algorithm。
所以我猜测你在使用Sun实现(上面的模式#1)和在其他JVM上丢失可移植性,或者直接使用Bouncy Castle类(上面的模式#2)并且在运行时需要BC库时,
无论哪种方式,您应该可能切换到160位密钥,所以您不会不必要地截断生成的SHA-1散列。
我发现了一个BC Crypto-Only方法(实际上来自BC的cms包),它可以产生一个基于UTF-8的密码编码。 这样我就可以生成兼容的KDF输出
private byte[] calculatePasswordDigest(char[] pass, byte[] salt, int iterations) throws PasswordProtectionException { try { /* JCE Version (does not work as BC uses PKCS12 encoding) SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWITHHMACSHA1","BC"); PBEKeySpec ks = new PBEKeySpec(pass, salt, iterations,160); SecretKey digest = kf.generateSecret(ks); return digest.getEncoded(); */ PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(); gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(pass), salt, iterations); byte[] derivedKey = ((KeyParameter)gen.generateDerivedParameters(160)).getKey(); return derivedKey; } catch(Exception e) { LOG.error("Failed to strengthen the password with PBKDF2.",e); throw new PasswordProtectionException(); } }