Невозможность проверить подпись 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, проверка прошла успешно.