digitalbazaar / forge

A native implementation of TLS in Javascript and tools to write crypto-based and network-heavy webapps
https://digitalbazaar.com/
Other
5.03k stars 777 forks source link

Decrypt Java BouncyCastle PBEWITHSHA256AND128BITAES-CBC-BC using forge #547

Open csandels opened 6 years ago

csandels commented 6 years ago

Hello.

I'm trying to use forge to decrypt a message that was encrypted using the Java BouncyCastle PBEWITHSHA256AND128BITAES-CBC-BC algorithm.

The Java code looks like the following:

String password = "Password";
String src = "AabOla0M5kc1NglPKNKVVNb0XSKD6IsWa0n84gfO0AM=";

byte[] srcBytes = Base64.decodeBase64(src);

byte[] saltBytes = new byte[16];
System.arraycopy(srcBytes, 0, saltBytes, 0, 16);

System.out.println("Salt64: " + Base64.encodeBase64String(saltBytes));

byte[] msgBytes = new byte[16];
System.arraycopy(srcBytes, 16, msgBytes, 0, 16);

System.out.println("Message64: " + Base64.encodeBase64String(msgBytes));

SecretKey key = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC", "BC")
        .generateSecret(new PBEKeySpec(password.toCharArray()));

System.out.println("Key64: " + Base64.encodeBase64String(key.getEncoded()));

byte[] ivBytes = new byte[16];
Arrays.fill(ivBytes, (byte)5);
PBEParameterSpec parameterSpec = new PBEParameterSpec(saltBytes, 1002, new IvParameterSpec(ivBytes));
Cipher decryptCipher = Cipher.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC", "BC");

decryptCipher.init(2, key, parameterSpec);
byte[] decBytes = decryptCipher.doFinal(msgBytes);
String decData = new String(decBytes);

System.out.println("Decrypted: " + decData);

This prints out:

Salt64: AabOla0M5kc1NglPKNKVVA==
Message64: 1vRdIoPoixZrSfziB87QAw==
Key64: AFAAYQBzAHMAdwBvAHIAZAAA
Decrypted: My Message

The javascript looks like the following:

const password = "Password";
const cryptSrc = "AabOla0M5kc1NglPKNKVVNb0XSKD6IsWa0n84gfO0AM=";
let iv = '';

let fullBytes = forge.util.createBuffer(forge.util.decode64(cryptSrc));
let saltBytes = fullBytes.getBytes(16);
let msgBytes = fullBytes.getBytes(16);

console.log("Salt64: " + forge.util.encode64(saltBytes));
console.log("Message64: " + forge.util.encode64(msgBytes));

let cryptoKey = forge.pkcs5.pbkdf2(password, saltBytes, 1002, 32, 'sha256');

console.log("Key64: " + forge.util.encode64(cryptoKey));

let decipher = forge.cipher.createDecipher('AES-CBC', cryptoKey);
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(msgBytes));
decipher.finish();
let decDat = decipher.output.data;

console.log("Decrypted: " + decDat);

Which print the following:

Salt64: AabOla0M5kc1NglPKNKVVA==
Message64: 1vRdIoPoixZrSfziB87QAw==
Key64: v9PGHutjWClnghucE23fNI1bMR6xwNpGbHC9n0JPlaY=
Decrypted: S5Ý°durI«R"]_

As we see, the javascript version has the same salt, message and password. I seem to have the following 2 problems:

  1. I'm not sure what to use for an iv in the javascript side. The iv on the Java side appears to have no effect at all. No matter what I use for an iv on the Java side, I get the correct decrypted message. I'm thinking BouncyCastle is disregarding the iv for this algorithm. If that's so, how would I correclty use the AES-CBC cipher in forge when it requires an iv?

  2. It appears the key is wrong. I'm assumed it should be 32 bytes since it's sha256. Iterations are the same, salt is the same. BUT... the BouncyCastle source for the PBEWithSHA256And128BitAES-CBC-BC SecretKeyFactory shows it uses SHA256 for the digest, a 128 bit key and iv size (implying I should use 16 bytes above) and a PKCS12 scheme. With some hints from the accepted SO answer at https://stackoverflow.com/questions/8674018/pbkdf2-with-bouncycastle-in-java/10569975, I figure BouncyCastle is using PKCS12 for iterations instead of PKCS5, which is used by forge. So, is there any way to use forge to generate the same key as Java has done in this case?

csandels commented 6 years ago

For anyone who might find this in a search wondering about the same sort of issues, though I haven't yet solved the issues above. Mainly, I don't know where the iv is or how it's derived in the BouncyCastle version, nor do I know how to use pkcs12 if in fact that is even the problem in #2 above.

However, I AM able to migrate the java data to a different encryption algorithm that can be decrypted with forge or crypto-js. This will solve my problem if nothing else, though with a bit more work.

There is a great example of using java and crypto-js together to share encrypted data at https://github.com/mpetersen/aes-example.

Following is roughly the same code, modified to use forge and a 256 bit key. Just change 256 to 1 for sha1 like the original crypto-js/java example. And to get java to do 256, just change SHA1 to SHA256 in the SecretKeyFactory.getInstance and pass 256 to PBEKeySpec instead of 128.

var AesUtil = function(keySize, iterationCount) {
  this.keySize = keySize / 8;
  this.iterationCount = iterationCount;
};

AesUtil.prototype.generateKey = function(salt, passPhrase) {
  var key = forge.pkcs5.pbkdf2(passPhrase, forge.util.hexToBytes(salt), this.iterationCount, this.keySize, 'sha256');
  return key;
}

AesUtil.prototype.encrypt = function(salt, iv, passPhrase, plainText) {
    var key = this.generateKey(salt, passPhrase);

    var cipher = forge.cipher.createCipher('AES-CBC', key);
    cipher.start({iv: forge.util.createBuffer(forge.util.hexToBytes(iv))});
    cipher.update(forge.util.createBuffer(plainText));
    cipher.finish();

    return forge.util.encode64(cipher.output.getBytes());
}

AesUtil.prototype.decrypt = function(salt, iv, passPhrase, cipherText) {
    var key = this.generateKey(salt, passPhrase);

    var cipher = forge.cipher.createDecipher('AES-CBC', key);
    cipher.start({iv: forge.util.createBuffer(forge.util.hexToBytes(iv))});
    cipher.update(forge.util.createBuffer(forge.util.decode64(cipherText)));
    cipher.finish();

    return cipher.output.data;
}
user163 commented 7 months ago

@csandels - Regarding your first comment: PBEWITHSHA256AND128BITAES-CBC-BC follows PKCS#12. node-forge provides the function forge.pki.pbe.generatePkcs12Key() that can be used to derive key and IV according to PKCS#12:

var forge = require('node-forge');
var pki = forge.pki;

const password = "Password";
const cryptSrc = "AabOla0M5kc1NglPKNKVVNb0XSKD6IsWa0n84gfO0AM=";

// separate salt/message
let fullBytes = forge.util.decode64(cryptSrc);
let salt = forge.util.createBuffer(fullBytes.substring(0, 16))
let msg = forge.util.createBuffer(fullBytes.substring(16));

// key/IV derivation
let sha256 = forge.md.sha256.create()
var key = pki.pbe.generatePkcs12Key(password, salt, 1, 1002, 16, sha256)
var iv = pki.pbe.generatePkcs12Key(password, salt, 2, 1002, 16, sha256)

// decryption
let decipher = forge.cipher.createDecipher('AES-CBC', key);
decipher.start({iv: iv});
decipher.update(msg);
decipher.finish();

// output
let decDat = decipher.output.data;
console.log("Decrypted: " + decDat); // Decrypted: My Message

// key/IV -------------
console.log("Key: ", forge.util.bytesToHex(key.data)); // Key:  733fa98d304f7135647bee191a112cd4
console.log("IV:  ", forge.util.bytesToHex(iv.data));  // IV:   bb8ba3bff3a42c7e662fd13727aca024

Regarding the Java code: This is misleading, as it pretends to set an IV, which is not the case (the IV is derived like the key). A more transparent implementation in this respect is:

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
...
Security.addProvider(new BouncyCastleProvider());

String password = "Password";
String src = "AabOla0M5kc1NglPKNKVVNb0XSKD6IsWa0n84gfO0AM=";

byte[] srcBytes = Base64.decodeBase64(src);
byte[] saltBytes = new byte[16];
byte[] msgBytes = new byte[srcBytes.length - saltBytes.length];
System.arraycopy(srcBytes, 0, saltBytes, 0, 16);
System.arraycopy(srcBytes, saltBytes.length, msgBytes, 0, msgBytes.length);

PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 1002);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC");
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);  

Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(msgBytes);

String decryptedText = new String(decryptedBytes, StandardCharsets.UTF_8);          
System.out.println("Decrypted: " + decryptedText); // Decrypted: My Message
...

Key and IV can be determined as follows:

import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
...
ParametersWithIV parametersWithIV = (ParametersWithIV)((BCPBEKey)secretKey).getParam();
KeyParameter keyParameter = (KeyParameter)parametersWithIV.getParameters();
System.out.println("Key: " + HexFormat.of().formatHex(keyParameter.getKey()));    // Key: 733fa98d304f7135647bee191a112cd4
System.out.println("IV:  " + HexFormat.of().formatHex(parametersWithIV.getIV())); // IV:  bb8ba3bff3a42c7e662fd13727aca024
...

Note that what you call key is just the password encoded according to PKCS#12, i.e. to which a NULL terminator has been appended and which has been encoded with UCS-2 BE (Unicode code points of the BMP encoded in Big Endian), s. RFC 7292, B.1 Password Formatting. This can be easily seen if key.getEncoded() is not Base64 but hex encoded:

00500061007300730077006f007200640000

The actual key and IV are derived from this according to the key derivation function defined in PKCS#12.

shashi-donon commented 3 months ago

i created my implementation of PBEWITHSHA256AND256BITAES-CBC-BC you can use jaspyt-js-pbe its open to use. i hope this helps you