Search code examples
javacryptographybouncycastle

BouncyCastle lightweight API equivalence for PBEWITHSHA256AND256BITAES-CBC-BC


I am currently registering the BouncyCastleProvider to JCE and installing the Unlimited Strength Policy in my Java Runtime. Because this enforces me to ship the runtime along with the product, I'd like to switch to BouncyCastle's lightweight API.

Unfortunately, the lightweight API comes along poorly documented, so I'm struggling on my way to setup an exact equivalence of the "PBEWITHSHA256AND256BITAES-CBC-BC" algorithm.

After delving into BouncyCastle's source and searching SO I have written a small test util which doesn't return the expected contents:

public class AESCipherTest {


private static final String data = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " +
    "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, " +
    "sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet " +
    "clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem " +
    "ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor " +
    "invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et " +
    "accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata " +
    "sanctus est Lorem ipsum dolor sit amet.";

private static final String password = "MySuperStrongPassword";

private static final byte[] salt = "MakeItSpicey".getBytes();

private static final String jce_algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC";

public static void main(String[] args) throws Exception {

  Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

  File file = new File("D:\\test.dat");
  file.createNewFile();

  FileOutputStream fos = new FileOutputStream(file);
  OutputStream cos = encryptWithJCE(fos, password);
  cos.write(data.getBytes());
  cos.flush();
  cos.close();

  FileInputStream fis = new FileInputStream(file);
  InputStream cis = decryptWithBCLWA(fis, password);
  byte[] readByteData = new byte[data.getBytes().length];
  cis.read(readByteData);
  cis.close();
  String readData = new String(readByteData);
  System.out.println(readData);
}

public static OutputStream encryptWithJCE(OutputStream os, String password) throws Exception {
  javax.crypto.spec.PBEParameterSpec pbeParamSpec = new javax.crypto.spec.PBEParameterSpec(salt, 20);
  javax.crypto.spec.PBEKeySpec pbeKeySpec = new javax.crypto.spec.PBEKeySpec(password.toCharArray());
  javax.crypto.SecretKeyFactory secretKeyFactory = javax.crypto.SecretKeyFactory.getInstance(jce_algorithm);
  javax.crypto.SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
  javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(jce_algorithm);
  cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, pbeParamSpec);

  OutputStream fOutputStream = new FilterOutputStream(
        new BufferedOutputStream(os)) {
     @Override
     public void close() throws IOException {
        // do nothing
     }
  };
  return new javax.crypto.CipherOutputStream(fOutputStream, cipher);
}

public static InputStream decryptWithBCLWA(InputStream inputStream, String password) throws Exception {
  org.bouncycastle.crypto.generators.PKCS12ParametersGenerator generator = new org.bouncycastle.crypto.generators.PKCS12ParametersGenerator(new org.bouncycastle.crypto.digests.SHA256Digest());
  char[] passwordChars = password.toCharArray();
  byte[] pkcs12PasswordToBytes = org.bouncycastle.crypto.PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
  generator.init(pkcs12PasswordToBytes, salt, 20);

  org.bouncycastle.crypto.modes.CBCBlockCipher cbcBlockCipher = new org.bouncycastle.crypto.modes.CBCBlockCipher(new org.bouncycastle.crypto.engines.AESEngine());
  org.bouncycastle.crypto.params.ParametersWithIV cipherParameters = (org.bouncycastle.crypto.params.ParametersWithIV) generator.generateDerivedParameters(256, 128);
  cbcBlockCipher.init(false, cipherParameters);
  org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher aesCipher = new org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher(cbcBlockCipher, new org.bouncycastle.crypto.paddings.PKCS7Padding());

  return new org.bouncycastle.crypto.io.CipherInputStream(inputStream, aesCipher);
}}

What am I missing? Any help is greatly appreciated!


Update

There's still one piece of old code remaining which needs transition from JCE to BC lightweight API:

byte[] keyBytes = ... // password in bytes
javax.crypto.spec.SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
javax.crypto.Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return new javax.crypto.CipherInputStream(in, cipher);

I am aware of the oddness of mixing an AES key with PBE, but as previously outlined it is old code which used to de/encrypt data and now I have to be able to read that encrypted data with BC lightweight API. Here I struggle with SecretKeySpec - is there something similar in BC lightweight API?


Solution

  • The answer of the updated question is that that the complete PBKDF2 function is fully skipped if you give the cipher a pre-computed AES key. Basically, you're just performing AES decryption here. If your keyBytes is indeed bytes retrieved from a password, you're likely vulnerable to password related attacks.

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
    
        byte[] keyBytes = new byte[16];
        byte[] iv = new byte[16];
        SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
        javax.crypto.Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC");
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
        final byte[] ciphertext = cipher.doFinal("The PBE encryption has gone!".getBytes(UTF_8));
    
        Cipher dCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        dCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        final byte[] plain = dCipher.doFinal(ciphertext);
    
        System.out.println(new String(plain, UTF_8));
    }
    

    outputs the plaintext:

    The PBE encryption has gone!
    

    [Added direct answer to the question of alphakermit]

    To perform the decryption in Bouncy you therefore need to perform basic CBC decryption, as pointed out by alphakermit in the comments:

        KeyParameter keyParameter = new KeyParameter(aesDecrypted);
        CBCBlockCipher cbcBlockCipher = new CBCBlockCipher(new AESEngine());
        cbcBlockCipher.init(false, keyParameter);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(cbcBlockCipher, new PKCS7Padding());
        return new CipherInputStream(in, aesCipher)
    

    using the org.bouncycastle.crypto.io.CipherInputStream instead of the one provided by Java itself, of course.