我可以毫无问题地在 Node Crypto 中执行验证,并且它输出
true
,但是当我尝试使用 Web Crypto 时,它输出 false
,没有任何错误,但两者都使用相同的变量。我无法使用 Node Crypto,因为代码将在 CF Worker Runtime 中运行,并且它不支持相关的 Node Crypto 功能。为什么会发生这样的情况以及我该如何解决。
此代码使用 Node Crypto 并输出
true
。
import crypto from "crypto";
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXjyD37iJ6K7dVWCANfrTEJkDFKZt
dlaCMOGuTE3qsy4PF3FqUnDi0EZxty8n6Mb3W3Ahj0ASkF+GwNW8C/ztdQ==
-----END PUBLIC KEY-----`;
const messageToVerify = "hello world";
const signature =
"MEUCIENzPHGDk+t1inhAvnqPX1OYLfSltYVIv1cipjW2F3CxAiEAzTVrj5CCHChsyeAif0qM6UvX3h0U7BDHhb+XmsXwO/c=";
(() => {
const verify = crypto.createVerify("SHA256");
verify.update(messageToVerify);
const verified = verify.verify(
publicKeyPEM,
Buffer.from(signature, "base64")
);
console.log(verified);
})();
此代码使用 Web Crypto API 并输出
false
。
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;
}
async function importEcdsaKey(pem) {
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const pemContents = pem.substring(
pemHeader.length,
pem.length - pemFooter.length - 1
);
const binaryDerString = atob(pemContents);
const binaryDer = str2ab(binaryDerString);
return await crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "ECDSA",
namedCurve: "P-256",
},
true,
["verify"]
);
}
function base64ToArrayBuffer(base64String) {
const binaryString = atob(base64String);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXjyD37iJ6K7dVWCANfrTEJkDFKZt
dlaCMOGuTE3qsy4PF3FqUnDi0EZxty8n6Mb3W3Ahj0ASkF+GwNW8C/ztdQ==
-----END PUBLIC KEY-----`;
const messageToVerify = "hello world";
const signature =
"MEUCIENzPHGDk+t1inhAvnqPX1OYLfSltYVIv1cipjW2F3CxAiEAzTVrj5CCHChsyeAif0qM6UvX3h0U7BDHhb+XmsXwO/c=";
(async () => {
const publicKey = await importEcdsaKey(publicKeyPEM);
const signatureArrayBuffer = base64ToArrayBuffer(signature);
const data = new TextEncoder().encode(messageToVerify);
const result = await crypto.subtle.verify(
{
name: "ECDSA",
hash: { name: "SHA-256" },
},
publicKey,
signatureArrayBuffer,
data
);
console.log(result);
})();
WebCrypto 要求 ECDSA 签名采用 P1363 格式,而发布示例中的签名是 ASN.1/DER 编码的。 这篇文章中解释了这两种格式。
如果签名是P1363格式,验证有效:
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;
}
async function importEcdsaKey(pem) {
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const pemContents = pem.substring(
pemHeader.length,
pem.length - pemFooter.length - 1
);
const binaryDerString = atob(pemContents);
const binaryDer = str2ab(binaryDerString);
return await crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "ECDSA",
namedCurve: "P-256",
},
true,
["verify"]
);
}
function base64ToArrayBuffer(base64String) {
const binaryString = atob(base64String);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXjyD37iJ6K7dVWCANfrTEJkDFKZt
dlaCMOGuTE3qsy4PF3FqUnDi0EZxty8n6Mb3W3Ahj0ASkF+GwNW8C/ztdQ==
-----END PUBLIC KEY-----`;
const messageToVerify = "hello world";
const signature = "Q3M8cYOT63WKeEC+eo9fU5gt9KW1hUi/VyKmNbYXcLHNNWuPkIIcKGzJ4CJ/SozpS9feHRTsEMeFv5eaxfA79w=="; // P1363 format works
//const signature = "MEUCIENzPHGDk+t1inhAvnqPX1OYLfSltYVIv1cipjW2F3CxAiEAzTVrj5CCHChsyeAif0qM6UvX3h0U7BDHhb+XmsXwO/c="; //ASN.1/DER format fails
(async () => {
const publicKey = await importEcdsaKey(publicKeyPEM);
const signatureArrayBuffer = base64ToArrayBuffer(signature);
const data = new TextEncoder().encode(messageToVerify);
const result = await crypto.subtle.verify(
{
name: "ECDSA",
hash: { name: "SHA-256" },
},
publicKey,
signatureArrayBuffer,
data
);
console.log(result);
})();