使用 BearSSL 验证使用 OpenSSL 创建的签名

Verifying signature created using OpenSSL with BearSSL

我正在尝试使用 BearSSL 在嵌入式设备上验证使用 OpenSSL 创建的 ECDSA 签名。

首先,我使用 OpenSSL 创建了一个私钥并提取了 public 密钥:

$ openssl ecparam -name secp256r1 -genkey -noout -out private.pem
$ openssl ec -in private.pem -pubout -out public.pem

然后我提取了原始 public 密钥:

$ openssl ec -noout -text -inform PEM -in public.pem -pubin
read EC key
Public-Key: (256 bit)
pub:
    04:28:4b:54:a4:d4:92:6c:82:2d:da:8a:e1:be:4b:
    49:61:5d:91:2b:2d:f5:f2:66:f8:9b:d1:be:cb:fb:
    db:fc:4f:68:cf:52:68:55:36:53:0f:8e:8d:69:3f:
    40:3a:06:62:ad:5b:5a:66:e6:1d:31:c6:13:08:f3:
    4f:94:7b:59:7a

我有一个 text.txt,其中只包含 hello world,并使用以下命令对其进行签名:

$ cat text.txt
hello world
$ openssl dgst -sha256 -sign private.pem text.txt > signature

现在包含 ASN1 格式的签名,长度为 72 字节,符合预期:

$ hexdump signature
0000000 4530 2002 ac54 51af 8ac0 cee8 dc74 4120
0000010 105c b65d a085 06c5 8e9f 1527 12f5 8e50
0000020 9d19 9b30 2102 a900 d2a5 343e 3a10 0bdd
0000030 e0a8 82f8 de2a 4f2d 51bf a775 bc42 2d2e
0000040 19c0 874f d85e 004b

现在进入嵌入部分。我首先包含数据、签名和 public 密钥:

uint8_t text[] = "hello world\n";
size_t textlen = 12;

uint8_t signature[] = {
  0x45, 0x30, 0x20, 0x02, 0xac, 0x54, 0x51, 0xaf, 0x8a, 0xc0, 0xce, 0xe8, 0xdc, 0x74, 0x41, 0x20,
  0x10, 0x5c, 0xb6, 0x5d, 0xa0, 0x85, 0x06, 0xc5, 0x8e, 0x9f, 0x15, 0x27, 0x12, 0xf5, 0x8e, 0x50,
  0x9d, 0x19, 0x9b, 0x30, 0x21, 0x02, 0xa9, 0x00, 0xd2, 0xa5, 0x34, 0x3e, 0x3a, 0x10, 0x0b, 0xdd,
  0xe0, 0xa8, 0x82, 0xf8, 0xde, 0x2a, 0x4f, 0x2d, 0x51, 0xbf, 0xa7, 0x75, 0xbc, 0x42, 0x2d, 0x2e,
  0x19, 0xc0, 0x87, 0x4f, 0xd8, 0x5e, 0x00, 0x4b };

static const uint8_t public_bytes[] = {
  0x04, 0x28, 0x4b, 0x54, 0xa4, 0xd4, 0x92, 0x6c, 0x82, 0x2d, 0xda, 0x8a, 0xe1, 0xbe, 0x4b,
  0x49, 0x61, 0x5d, 0x91, 0x2b, 0x2d, 0xf5, 0xf2, 0x66, 0xf8, 0x9b, 0xd1, 0xbe, 0xcb, 0xfb,
  0xdb, 0xfc, 0x4f, 0x68, 0xcf, 0x52, 0x68, 0x55, 0x36, 0x53, 0x0f, 0x8e, 0x8d, 0x69, 0x3f,
  0x40, 0x3a, 0x06, 0x62, 0xad, 0x5b, 0x5a, 0x66, 0xe6, 0x1d, 0x31, 0xc6, 0x13, 0x08, 0xf3,
  0x4f, 0x94, 0x7b, 0x59, 0x7a };

static const br_ec_public_key public_key = {
  .curve = BR_EC_secp256r1,
  .q = (void *)public_bytes,
  .qlen = sizeof(public_bytes)
};

此外,出于偏执,我比较了 text.txt 和我的字符串的 md5 总和,然后再进行快速检查:

$ md5sum text.txt
6f5902ac237024bdd0c176cb93063dc4  text.txt
uint8_t sum[br_md5_SIZE];
br_md5_context md5ctx;
br_md5_init(&md5ctx);
br_md5_update(&md5ctx, text, textlen);
br_md5_out(&md5ctx, sum); // sum is the same as md5sum command output

我之前告诉 OpenSSL 使用 sha256 对使用 ECDSA 的签名过程的有效负载进行哈希处理,所以我现在在 BearSSL 中做同样的事情并尝试使用 secp256r1 验证签名:

br_sha256_context sha256ctx;
br_sha256_init(&sha256ctx);
br_sha256_update(&sha256ctx, text, textlen);
br_sha256_out(&sha256ctx, hash);

uint32_t result = br_ecdsa_i15_vrfy_asn1(&br_ec_prime_i15, hash, sizeof(hash), &public_key, signature, sizeof(signature));

我的期望是验证有效,因为我给它相同的散列、散列函数、曲线、相应的 public 密钥和签名。但是,这不起作用。我可能遗漏了一些明显但无法解决的问题。

签名文件内容显示为

$ hexdump signature
0000000 4530 2002 ac54 51af 8ac0 cee8 dc74 4120
...

显示为 16 位值。

C程序中的签名定义为一个8位数值的数组

uint8_t signature[] = {
  0x45, 0x30, 0x20, 0x02, 0xac, 0x54, 0x51, 0xaf, 0x8a, 0xc0, 0xce, 0xe8, 0xdc, 0x74, 0x41, 0x20,
...
};

根据字节顺序,这可能正确也可能不正确。 4530对应的是4530还是3045

使用小端字节序,十六进制转储

4530 2002 ...

将对应于 (*)

uint8_t signature[] = {
  0x30, 0x45, 0x02, 0x20, ...
};

我建议将十六进制转储显示为 8 位值,例如通过使用

od -t x1 signature

并在必要时修复 C 代码中的数组初始化。

根据dave_thompson_085的评论,正确的字节顺序是0x30、0x45,所以建议的修复(*)是解决方案。

And an ECDSA signature on a 256-bit group definitely starts with first tag=SEQUENCE+constructed (always 0x30) then body length usually 68 to 70 (0x44 to 0x46)