无法使用 WebCrypto API (subtlecrypto) 验证从安全嵌入式芯片生成的原始 UInt8Array ECDSA (secp256r1) message/signature/pubkey

Unable to verify a raw UInt8Array ECDSA (secp256r1) message/signature/pubkey using WebCrypto API (subtlecrypto), generated from secure embedded chip

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

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

只有在前面用 0x04 填充 64 字节数组后,public 密钥才会成功导入 WebCrypto。

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

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

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

我没有理由怀疑芯片的值是错误的,而且我已经从芯片生成了很多 messages/signatures...none 其中有效。我也尝试了第二个芯片,结果相同。

芯片输出示例(未验证):

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,0x1C,0xDB,0x90,0xB7,0x1A,0x5E,0x43,0xCF,0xD8,0xC3,0x8C,0x77,0x74,0xE2,0xA0,0x29,0xFF,0x43,0x22,0x7D,0xF9,0x41,0x56,0x12,0x8A,0x1B,0xEA,0x4D,0x57,0x8A,0x37,0x9C,0x6A,0x85,0x0A,0x56,0xBE,0xEC,0x1A,0x69
};

uint8_t publicKey[64] = {
0x39,0xC3,0xDD,0x74,0x13,0x17,0x29,0x44,0x6D,0xC1,0xB3,0xDA,0x67,0xD4,0x9F,0xC0,
0x46,0xFC,0xBF,0x07,0x2F,0xCC,0x5B,0x9F,0xA5,0x1C,0x05,0xB9,0x74,0x30,0x7F,0x96,
0x9C,0x40,0x3B,0x16,0x35,0xF0,0x44,0x9F,0x02,0xBD,0x42,0x27,0x51,0xE3,0x31,0x21,
0xA4,0x43,0x4F,0x15,0x2F,0x2B,0x2B,0x2A,0x3F,0x67,0x52,0x19,0xC5,0xD9,0x25,0xF6,
};

注意:上面的public键在导入前用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/sjcl-full@1.0.0/sjcl.min.js"></script>

至此,发布的数据验证成功。