3

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?

Community
  • 1
  • 1
alphakermit
  • 111
  • 9

1 Answers1

2

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.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Thanks for pointing this out! This finally got me through. I'll accept your answer as correct. Maybe you may add the following to it for completeness (BC lightweight API) sake: `KeyParameter keyParameter = new KeyParameter(keyBytes); CBCBlockCipher cbcBlockCipher = new CBCBlockCipher(new AESEngine()); cbcBlockCipher.init(false, keyParameter); PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(cbcBlockCipher, new PKCS7Padding()); return CipherInputStream(in, aesCipher)` – alphakermit Oct 08 '14 at 09:12
  • As I finally got through all that cryptographic stuff: Is there a way to ship you some "bounty"? – alphakermit Oct 08 '14 at 09:29
  • You should be able to create a bounty with reason: "award existing answer" on your question. But I've got rep to spare and you don't have that much, so maybe you could revisit when you get to the "trusted user" level on SO :) – Maarten Bodewes Oct 08 '14 at 09:38
  • Because of this, I initially thought of a more physical bounty on a postal way since the Netherlands are not that far away. Hence the quots. – alphakermit Oct 08 '14 at 09:42