如何使用Bouncy Castle轻量级API与AES和PBE。

10
我有一段使用JCE算法“PBEWithSHA256And256BitAES-CBC-BC”创建的密文。提供者是BouncyCastle。我想使用BouncyCastle轻量级API解密此密文。我不想使用JCE,因为那需要安装无限制权限策略文件。
当涉及到使用BC和PBE以及AES时,文档好像很少。
这是我目前的代码。解密代码可以运行但返回的结果却是垃圾。
以下是加密代码:
String password = "qwerty";
String plainText = "hello world";

byte[] salt = generateSalt();
byte[] cipherText = encrypt(plainText, password.toCharArray(), salt);

private static byte[] generateSalt() throws NoSuchAlgorithmException {
    byte salt[] = new byte[8];
    SecureRandom saltGen = SecureRandom.getInstance("SHA1PRNG");
    saltGen.nextBytes(salt);
    return salt;
}

private static byte[] encrypt(String plainText, char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
    Security.addProvider(new BouncyCastleProvider());

    PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 20);

    PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
    SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithSHA256And256BitAES-CBC-BC");
    SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

    Cipher encryptionCipher = Cipher.getInstance("PBEWithSHA256And256BitAES-CBC-BC");
    encryptionCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);

    return encryptionCipher.doFinal(plainText.getBytes());
}

解密代码,

byte[] decryptedText = decrypt(cipherText, password.getBytes(), salt);

private static byte[] decrypt(byte[] cipherText, byte[] password, byte[] salt) throws DataLengthException, IllegalStateException, InvalidCipherTextException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
    BlockCipher engine = new AESEngine();
    CBCBlockCipher cipher = new CBCBlockCipher(engine);

    PKCS5S1ParametersGenerator keyGenerator = new PKCS5S1ParametersGenerator(new SHA256Digest());
    keyGenerator.init(password, salt, 20);

    CipherParameters keyParams = keyGenerator.generateDerivedParameters(256);
    cipher.init(false, keyParams);

    byte[] decryptedBytes = new byte[cipherText.length];
    int numBytesCopied = cipher.processBlock(cipherText, 0, decryptedBytes, 0);

    return decryptedBytes;
}
4个回答

11

我尝试过这个方法,看起来可以行得通。大量地从BC类org.bouncycastle.jce.provider.test.PBETest中借鉴。

private byte[] decryptWithLWCrypto(byte[] cipher, String password, byte[] salt, final  int iterationCount)
        throws Exception
{
    PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
    char[] passwordChars = password.toCharArray();
    final byte[] pkcs12PasswordBytes = PBEParametersGenerator
            .PKCS12PasswordToBytes(passwordChars);
    pGen.init(pkcs12PasswordBytes, salt, iterationCount);
    CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
    ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
    aesCBC.init(false, aesCBCParams);
    PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC,
            new PKCS7Padding());
    byte[] plainTemp = new byte[aesCipher.getOutputSize(cipher.length)];
    int offset = aesCipher.processBytes(cipher, 0, cipher.length, plainTemp, 0);
    int last = aesCipher.doFinal(plainTemp, offset);
    final byte[] plain = new byte[offset + last];
    System.arraycopy(plainTemp, 0, plain, 0, plain.length);
    return plain;
}

这行代码 pGen.generateDerivedParameters(256, 128); 是设置密钥长度吗? - george_h
1
@george_h:256 是密钥长度;128 是 IV 长度。 - President James K. Polk

9
你的解密方法存在一些问题:
private static byte[] decrypt(final byte[] bytes, final char[] password, final byte[] salt) throws DataLengthException, IllegalStateException, InvalidCipherTextException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {

    final PBEParametersGenerator keyGenerator = new PKCS12ParametersGenerator(new SHA256Digest());
    keyGenerator.init(PKCS12ParametersGenerator.PKCS12PasswordToBytes(password), salt, 20);
    final CipherParameters keyParams = keyGenerator.generateDerivedParameters(256, 128);

    final BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
    cipher.init(false, keyParams);

    final byte[] processed = new byte[cipher.getOutputSize(bytes.length)];
    int outputLength = cipher.processBytes(bytes, 0, bytes.length, processed, 0);
    outputLength += cipher.doFinal(processed, outputLength);

    final byte[] results = new byte[outputLength];
    System.arraycopy(processed, 0, results, 0, outputLength);
    return results;
}

主要问题在于您在执行解密时未使用块密码,并且在generateDerivedParameters方法中缺少IV大小。我很快就发现了第一个问题,第二个问题则不太明显。我只是通过查看Bouncy Castle测试中的名为PBETest的测试才发现了这个问题。

谢谢你的解决方案,laz。它完美地解决了我的问题,但由于Greg先回答,我认为公平起见我应该接受他的答案。 - Adrian
感谢您的反馈。当GregS提供答案时,我不知道他已经回答了。我很想知道为什么初始化向量的大小需要为128,以及有人如何知道这是必需的。这就是让我困惑的部分。 - laz
1
聪明人想法相似 :) 我知道AES是一个128位的分组密码,所以AES的初始向量(IV)总是128位。我本可以使用BlockCipher.getBlockSize() * 8来更通用一些。 - President James K. Polk

1

生成与JCE对应的密钥并不是一件简单的事情。我只是简要地浏览了您的代码,发现至少有一个差异。JCE使用PKCS12生成器,而您使用的是PKCS5S1。

如果存在其他差异,我并不感到惊讶。您需要将自己的代码与BC源代码进行比较。


谢谢你的回复,ZZ。我也尝试使用PKCS12,但是没有任何改变。 - Adrian

0
我注意到你的加密方法接受字符数组作为密码,但解密则接受字节。在Java中,字符是16位而字节是8位。这可能导致加密/解密使用不同的密钥,并且可能解释了解密结果为乱码的问题吗?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接