Michel
Michel

Reputation: 4157

PHP openssl_encrypt and Javascript crypto.subtle.encrypt have different output (AES-256-GCM)

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?

Upvotes: 1

Views: 637

Answers (1)

Topaco
Topaco

Reputation: 49341

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.

Upvotes: 2

Related Questions