javascriptphpcryptographyecdsawebcrypto-api

Verify with PHP ECDSA signature generated with Web Crypto API


I have the following task. I have to generate ECDSA key pair. Sign data with the private key and verify the signature in PHP. For some unknown for me reason i am unable to verify the data successfuly.

Here is example of my javascript, that generates the keys and signs the data.

(async () => {

  async function arrayBufferToBase64(arrayBuffer) {
    var binary = '';
    var bytes = new Uint8Array(arrayBuffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa(binary);
  }

  let keyPair = await window.crypto.subtle.generateKey(
    {
      name: "ECDSA",
      namedCurve: "P-256",
    },
    false,
    ["sign", "verify"]);

    let data = 'text_value';

    let signature = await window.crypto.subtle.sign({
      name: "ECDSA",
      hash: { name: "SHA-256" },
    }, keyPair.privateKey, new TextEncoder().encode(data));
    
    console.log('Data: ' + data);

    console.log('Signature: ' + await arrayBufferToBase64(signature));

    console.log('Public key: ' + await arrayBufferToBase64(await window.crypto.subtle.exportKey('spki', keyPair.publicKey)));

})();

This code gives similar output.

Data: text_value
Signature: TfsoDx8TyuAth7SzsoagHVykRU+eNWaGOWEulrSjZ57KKa2T/8zO/7+/8TvRXLvuXSbEloRFbigzUpIlOlGMcw==
Public key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01iwlSEr1pLOLu2Ks9gxMhH7C3NWQ95yKO2vvv7XYfRGKAVRaLVLJ6j3J6klnNez5kWeECdJ1OhoQULEyokEZQ==

Having the data, signature and public key i send them to PHP to verify the signature.

I have the following PHP code (i am using php 8.1):

<?php

$data = 'text_value';
$signature = base64_decode("TfsoDx8TyuAth7SzsoagHVykRU+eNWaGOWEulrSjZ57KKa2T/8zO/7+/8TvRXLvuXSbEloRFbigzUpIlOlGMcw==");
$public_key = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01iwlSEr1pLOLu2Ks9gxMhH7C3NWQ95yKO2vvv7XYfRGKAVRaLVLJ6j3J6klnNez5kWeECdJ1OhoQULEyokEZQ==";
$public_key = "-----BEGIN PUBLIC KEY-----\n$public_key\n-----END PUBLIC KEY-----";

echo "Data: $data\n\n";
echo "Public key: \n$public_key\n\n";

$verify = openssl_verify($data, $signature, $public_key, OPENSSL_ALGO_SHA256);
echo $verify === 1 ? 'Valid signature' : 'Invalid signature';

echo "\n\nopenssl_error_string(): " . openssl_error_string() . "\n\n";

What am i doing wrong ?


Solution

  • ECDSA uses different signature formats, the ASN.1/DER and the IEEE P1363 (r|s) format. WebCrypto applies IEEE P1363, PHP/OpenSSL uses the ASN.1/DER format. So that the verification is successful, the IEEE P1363 formatted signature must be converted to ASN.1/DER format.

    The IEEE1363 signature from your example is hex encoded (instead of Base64 encoded):

    4dfb280f1f13cae02d87b4b3b286a01d5ca4454f9e35668639612e96b4a3679eca29ad93ffccceffbfbff13bd15cbbee5d26c49684456e28335292253a518c73
    

    and after conversion to ASN.1/DER format:

    304502204dfb280f1f13cae02d87b4b3b286a01d5ca4454f9e35668639612e96b4a3679e022100ca29ad93ffccceffbfbff13bd15cbbee5d26c49684456e28335292253a518c73
    

    or Base64 encoded:

    MEUCIE37KA8fE8rgLYe0s7KGoB1cpEVPnjVmhjlhLpa0o2eeAiEAyimtk//Mzv+/v/E70Vy77l0mxJaERW4oM1KSJTpRjHM=
    

    If the signature formatted in this way is used, verification with the PHP code is successful.


    In the IEEE P1363 format, the r and s values of the ECDSA signature (32 bytes each for P-256) are simply concatenated: r|s. In the other format, both values are ASN.1/DER encoded.

    Here you will find a description of both formats, which explains the above conversion. The relationship between the two formats also becomes clear when the ASN.1/DER encoded signature is loaded into an ASN.1/DER parser, e.g. here:

    enter image description here