encryptionphpseclibaes-gcmsubtlecryptowebcrypto

Is phpseclib AES-GCM encryption compatible with javascript WebCrypto?


I'm trying to encrypt/decrypt symmetrically from php/phpseclib to js/WebCrypto(SubtleCrypto). The algorithm is AES-GCM with PBKDF2 derivation and also with plain key. I had no success. The error received from the window.crypto.subtle.decrypt() function is:

OperationError: The operation failed for an operation-specific reason

RSA-OAEP works without any problems.

Did anybody do this before - is it possible at all? I didn't find anything that confirms or denies a compatibility between these modules.

Edit: adding code example

encryption:

<?php
require_once($_SERVER['DOCUMENT_ROOT'] . '/../vendor/autoload.php');
use phpseclib3\Crypt\AES;

$TEST_AES_KEY = "TWw4QCkeZEnXoCDkI1GEHQ==";
$TEST_AES_IV = "CRKTyQoWdWB2n56f";
$message = "123&abc";

$aes = new AES('gcm');
$aes->setKey($TEST_AES_KEY);
$aes->setNonce($TEST_AES_IV);

$ciphertext = $aes->encrypt($message);
$tag = $aes->getTag();
$ciphertextBase64 = base64_encode($ciphertext . $tag);
echo $ciphertextBase64;

decryption:

<!DOCTYPE html>
<html>
<script>
    function _base64ToArrayBuffer(base64) {
        var binary_string   = atob(base64);
        var len             = binary_string.length;
        var bytes           = new Uint8Array(len);
        for (var i = 0; i < len; i++) {
            bytes[i] = binary_string.charCodeAt(i);
        }
        return bytes.buffer;
    }

    async function _importKeyAes(key) {
        return await window.crypto.subtle.importKey(
            "raw",
            key,
            { name: "AES-GCM" },
            false,
            ["encrypt", "decrypt"]
        );
    }

    async function decryptMessageSymetric(key, data, iv) {
        keyArrayBuffer  = _base64ToArrayBuffer(key);
        key             = await _importKeyAes(keyArrayBuffer);
        iv              = _base64ToArrayBuffer(iv);
        data            = _base64ToArrayBuffer(data);
        result = await window.crypto.subtle.decrypt(
            { name: "AES-GCM", iv: iv, tagLength: 128 },
            key,
            data
        );
        return new TextDecoder().decode(result);
    }

    TEST_AES_KEY = "TWw4QCkeZEnXoCDkI1GEHQ==";
    TEST_AES_IV = "CRKTyQoWdWB2n56f";
    messageEncrypted = "LATYboD/FztIKGVkiJNWHOP72C77FiY="; // result from phpseclib encryption

    result = decryptMessageSymetric(TEST_AES_KEY, messageEncrypted, TEST_AES_IV);
    console.log(result);
</script>
</html>

Solution

  • There are only two minor encoding bugs:

    With these changes decryption is successful:

    (async () => {
    
        function _base64ToArrayBuffer(base64) {
            var binary_string   = atob(base64);
            var len             = binary_string.length;
            var bytes           = new Uint8Array(len);
            for (var i = 0; i < len; i++) {
                bytes[i] = binary_string.charCodeAt(i);
            }
            return bytes.buffer;
        }
    
        async function _importKeyAes(key) {
            return await window.crypto.subtle.importKey(
                "raw",
                key,
                { name: "AES-GCM" },
                false,
                ["encrypt", "decrypt"]
            );
        }
    
        async function decryptMessageSymetric(key, data, iv) {
            keyArrayBuffer  = _base64ToArrayBuffer(key);
            key             = await _importKeyAes(keyArrayBuffer);
            iv              = new TextEncoder().encode(iv); // Remove Base64 decoding
            data            = _base64ToArrayBuffer(data);
            result = await window.crypto.subtle.decrypt(
                { name: "AES-GCM", iv: iv, tagLength: 128 },
                key,
                data
            );
            return new TextDecoder().decode(result);
        }
    
        TEST_AES_KEY = "TWw4QCkeZEnXoCDkI1GEHQ==";
        TEST_AES_IV = "CRKTyQoWdWB2n56f";
        messageEncrypted = "7K+HAB7Ch9V4jJ1XJPM0sANXA2ocJok="; // Apply modified ciphertext
    
        result = await decryptMessageSymetric(TEST_AES_KEY, messageEncrypted, TEST_AES_IV); 
        console.log(result);
    })();


    Note that a static IV is a serious security risk for GCM, s. here.