8

I am attempting to reproduce an encryption operation using AES-256-CCM that is currently performed in Java with the Bouncy Castle provider. When attempting the same operation in PHP using openssl I cannot find a set of parameters that produces the same output.

As the AEAD modes were recently added to PHP (7.1), documentation on how this works is scarce.

A minimum example of the "working" encryption in Java looks like:

    public static void main(String args[]) {
    try {
        java.security.Security.addProvider(new BouncyCastleProvider());
        byte[] key = Base64.decodeBase64("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=".getBytes());
        byte[] iv = Base64.decodeBase64("rcFcdcgZ3Q/A+uHW".getBytes());

        SecretKey aesKey = new SecretKeySpec(key, 0, key.length, "AES");
        Cipher aesCipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
        aesCipher.init(1, aesKey, new IvParameterSpec(iv));

        byte[] encrypted = aesCipher.doFinal("test".getBytes());
        System.out.println(Hex.encodeHex(encrypted));

        // Output: 411d89ff74205c106d8d85a8
    }
    catch (Throwable e) {
        e.printStackTrace();
    }
}

As I am trying to re-produce this using different two different libraries and languages I have set the key and iv to known values.

When trying to re-produce this using PHP and openssl I am trying with the following code

$key = base64_decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
$iv = base64_decode('rcFcdcgZ3Q/A+uHW');
$data = 'test';
$tag = null;

$encrypted = openssl_encrypt($data,'aes-256-ccm', $key,OPENSSL_RAW_DATA, $iv, $tag,"",8);
echo(bin2hex($encrypted . $tag));
// d1a7403799b8c37240f36edb

Clearly the results do not match. In search of an answer as to what is incorrect I created the same operation using SJCL in javascript. The example for that is:

var data = "test";
var key = sjcl.codec.base64.toBits("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
var iv = sjcl.codec.base64.toBits("rcFcdcgZ3Q/A+uHW");
var  p = { 
        adata: "",
        iter: 0,
        mode: "ccm",
        ts: 64,
        ks: 256,
        iv: iv,
        salt: ""
        };
var encrypted = sjcl.encrypt(key, data, p, {}); 
console.log(encrypted);
// Output: {"iv":"rcFcdcgZ3Q/A+uHW","v":1,"iter":0,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"","ct":"QR2J/3QgXBBtjYWo"}

// QR2J/3QgXBBtjYWo === 411d89ff74205c106d8d85a8

The Bouncy Castle and SJCL libraries produce the same output but I can't tell what is different.

I have tried pre-processing the key with PBKDF2 as suggested in Encrypt in Javascript with SJCL and decrypt in PHP with no success. I have tried SHA256'ing the key with no success.

Why is the output in php/openssl different than Bouncy Castle and SJCL?

jww
  • 97,681
  • 90
  • 411
  • 885
Josh Glick
  • 83
  • 3
  • 1
    CCM is a packet based scheme that, to put it mildly, sucks balls. I would not rule out implementation errors in OpenSSL. Haven't found any bug report but please use the latest version of OpenSSL. – Maarten Bodewes Dec 31 '17 at 13:19
  • I never did figure out what was different. I switched my project from PHP to javascript with sjcl and everything is fine. – Josh Glick Jan 16 '18 at 22:03
  • Encrypt in Java and decrypt in php or viceversa is going to give you headaches cause it's difficult to get it right, that's my experience. That's the reason when I need the same thing (encrypt in java decrypt in php) what I do is just execute the openssl binary externally with its parameters to encrypt/decrypt accordingly. – Nelson Mar 10 '18 at 14:19
  • In the posted PHP code AES-CCM is used with a 12 bytes nonce. `openssl_encrypt` generates under these conditions a wrong ciphertext / tag due to a bug, see [here](https://stackoverflow.com/a/61797909). This is a pure PHP bug. OpenSSL itself returns the correct result. – Topaco May 27 '20 at 18:14

1 Answers1

1

When I stumbled upon a similar problem, I discovered that the problem resided in the IV, more precisely: the length of it. As far as You use an IV with the length under 12, it results with the same hashes. You can try it with your own code:

java.security.Security.addProvider(new BouncyCastleProvider());
byte[] key = Base64.getDecoder().decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=".getBytes());
byte[] iv = "12345678901".getBytes();

SecretKey aesKey = new SecretKeySpec(key, 0, key.length, "AES");
Cipher aesCipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
aesCipher.init(1, aesKey, new IvParameterSpec(iv));

byte[] encrypted = aesCipher.doFinal("test".getBytes());
System.out.println(Hex.encodeHex(encrypted));
// Output: e037af9889af21e78252ab58

and same with PHP:

$key = base64_decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
$iv = "12345678901";
$tag = null;

$encrypted = openssl_encrypt("test", "aes-256-ccm", $key, OPENSSL_RAW_DATA, $iv, $tag, null, 8);
print bin2hex($encrypted . $tag);
# e037af9889af21e78252ab58

If you would extend the IV, you'll see the results will differ. NB! Keep in mind that if you'd shorten the AES key (to 128 bytes), then Java will automatically switch to aes-128, but in PHP you have to change the algorithm manually.

Tiit
  • 520
  • 4
  • 15