我使用 openssl 创建了 ECDSA 密钥对
openssl ecparam -name prime256v1 -genkey -noout -out ecdsa_private_key.pem
openssl ec -in ecdsa_private_key.pem -pubout -out ecdsa_public_key.pem
并使用文本文件生成签名
openssl dgst -sha256 -sign ecdsa_private_key.pem -out ecdsa_signature.bin data.txt
然后我尝试使用 js webcrypto 验证此签名,得到
false
结果
import { readFile } from "node:fs/promises"
import { EOL } from "node:os";
/**
* @param { string } pem
*/
function pemToArrayBuffer(pem) {
const lines = pem.trim().split(EOL);
lines.pop();
lines.shift();
return Buffer.from(lines.join(""), "base64");
}
const pemPublicKey = await readFile("./ecdsa_public_key.pem", "utf8")
const publicKeyArrayBuffer = pemToArrayBuffer(pemPublicKey);
const publicKey = await crypto.subtle.importKey(
'spki',
publicKeyArrayBuffer,
{
name: 'ECDSA',
namedCurve: 'P-256',
},
true,
['verify']
);
const data = await readFile("./data.txt");
const signature = await readFile("./ecdsa_signature.bin");
const isValid = await crypto.subtle.verify(
{
name: 'ECDSA',
hash: { name: 'SHA-256' }
},
publicKey,
signature,
data
);
console.log(isValid); //false
使用 openssl 时一切都很好
openssl dgst -sha256 -verify ecdsa_public_key.pem -signature ecdsa_signature.bin data.txt
正如@Topaco 提到的,问题出在签名格式上。
要将
ASN.1/DER
签名转换为 P1363
,可以使用以下函数:
/**
* @param { Uint8Array } signature
*/
function toP1363(signature) {
let i = 0;
if (signature[i++] !== 0x30) throw new Error("Invalid ASN.1 sequence header");
const length = signature[i++];
if (signature.length < length + i) throw new Error("Invalid ASN.1 sequence length");
//r
if (signature[i++] != 0x02) throw new Error('Invalid ASN.1 "r" tag');
let rLength = signature[i++];
while (signature[i] == 0x00) { i++; rLength--; }
const r = new Uint8Array(signature.buffer, i, rLength);
i += rLength;
//s
if (signature[i++] != 0x02) throw new Error('Invalid ASN.1 "s" tag');
let sLength = signature[i++];
while (signature[i] == 0x00) { i++; rLength--; }
const s = new Uint8Array(signature.buffer, i, sLength - 1);
//r|s
const l = Math.trunc((Math.max(r.length, s.length) + 1) / 2) * 2;
const result = new Uint8Array(l * 2);
result.set(r, l - r.length);
result.set(s, (l * 2) - r.length);
return result;
}
转换签名后一切顺利