如何使用 Web Crypto API 验证公钥

问题描述 投票:0回答:1

我可以毫无问题地在 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);
})();

javascript cryptography public-key webcrypto-api node-crypto
1个回答
0
投票

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);
})();

© www.soinside.com 2019 - 2024. All rights reserved.