我正在尝试通过 WebCrypto 验证 ECDSA 签名并失败。签名是使用 Java(Bouncy Castle)创建的。使用的曲线是 secp256r1,并且 SHA256 哈希用于签名创建。然后我尝试在 Java 中使用 RSA (SHA256) 创建签名并尝试在 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.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);
}
}