1

I'm trying to encrypt in PHP with openssl and decrypt in Javascript with crypto.subtle. The problem is the encrypted string in PHP is 16 bytes shorter than the same encrypted string in Javascript and I can't figure out what the problem is.

(For the test I use fixed variables (password, salt and iv).

PHP:

$msg = 'Hello world! Goodbye world!';
$passHash = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU='; // abcdefghijklmnopqrstuvwxyz012345
$saltHash ='nD45YGPbiHv/5B6MBFrf00CqtjEjOmsvv5mYf+d1iYU=';  // random bytes
$ivHash='eq32nRkkyPDUwHdr';                                 // random bytes

$password = base64_decode($passHash);
$rounds = 100000;
$salt = base64_decode($saltHash);
$iv = base64_decode($ivHash);
$bits = hash_pbkdf2('sha256', $password, $salt, $rounds, 64, true);
$aesKey = pack('C*',...(array_slice(unpack('C*',$bits),32,64)));

$ENC = openssl_encrypt(
    $msg,
    'aes-256-gcm',
    $aesKey,
    3,
    $iv,
    $tag,
    32
    );

Javascript:

let aesKey = await crypto.subtle.importKey(
    'raw', 
    aesBits, 
    {
        "name": "AES-GCM"
    },
    false,
    ['encrypt']
);   
let enc = await crypto.subtle.encrypt(
    {
      "name": "AES-GCM",
      "iv": iv
    }, 
    aesKey, 
    msg
);

Salt, iv and aesKey are exactly the same in both scripts. But the output always differs 16 bytes (base64 encoded):

PHP: mXa+Wf0pfgW/IZlNpDj3F7YjpkTCcTPPUGlJ                           //27 bytes
JS : mXa+Wf0pfgW/IZlNpDj3F7YjpkTCcTPPUGlJCrVR35+8KljvZ18h+ZrDgQ==   //43 bytes

If I decrypt the JS encrypted string in PHP I get Hello world! Goodbye world!A9��8U��^NpMu< (the string + 16 bytes garbage)

If I decrypt the PHP encrypted string in JS I get an error as JS expects an arrayBuffer(43) and only gets an arrayBuffer(27).

I've tried adding the $tag in PHP to the encrypted string but then the output is still different.

So my question is where those 16 bytes in JS come from and how do I add them in PHP when sending encrypted data and get rid of them when decrypting in PHP?

Michel
  • 4,076
  • 4
  • 34
  • 52
  • Does this help https://stackoverflow.com/questions/71129408/encrypt-in-php-just-like-javascript-cryptojs – RiggsFolly Mar 29 '23 at 13:31
  • Or maybe this https://stackoverflow.com/questions/24337317/encrypt-with-php-decrypt-with-javascript-cryptojs – RiggsFolly Mar 29 '23 at 13:32
  • @RiggsFolly In both questions OP uses the [CryptoJS](https://cryptojs.gitbook.io/docs/) library and I'm using the native [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) – Michel Mar 29 '23 at 13:41
  • `$rounds` is missing. – Topaco Mar 29 '23 at 13:43
  • @Topaco. Sorry, cut that off when copying the code. It's set to 100000 – Michel Mar 29 '23 at 13:45

1 Answers1

2

WebCrypto concatenates ciphertext and tag, PHP/OpenSSL does not.

Also, in the PHP code additional authenticated data AAD (here 32) is passed in the 7th parameter, in the WebCrypto code no AAD is passed.
Maybe 32 should specify the tag size. But this must be done in the 8th parameter, see here. Also, 32 would be an invalid value (the maximum tag size and the default is 16 bytes).

With:

$ENC = openssl_encrypt(
    $msg,
    'aes-256-gcm',
    $aesKey,
    3, // more transparent: OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
    $iv,
    $tag);
print(base64_encode($ENC . $tag)); // mXa+Wf0pfgW/IZlNpDj3F7YjpkTCcTPPUGlJCrVR35+8KljvZ18h+ZrDgQ==

the PHP code produces the same output (Base64 encoded) as the WebCrypto code.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • I put the tag length to 16, but still I get a different encrypted string. PHP is producing `mXa+Wf0pfgW/IZlNpDj3F7YjpkTCcTPPUGlJbcxzgvqQnMuFsZQ3uysrMg==` where the last 16 bytes are totally different from JS (and yours). – Michel Mar 29 '23 at 14:12
  • Wow. Removing the entire tag-length did the trick. after 11 hours of trying ;/ – Michel Mar 29 '23 at 14:13
  • @Michel - Note that the tag size is not specified in the 7th but in the 8th parameter and (btw 32 is not allowed as tag size, the max tag size and teh default is 16). In the 7th parameter the AAD is passed, i.e. 32 is interpreted as AAD). – Topaco Mar 29 '23 at 14:22
  • Have read the manual page at least a dozen times and never noticed it. Thanks for the support. – Michel Mar 29 '23 at 14:31