0

A short description: A JavaScript receives from a PHP-Script a RSA-Public-Key, the private-key will be stored.

In the JavaScript script, some data is encrypted and then passed to a PHP script that processes it.

There the encrypted data must be decrypted, but at this point my problem starts, my code doesn't decrypt the data.


My Code:

PHP:

header('Content-Type: application/json');
/*...*/

if(/*Conditions...*/){ /* When the Public Key is requested this is true */
    die(json_encode(["EncryptionCode" => generateEncryptionCode()]));
} else if (
    /*Conditions...*/ //When the encoded data will be passed this is true
    && !empty($_POST["key_id"]
    && !empty($_POST["encrypted_data"])
){
    $data = decryptData(
        ciphertext: $_POST["username"],
        key_id: $_POST["key_id"]
    );

    // Now when the $data is correctly decoded (Here, when the decoded data is equal to the given text "This is some text that has to be encrypted and decrypted.")
    $test_text = 'This is some text that has to be encrypted and decrypted.';
    if($data == $test_text){
        die(json_encode(["success" => true]));
    } else {
        die(json_encode(["Error"=>"Data not correctly decoded"]));
    }
} else {
    die(json_encode(["Error" => "Permission denied"]);
}


/********************************
*  Functions that will be used  *
********************************/

/**
* Generates an RSA encryption key pair and stores the private key in the database.
*
* @return array An array containing the public key and the ID of the stored private key.
*/
function generateEncryptionCode() : array{
    global $database;
    $config = array(
        "private_key_bits" => 2048,
        "private_key_type" => OPENSSL_KEYTYPE_RSA,
    );

    $privateKey = openssl_pkey_new($config);

    $publicKey = openssl_pkey_get_details($privateKey)['key'];

    $id = $database->add_encryption_key($privateKey);

    return ["key"=>$publicKey, "id"=>$id];
}

/**
* Decrypts ciphertext using a private key retrieved from the database.
*
* @param string $ciphertext The base64-encoded ciphertext to decrypt.
* @param int $key_id The ID of the private key stored in the database.
* @return string The decrypted data.
*/
function decryptData($ciphertext, $key_id){
    global $database;
    $b64_ciphertext = base64_decode($ciphertext);
    
    $private_key_string = $database->get_encryption_key($key_id);
    $decrypted_data = '';
    if(
        !openssl_private_decrypt(
            $b64_ciphertext, 
            $decrypted_data, 
            $private_key_string, 
            OPENSSL_PKCS1_OAEP_PADDING
        )
    ){
        $error = openssl_error_string();
        die(json_encode(["OpenSSL Fehler: " . $error]));   
    }
    return $decrypted_data;
}

JavaScript

const url = 'url_to_the_php_script';
let formData = new FormData()
/*formData.append(Data_for_permission)*/ /*The data that give permission in the PHP script*/
let text = "This is some text that has to be encrypted and decrypted."

// Call the PHP-Script to get the public_key
call_api(url, formData)
.then((data) => {
    // get the public key of the data
    encryption_key = data['EncryptionCode'];
    // encrypt the data with the key
    (async function() => {
        var encrypted_text = await encrypt_data(text, await import_public_key(encryption_key['key']))
        // now pass the encrypted data to the PHP file
        formData = new FormData()
            formData.append('key_id', encryption_key['id'])
            formData.append('encrypted_data', encrypted_text)
            /*formData.append(Data_for_permission)*/ /*The data that give permission in the PHP script*/
        call_api(url, formData)
        .then((data) {
            // If the encrypted data was successfully passed to the PHP script and no error occurred in the PHP script while it was processing the data
            if(data['success']){} //Success!
            else {
                console.error('Error', 'An error occured.')
            }
        })
    })
})
.catch((error) => {
    console.error('Error', error)
})


/********************************
*  Functions that will be used  *
********************************/

/**
* Encrypts the given value using RSA-OAEP encryption.
*
* @param {string} value The value to encrypt.
* @param {CryptoKey} encryption_key The encryption key.
* @returns {Promise<string>} A promise that resolves to the encrypted value.
*/
async function encrypt_data(value, encryption_key){
    const enc = new TextEncoder();
    const encoded = enc.encode(value)
    const ciphertext = await window.crypto.subtle.encrypt(
        {
            name: 'RSA-OAEP'
        },
        encryption_key,
        encoded
    )
    
    const encoder = new TextEncoder()
    const data_view = new DataView(ciphertext)
    const u_int_8_array = new Uint8Array(ciphertext.byteLength)
    
    for (let i=0; i<ciphertext.byteLength; i++){
        u_int_8_array[i] = data_view.getUint8(i);
    }
    return btoa(String.fromCharCode.apply(null, u_int_8_array))
}

/**
* Imports a public key from a PEM-encoded string.
*
* @param {string} pem The PEM-encoded public key string.
* @returns {Promise<CryptoKey>} A promise that resolves to the imported public key.
*/
function import_public_key(pem){
    // fetch the part of the PEM string between header and footer
    var pem_contents = pem
    .replace(/-----BEGIN PUBLIC KEY-----|-----END PUBLIC KEY-----/g, '')
    .replace(/\s/g, '');
    // base64 decode the string to get the binary data
    const binary_key_string = window.atob(pem_contents)
    // convert from a binary string to an ArrayBuffer
    const binary_key = (function(str){
        const buf = new ArrayBuffer(str.length);
        const bufView = new Uint8Array(buf);
        for (let i = 0, strLen = str.length; i < strLen; i++) {
            bufView[i] = str.charCodeAt(i);
        }
        return buf;
    })(binary_key_string);
    
    return window.crypto.subtle.importKey(
        'spki',
        binary_key,
        {
            name: 'RSA-OAEP',
            hash: 'SHA-256'
        },
        true,
        ["encrypt"]
    );
}

/**
* Makes a POST request to the specified URL with the provided form data.
*
* @param {string} url The URL to make the request to.
* @param {FormData} formData The form data to include in the request body.
* @returns {Promise} A promise that resolves to the response data or an error.
*/
function call_api(url, formData) {
    return new Promise((resolve, reject) => {
        fetch(url, {
            method: 'POST',
            body: formData
        })
        .then(response => {
            if(!response.ok){ throw new Error('Error fetching data.') }
            return response.json()
        })
        .then(data => {
            resolve(data)
        })
        .catch(error => {
            resolve(error)
        })
    })
}

In a test run I got this Keys:

-----BEGIN PUBLIC KEY-----\n MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudG/Hbb3YDYSgd6IqR22 Vk9AOHhAmuR11zbFh1Sbetq8kdFXnYP2nj/fCaTemr63tfd8FY8LVnmuU6BbFL0Z CmpGTGuOtnYlyVd7NuNGdkwAi98apK2g0Sozl4ez4+KzqNy+B7+EPOknuR5tWQ8q 21DMmZU8RFXaiHrdWWONegiWyDc45jDXpWurnTDTC4BD3AC9fD3tbmbAxmuW4MDX aK6+nkGnv7R8ZCrr1BpV2HnYpikDiW0+1ISKjd1gEb37l1fb+VPH2vDi9cEmqubj ugNnvT2Eb9BEIsAV63Jqs60rQZOC74e9NXDr6QMW3x2qBexQQIS4K3rFCuPZb2AR pQIDAQAB \n-----END PUBLIC KEY-----

-----BEGIN PRIVATE KEY-----\n MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC50b8dtvdgNhKB 3oipHbZWT0A4eECa5HXXNsWHVJt62ryR0Vedg/aeP98JpN6avre193wVjwtWea5T oFsUvRkKakZMa462diXJV3s240Z2TACL3xqkraDRKjOXh7Pj4rOo3L4Hv4Q86Se5 Hm1ZDyrbUMyZlTxEVdqIet1ZY416CJbINzjmMNela6udMNMLgEPcAL18Pe1uZsDG a5bgwNdorr6eQae/tHxkKuvUGlXYedimKQOJbT7UhIqN3WARvfuXV9v5U8fa8OL1 wSaq5uO6A2e9PYRv0EQiwBXrcmqzrStBk4Lvh701cOvpAxbfHaoF7FBAhLgresUK 49lvYBGlAgMBAAECggEATf6P7XUC2EtZ8VDqo7Fo+0lAd3NiCqGiJLdEqc0FhceS JtJrqB3fwgSlJXMiTGmIysQaPSJRa/afCVLhaA8HF6wL3b+3ozZsHdquSReUV8sG 367BjCWkvqasCQpYo2pgZpxg1ve4Faj3l2gCFcOcBXogpsZRCY3PsEGB7ycuFu4U NFioP6EtN3XczXSCKbYt1KYdj2ndFHyGMfIL0LKmwNPamU40qJs09xMluIr/qUds YrxBe5JWWuODCfR0FUhOeMbVxRaZzZi6qH1WAkZCYjbF4eFhFN3oG5neEAO5T+8j TapdB32YF6kDajdVIX0NFVLNBbOPJ2ihlviQ5BlqcQKBgQDtz8sr1bpbMuGczfL+ ALKe1WHuoXmrIzH/jBiPo19+LJAR3L5xT3lcXPmNZaVLGYenp5cJvudhxrIOR82X EzyC/4Ia/RkP8uWGUbVkPQF1NtXReU2AjxzZar7TUFGY7oCd9oZOOtvOnXsCbJiF 1u2yAJ6JK0DTR/Mw+El4CdPYfwKBgQDIB/ltlmMVcD1odHk14eOmMNWobG996D7k dRnobcAhQKMXZXJBupJUdSfxgy9kHy696QK62CYtCk4+DmCCI0fsNJ3kUCHcrvQ+ ETp904T5ZX+hk2vDMjJr29Ww2bmUpik1bvVuUEmC9oDPIzLFgykFXNJ3AZrOyVMn 0xODfTOj2wKBgQDlCo0T2vVxgL/q1jCCkwl2EO4Rd1RHj85H4haFwUPnsePQUFrb pz+rxaBUnuFkQ2J0BuVhbYxMj6JOPrm0F8LgKFaWx82rnrWReIDL2jXdPsMQzVPn ze5rOHQx8dmlAZC+kwEnt2icxvAClbUQssCcAByw4Ae/djyznW6lPlHa4QKBgQCt KzokRS1CQgjnhO3qV8Rc+6n8ROPAfG72GOp07Y6HOw32Ezz26i4EL+iEjK1aYCR3 BGH4n2dtVp6l2oxyHVkGhAaswTKPema31PJuO8/CmLwFhTqloa9E8OvuTo76wV6r g4O2HIuHdR/OMwqhMwswOUt6+0ip/GCg+XrLOniaQQKBgFnXxe0QXN28bwnMRiGW BiEkOiq26sgueJuOApajsgXsFH/5O5covv6Qh7exe1PbeMboGArv5O1i5Zkc9xcQ phCZ21kEKQGNWkOc9AXK3MPnpuxrbfmPP2LY4qu7K9Zzn/iO/BPWfkoaXmB6GFb3 U5RSViL6D7Gb/Ht5UER44MGR \n-----END PRIVATE KEY-----

When the encryption is done the encrypted data (ciphertext) look like this:

AIoQF+ZIRqm/JkDEyZZhN7YoLZSSUIDd+DxN67b2mQFBmTWDj5BJ0Hb+ZFYYsobnrqBzyEEir0EI4Mg/lZv8CapkFmN0MXfe/Rov2JQ5YA6eNmm8WzWhi7AT9FhNBEBs03tbSWo92sfCAC1M0s6oEYiuwV7YTobxDgA6tWaDWzaulkMbaXNg02nteSXMIxG8ZAQrvxr4PzeLT0t9PMYCNIKv3+xr0Xswox7K2yg0eRlKYL9xQ3pGGObFU4LWPSVDTEM7LnCs4qlUz3RxfVc4Bpdpfr1ZMN+FL3qB6df9gV/nx2l3L2gIbPMYosqvHhhI6QDPZk8VqfHqYhq9tv8/4A==

But these decryptData function of the PHP-Script doesn't decrypt the data:

if(!openssl_private_decrypt(
    $b64_ciphertext,
    $decrypted_data,
    $private_key_string,
    OPENSSL_PKCS1_OAEP_PADDING
)){
    $error = openssl_error_string();
    die(json_encode(["OpenSSL Fehler: " . $error]));
}

After the check if the openssl_private_decrypt is true an error occured: The error:

    error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error

I tryed to change the padding to OPENSSL_NO_PADDING. But nothing happened, the error was gone but the $decrypted_data wasn't set to the decrypted data.

At this point I really don't know what I am doing wrong.

If you know of anything I can do, I would be very grateful.

4
  • 4
    At least one problem is that the paddigs are not compatible. PHP/OpenSSL supports only SHA-1 for OAEP, while in the WebCrypto code SHA-256 is used. So either apply SHA-1 in the WebCrypto code or change the library on the PHP side, e.g. phpseclib. Commented Jun 5, 2023 at 17:41
  • It looks like there are a lot of things about the code you've posted that are wrong, the most egregious of which is generating a new public/private keypair and sending both to the client. You might as well use plaintext. Generally, the private key should never, ever leave the machine on which it was generated. Commented Jun 5, 2023 at 21:01
  • Thank you @tapaco for ur comment that was really helpful to understand what's going wrong. Commented Jun 7, 2023 at 5:48
  • @Sammitch - I really don't know if you're just blind or just looking for a fight. In my code I only send the public key to the client and the private key is saved, if you don't show the interest to HELP others, then I ask you to ignore the comment function. This is not helpful! Commented Jun 7, 2023 at 5:48

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.