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?

5
  • Does this help stackoverflow.com/questions/71129408/… Commented Mar 29, 2023 at 13:31
  • Or maybe this stackoverflow.com/questions/24337317/… Commented Mar 29, 2023 at 13:32
  • @RiggsFolly In both questions OP uses the CryptoJS library and I'm using the native SubtleCrypto Commented Mar 29, 2023 at 13:41
  • $rounds is missing. Commented Mar 29, 2023 at 13:43
  • @Topaco. Sorry, cut that off when copying the code. It's set to 100000 Commented Mar 29, 2023 at 13:45

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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).
Wow. Removing the entire tag-length did the trick. after 11 hours of trying ;/
@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).
Have read the manual page at least a dozen times and never noticed it. Thanks for the support.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.