PBKDF2与Java中的bouncycastle

and*_*rea 38 java bouncycastle jce pbkdf2

我正在尝试将密码安全地存储在数据库中,为此我选择存储使用PBKDF2函数生成的哈希.我想使用充气城堡库来做这个,但我不知道为什么我不能通过使用JCE接口让它工作...问题是在3种不同模式下生成散列:
1.使用PBKDF2WithHmacSHA1密钥工厂由sun提供
2.直接使用充气城堡api
3.通过JCE使用充气城堡
产生2个不同的值:一个与前两个共同,一个与第三个相同.

这是我的代码:

    //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());
Run Code Online (Sandbox Code Playgroud)

我知道PBKDF2是使用HMAC SHA1实现的,这就是为什么我在最后一个方法中选择了作为算法的"PBEWITHHMACSHA1",我从充气城堡java docs中获取.

输出如下:

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
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

Mat*_*all 30

简而言之,差异的原因是模式#1和#2中的PBKDF2算法使用PKCS#5 v2方案2(PKCS5S2)进行迭代密钥生成,但模式#3中的"PBEWITHHMACSHA1"的BouncyCastle提供程序使用PKCS#而是改为12 v1(PKCS12)算法.这些是完全不同的密钥生成算法,因此您可以获得不同的结果.

下面解释了为什么会出现这种情况以及为什么会得到不同大小的结果的更多细节.

首先,当您构建JCE KeySpec时,keyLength参数仅向提供者表达您想要的密钥大小的"首选项".来自API文档:

注意:这用于指示可变密钥大小密码的密钥长度的首选项.实际密钥大小取决于每个提供商的实现.

JCEPBEKey的来源判断,Bouncy Castle提供程序似乎不尊重此参数,因此您应该期望从使用JCE API时使用SHA-1的任何BC提供程序获得160位密钥.

您可以通过以编程方式访问测试代码中getKeySize()返回keybc变量的方法来确认:

Key keybc = factorybc.generateSecret(keyspecbc);
// ...
Method getKeySize = JCEPBEKey.class.getDeclaredMethod("getKeySize");
getKeySize.setAccessible(true);
System.out.println(getKeySize.invoke(keybc)); // prints '160'
Run Code Online (Sandbox Code Playgroud)

现在,要了解什么是"PBEWITHHMACSHA1"提供对应于,你可以找到以下的来源BouncyCastleProvider:

put("SecretKeyFactory.PBEWITHHMACSHA1", 
    "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA");
Run Code Online (Sandbox Code Playgroud)

JCESecretKeyFactory.PBEWithSHA的实现如下所示:

public static class PBEWithSHA
    extends PBEKeyFactory
{
    public PBEWithSHA()
    {
        super("PBEwithHmacSHA", null, false, PKCS12, SHA1, 160, 0);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在上面看到,此密钥工厂使用PKCS#12 v1(PKCS12)算法进行迭代密钥生成.但是,要用于密码散列的PBKDF2算法使用PKCS#5 v2方案2(PKCS5S2).这就是为什么你会得到不同的结果.

我快速浏览了注册的JCE提供程序BouncyCastleProvider,但看不到任何使用PKCS5S2的密钥生成算法,更不用说同时使用HMAC-SHA-1的算法了.

所以我猜你会遇到使用Sun实现(上面的模式#1)和在其他JVM上丢失可移植性,或者直接使用Bouncy Castle类(上面的模式#2)并且在运行时需要BC库.

无论哪种方式,您都应该切换到160位密钥,这样就不会不必要地截断生成的SHA-1哈希.


eck*_*kes 6

我找到了一个 BC Crypto-Only 方法(实际上来自 BC 的 cms 包),它可以生成基于 UTF-8 的密码编码。这样我就可以生成兼容的 KDF 输出

http://packages.python.org/passlib/lib/passlib.hash.cta_pbkdf2_sha1.html#passlib.hash.cta_pbkdf2_sha1

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();
    }
}
Run Code Online (Sandbox Code Playgroud)