Невозможность проверить подпись ECDSA с помощью WebCrypto

Я пытаюсь проверить подпись ECDSA через WebCrypto и не могу. Подпись создается с использованием Java (Bouncy Castle). Используемая кривая - secp256r1, а хэш SHA256 используется при создании подписи. Затем я попытался создать подпись с использованием RSA (SHA256) на Java, попытался проверить в WebCrypto и преуспел. Это похоже на конкретную проблему ECDSA. В java я экспортировал открытый ключ в SPKI, а затем успешно импортировал в WebCrypto. Что может быть причиной сбоя WebCrypto при проверке ключей на основе ECDSA.

      let publicKey;
const verifyButton = document.querySelector(".spki .encrypt-button");

console.log("Starting js code");

const content = str2ab('HelloWorld');
const signatureB64 = 'MEUCIEAcdw929MCVQhk7BxHslo4cq0TwmkaNtL/JQgbbef1JAiEA5WKai5VCMhwKs7Zyh7fiG2DIKxoytw9OaYwsfA0etzY=';
const signature = str2ab(window.atob(signatureB64));

const pemEncodedKey = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5J8msRi8yYS8ndRquLAXW4K7tdra9Awgl1CmOPPQsfFHYieZWMdWkcxreYI/hrbnwKP7MtjcTrt8seoVUprx8Q==
-----END PUBLIC KEY-----`;

function importECDSAKey(pem) {
    // fetch the part of the PEM string between header and footer
    const pemHeader = "-----BEGIN PUBLIC KEY-----";
    const pemFooter = "-----END PUBLIC KEY-----";
    const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
    // base64 decode the string to get the binary data
    const binaryDerString = window.atob(pemContents);
    // convert from a binary string to an ArrayBuffer
    const binaryDer = str2ab(binaryDerString);

    return window.crypto.subtle.importKey(
      "spki",
      binaryDer,
      {
        name: "ECDSA",
        namedCurve: 'P-256'
      },
      true,
      ["verify"]
    );
}


async function verifySignature() {
    //download("signatureecdsa_13Aug.bin", window.btoa(window.atob(signatureB64)));
    console.log("Verify Content: " + content);
    console.log("Verify Signature: " + signature);
    let result = await window.crypto.subtle.verify(
      {
        name: "ECDSA",
        namedCurve: 'P-256',
        hash: {name: "SHA-256"},
      },
      publicKey,
      signature,
      content
    );
    console.log(result ? "valid" : "invalid");
    
}

function str2ab(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;
}

const importKeyButton = document.querySelector(".spki .import-key-button");
  importKeyButton.addEventListener("click", async () => {
    publicKey = await importECDSAKey(pemEncodedKey);
    console.log(publicKey);
    
  });

verifyButton.addEventListener("click", async () => {
    verifySignature();
    
});
      <!DOCTYPE html>
<html>
<style>
/* General setup */

* {
    box-sizing: border-box;
}

html,body {
    font-family: sans-serif;
    line-height: 1.2rem;
}

/* Layout and styles */

h1 {
    color: green;
    margin-left: .5rem;
}

.description, .sign-verify {
    margin: 0 .5rem;
}

.description > p {
    margin-top: 0;
}

.sign-verify {
    box-shadow: -1px 2px 5px gray;
    padding: .2rem .5rem;
    margin-bottom: 2rem;
}

.sign-verify-controls > * {
    margin: .5rem 0;
}

input[type="button"] {
    width: 5rem;
}

.signature-value {
    padding-left: .5rem;
    font-family: monospace;
}

/* Validity CSS */
.valid {
    color: green;
}

.invalid {
    color: red;
}

.invalid::after {
    content: ' ✖';
}

.valid::after {
    content: ' ✓';
}

/* Whole page grid */
main {
    display: grid;
    grid-template-columns: 32rem 1fr;
    grid-template-rows: 4rem 1fr;
}

h1 {
    grid-column: 1/2;
    grid-row: 1;
}

.examples {
    grid-column: 1;
    grid-row: 2;
}

.description {
    grid-column: 2;
    grid-row: 2;
}

/* sign-verify controls grid */
.sign-verify-controls {
    display: grid;
    grid-template-columns: 1fr 5rem;
    grid-template-rows: 1fr 1fr;
}

.message-control {
    grid-column-start: 1;
    grid-row-start: 1;
}

.signature {
    grid-column-start: 1;
    grid-row-start: 2;
}

.sign-button {
    grid-column-start: 2;
    grid-row-start: 1;
}

.verify-button {
    grid-column-start: 2;
    grid-row-start: 2;
}

/* Animate output display */
.fade-in {
    animation: fadein .5s;
}

@keyframes fadein {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}
</style>
<head>
 <meta charset="utf-8">
    <!-- head definitions go here -->
</head>
<body onload="">
      <section class="import-key spki">
      <h2 class="import-key-heading">ECDSA Siganture Verification</h2>
      <section class="import-key-controls">
        <input class="import-key-button" type="button" value="Import Key">
        <input class="encrypt-button" type="button" value="Verify">
      </section>
    </section>
</body>
    <script src="sample.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/node-forge@0.7.0/dist/forge.min.js"></script>
</html>

Подпись проверяется в java:

      /*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.codegic.authenticity.text.pkcs1;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 *
 * @author Sajid Hussain
 */
public class ECDSASignatureVerifier {

    static String content = "HelloWorld";
    static String sigantureB64 = "MEUCIEAcdw929MCVQhk7BxHslo4cq0TwmkaNtL/JQgbbef1JAiEA5WKai5VCMhwKs7Zyh7fiG2DIKxoytw9OaYwsfA0etzY=";
    static String pemEncodedCert = "-----BEGIN CERTIFICATE-----\n"
            + "MIIDQTCCAimgAwIBAgIUUm4Lc5DyI1TgpT2FdmDW1TIJ8rQwDQYJKoZIhvcNAQEL\n"
            + "BQAwODELMAkGA1UEBhMCUEsxEDAOBgNVBAoTB0NvZGVnaWMxFzAVBgNVBAMTDkNv\n"
            + "ZGVnaWMgU3ViIENBMB4XDTIxMDgxMjEwMTE0M1oXDTI2MDgxMjEwMTE0M1owHTEb\n"
            + "MBkGA1UEAxMSRUNEU0EtVGVzdGluZy1DZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0D\n"
            + "AQcDQgAE5J8msRi8yYS8ndRquLAXW4K7tdra9Awgl1CmOPPQsfFHYieZWMdWkcxr\n"
            + "eYI/hrbnwKP7MtjcTrt8seoVUprx8aOCAScwggEjMA4GA1UdDwEB/wQEAwIGwDAf\n"
            + "BgNVHSMEGDAWgBRMIjotWPXzdcBEsvsWI9GGgsK5sTCBxQYDVR0gBIG9MIG6MIG3\n"
            + "BgkrBgEEAYOoZAEwgakwgaYGCCsGAQUFBwICMIGZDIGWQXMgcGVyIHRoaXMgcG9s\n"
            + "aWN5IGlkZW50aXR5IG9mIHRoZSBzdWJqZWN0IGlzIG5vdCBpZGVudGlmaWVkLiBZ\n"
            + "b3UgbXVzdCBub3QgdXNlIHRoZXNlIGNlcnRpZmljYXRlcyBmb3IgdmFsdWFibGUg\n"
            + "dHJhbnNhY3Rpb25zLiBOTyBMSUFCSUxJVFkgSVMgQUNDRVBURUQuMAkGA1UdEwQC\n"
            + "MAAwHQYDVR0OBBYEFIEA+hTr95sTIKNgdm/qL3WdAB4MMA0GCSqGSIb3DQEBCwUA\n"
            + "A4IBAQAHcmrw0gjBdqjYowT4TsUBEL6Dei7gt/qEgDBOsewgESZmoDqSbZXNMHsJ\n"
            + "kvceQ3hCLt9FTQPijyXMQJyBSKdYym8PXk3BJRbZpl3Kdy/jZMBg10oZo5PMZh8i\n"
            + "MZNpvzdiULpAqtquRK1kUS/hB/5qnWuypuQgFdrM/S2IuJuTzdmUuTBt3yjK12Zp\n"
            + "8j8gqT1JlL/SYkFA3HvdFMu6t6pDWyfL3h48YpQqjpHw7H55IGKrBRhpDlGNYpO9\n"
            + "/MkNhaKlCKA6/LXqJ1Exonu+WbJk5X23F+SaThrxWR7iY7aIdLjj1Sqd73wKhtM8\n"
            + "39gP1Xtl+AyoVLHyXFvqbuwyZaPU\n"
            + "-----END CERTIFICATE-----";

    public static void main(String[] args) throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        Security.removeProvider("BC");
        Security.addProvider(new BouncyCastleProvider());
        verifyTextSiganture();
    }

    private static void verifyTextSiganture() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException, CertificateException {
        Base64.Decoder decoder = Base64.getDecoder();
        Signature signAlg = Signature.getInstance("SHA256withECDSA");
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        X509Certificate signerCertificate = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(pemEncodedCert.getBytes()));
        signAlg.initVerify(signerCertificate.getPublicKey());
        signAlg.update(content.getBytes());
        boolean result = signAlg.verify(decoder.decode(sigantureB64.getBytes()));
        System.out.println("Verification Result: " + result);
    }

}

1 ответ

Решение

Подписи EC могут быть указаны в двух форматах: r|s (IEEE P1363) или ASN.1 / DER. Оба формата объясняются здесь .

WebCrypto использует формат r | s, а MEUCI...имеет формат ASN.1. Ваша подпись в формате r | s закодирована в Base64:

      QBx3D3b0wJVCGTsHEeyWjhyrRPCaRo20v8lCBtt5/UnlYpqLlUIyHAqztnKHt+IbYMgrGjK3D05pjCx8DR63Ng==

Если вы используете эту подпись в коде JavaScript, проверка прошла успешно.

Другие вопросы по тегам