Search code examples
javascriptphpcryptoapiphp-openssl

Encrypt in JS and Decrypt in PHP using AES encryption algorithm in GCM and Base64 encoding


I am try to Encrypt data in JS and Decrypt it in PHP using AES encryption algorithm in GCM and Base64 encoding. But during decryption I always get false as result without any openssl error. I have verify the data during transmission at frontend and backend are the same. I have also added "ext-openssl": "*" to my composer.json file in my laravel. What could I be doing wrong that I don't get back the result as decrypted.

Below is My encryption Method in Frontend with JS

async function encryptData(data, key) {
    const encoder = new TextEncoder();
    const encodedData = encoder.encode(data);
    // Ensure the key is 256 bits (32 bytes)
    if (key.length !== 32) {
        throw new Error('AES key must be 256 bits (32 bytes)');
    }
    const cryptoKey = await crypto.subtle.importKey('raw', new TextEncoder().encode(key), {
        name: 'AES-GCM'
    }, false, ['encrypt']);
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encrypted = await crypto.subtle.encrypt({
        name: 'AES-GCM',
        iv
    }, cryptoKey, encodedData);
    // Concatenate IV and encrypted data and convert to base64
    const combinedData = new Uint8Array(iv.length + encrypted.byteLength);
    combinedData.set(iv);
    combinedData.set(new Uint8Array(encrypted), iv.length);
    // Convert to base64 using btoa
    const base64String = btoa(String.fromCharCode.apply(null, combinedData));
    return base64String; // Return the base64 string without URL encoding
}

Below is the Submission of the Encrypted data in Frontend with JS

async function submitData() {
    var licence = $('#licence').val();
    const encryptionKey = '12345678901234567890123456789012';

    try {
        let encryptedLicence = await encryptData(licence, encryptionKey);

        var jsonData = {
            licence: encryptedLicence
        };

        var queryParams = Object.keys(jsonData)
            .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(jsonData[key]))
            .join('&');
        var targetUrl = 'submit?' + queryParams;
        window.location.href = targetUrl;
    } catch (error) {
        console.error('Error encrypting data:', error.message);
    }
}

Below is how I receive data Backend in Php

 public function formData(Request $request){                                           
 $key = '12345678901234567890123456789012';                     
 $data=$request->licence;                                       
 $decryptedProduct = self::decryptingMyData($data, $key);     
 dd($decryptedProduct);
 }

And Below is how I decrypt in Php

public function decryptingMyData($encryptedData, $key) {                                                          
// URL-decode the received data                                                                                 
$receivedData = urldecode($encryptedData);                                                                      
$decodedData = base64_decode($receivedData);                                                                    
$iv = substr($decodedData, 0, 12);                                                                              
$encryptedText = substr($decodedData, 12);                                                                      
$decrypted = openssl_decrypt($encryptedText, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv);                     
if ($decrypted === false) {                                                                                     
// Decryption failed                                                                                          
 $opensslError = openssl_error_string();                                                                      
 dd($decrypted ,$opensslError);                                                                                           
return decrypted;                                                                                                
}                                                                    
return $decrypted;                                                                                                                                    

Solution

  • The other answer suggests HTTPS as an alternative for the required encryption. While HTTPS may be the better approach, the question remains as to why the posted codes are not compatible, which is addressed by the following answer.

    The posted codes apply AES/GCM as algorithm. This is authenticated encryption, which uses a tag for authentication.
    The WebCrypto library implicitly attaches the tag to the ciphertext, while PHP/OpenSSL processes ciphertext and tag separately. Therefore, both must be separated on the PHP/OpenSSL side and also passed separately to openssl_decrypt().
    This is missing in the current PHP code and can be fixed as follows:

    ...
    $tag = substr($encryptedText, -16); 
    $encryptedText = substr($encryptedText, 0, -16); 
    $decrypted = openssl_decrypt($encryptedText, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
    ...
    

    With this change decryption works.


    For the sake of completeness, please note that different lengths are also possible for the tag (12 to 16 bytes, in exceptional cases also 8 and 4 bytes, see NIST SP 800-38D, sec. 5.2.1.2). The longer the tag, the more secure, which is why 16 bytes are usually applied. This is also the default value for both libraries used here.

    Also keep in mind that passwords should not be used directly as keys for security reasons due to their generally lower entropy, but instead a key derivation function such as PBKDF2 in conjunction with a random salt.