How to use Bouncy Castle lightweight API with AES

2019-01-17 08:49发布

I have a block of ciphertext that was created using the JCE algorithim "PBEWithSHA256And256BitAES-CBC-BC". The provider is BouncyCastle. What I'd like to do it decrypt this ciphertext using the BouncyCastle lightweight API. I don't want to use JCE because that requires installing the Unlimited Strength Jurisdiction Policy Files.

Documentation seems to be thin on the ground when it comes to using BC with PBE and AES.

Here's what I have so far. The decryption code runs without exception but returns rubbish.

The encryption code,

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());
}

The decryption code,

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条回答
来,给爷笑一个
2楼-- · 2019-01-17 09:13

There were a few problems with your decrypt method:

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;
}

The main problems were the way you were carrying out the decryption without using a block cipher and the missing IV size to the generateDerivedParameters method. I saw the first problem pretty quickly, the 2nd one was much less obvious. I only discovered that one through looking at a Bouncy Castle test called PBETest.

查看更多
3楼-- · 2019-01-17 09:16

I tried this and it seemed to work. Borrowed heavily from the BC class 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;
}
查看更多
别忘想泡老子
4楼-- · 2019-01-17 09:17

It's not trivial to generate the key exactly as the JCE counterparts. I just briefly browsed your code. Found at least one discrepancy. JCE uses PKCS12 generator but you use PKCS5S1.

I am not surprised if there are other differences. You need to compare your code against BC source.

查看更多
祖国的老花朵
5楼-- · 2019-01-17 09:21

I noticed that your encryption method accepts a password as a char array but the decrypt accepts password as bytes. In Java chars are 16-bit while bytes are 8-bit. This could result in different keys for encrypt/decrypt and perhaps explain the issues with gibberish decrypted results?

查看更多
登录 后发表回答