无法使用从安全嵌入式芯片生成的 WebCrypto API (subtlecrypto) 验证原始 UInt8Array ECDSA (secp256r1) 消息/签名/公钥

问题描述

我正在使用安全嵌入式芯片 (ATECC508) 使用 secp256r1 曲线生成 ECDSA 消息/签名组合。

Uint8Arrays中芯片输出的信息:message[32]、signature[64]、publickey[64]; 公钥采用原始字节格式,在 64 字节数组中给出两个 X/Y 坐标,前面没有 0x04 填充。

只要我在前面用 0x04 填充 64 字节数组,公钥才会成功导入到 WebCrypto 中。

然而,当我尝试验证消息和签名时,它总是失败。

我创建了一个模拟器脚本来帮助调试。它使用 WebCrypto 代替加密芯片来生成消息、签名和公钥。模拟器生成一个 32 字节的 Uint8array 消息数组,并对消息进行签名,返回一个 64 字节的 Uint8array 作为其签名。 WebCrypto 生成的公钥也被验证在前面有 0x04 填充,在代表曲线 X/Y 坐标的 64 字节之前。这意味着我相信模拟器生成的信息应该与芯片提供的信息格式完全相同。

WebCrypto 生成的任何消息、签名和公钥都可以成功验证...但我无法验证来自芯片的信息...总是不成功。

我没有理由怀疑来自芯片的值是错误的,而且我已经从芯片生成了许多消息/签名......它们都不起作用。我也尝试了第二个芯片,结果相同。

来自芯片的输出示例(不验证):

uint8_t message[32] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F
};

uint8_t signature[64] = {
0xD6,0x82,0x25,0xCC,0x68,0x6F,0x4F,0x84,0x91,0x48,0x63,0x6E,0x67,0x3C,0xD4,0xC0,0xF8,0xE5,0x9D,0x7B,0xAD,0x6B,0xB3,0xF1,0xDB,0x90,0xB7,0x5E,0x43,0xCF,0xD8,0xC3,0x8C,0x77,0x74,0xE2,0xA0,0x29,0xFF,0x22,0x7D,0xF9,0x41,0x56,0x8A,0xEA,0x4D,0x57,0x37,0x9C,0x6A,0x85,0xBE,0xEC,0x69
};

uint8_t publicKey[64] = {
0x39,0xDD,0x44,0x6D,0xC1,0xDA,0x9F,0x46,0xFC,0xBF,0x2F,0x5B,0xA5,0xB9,0x30,0x7F,0x96,0x40,0x3B,0x35,0xF0,0xBD,0x42,0x27,0x51,0xE3,0x31,0x21,0xA4,0x2B,0x2A,0x3F,0x52,0xC5,0xD9,0xF6,};

注意:上面的公钥在导入前用0x04填充,否则WebCrypto会报错。

来自 WebCrypto 模拟器 javascript 控制台的输出示例(这验证没问题):

Message to import in hex: 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
Message Uint8Array conversion: Uint8Array(32) [ 0,1,2,3,4,5,6,7,8,9,… ]

the signature in hex: 5d4575a19cad9a56b554bafbb1db212d8badd48b5d29f6556fa33f0e8650175f48157e70377c7aa5f97e55bc455b6520060dc86f32a00577a254a97e1b7b394c
the signature buffer: 
Uint8Array(64) [ 93,69,117,161,156,173,154,86,181,84,… ]

public key in hex:
04d18669eb64b54118fbcdacbf79d2f185383abadb1982382a51675650f4596d9f917a7b9b215f2424418b4e7c824500dc29ea507f08b4ef51ca35f58de75ca274

注意:上面是十六进制字符串,但我在调用 WebCrypto API 之前转换为 UintArray8。 PublicKey 转换为正确的 CryptoKey() 类型,然后用于验证。

我用于验证的 javascript 代码

   let result = await window.crypto.subtle.verify({
            name: "ECDSA",hash: { name: "SHA-256" }
        },publicKey,sigbuffer,msgbuffer);

最后,导入:

return window.crypto.subtle.importKey(
        'raw',key,{
            name: 'ECDSA',namedCurve: 'P-256'
        },true,["verify"]
    );

我的意图是能够将芯片中的信息成功导入并验证到WebCrypto API中,但到目前为止我一直没有成功。目前我完全被困住了,非常感谢任何帮助......提前致谢

解决方法

如果验证了未散列的数据,则发布数据的验证成功(我使用 C# 解决方案对此进行了测试,请参阅在线 here)。总之,这证明未散列的数据已签名。

然而,对未散列的数据进行签名并不常见,一般对散列的数据进行签名。一方面,这纯粹是出于实际原因(非对称算法一般只能用于加密短消息),但另一方面出于安全原因,它也是必要的,s。例如here

出于这个原因,许多库隐式执行散列,这就是必须指定摘要的原因。对于 WebCrypto API 也是如此,导致散列数据使用为未散列数据生成的签名进行验证,这当然会失败。

现在也无法导出其哈希值仅生成您的消息的数据,然后将该数据用作 WebCrypto API 的输入。加密哈希应该可以防止这种推断。

由于这些原因,无法使用 WebCrypto API 验证数据!

所以你需要一个不会隐式散列的 JavaScript 库,例如SJCL

var pubHex = '39c3dd74131729446dc1b3da67d49fc046fcbf072fcc5b9fa51c05b974307f969c403b1635f0449f02bd422751e33121a4434f152f2b2b2a3f675219c5d925f6';
var pub = new sjcl.ecc.ecdsa.publicKey(
    sjcl.ecc.curves.c256,sjcl.codec.hex.toBits(pubHex)
)

var msgHex = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f';
var msg = sjcl.codec.hex.toBits(msgHex)

var sigHex = 'd68225cc686f4f849148636e673cd4c0f8e59d7bad6bb3f11cdb90b71a5e43cfd8c38c7774e2a029ff43227df94156128a1bea4d578a379c6a850a56beec1a69';
var sig = sjcl.codec.hex.toBits(sigHex)

var verified = false;
try {
    var verified = pub.verify(msg,sig)
} catch (e) {}
console.log('Verification:',verified)
<script src="https://cdn.jsdelivr.net/npm/[email protected]/sjcl.min.js"></script>

这样,发布的数据就被成功验证了。