OpenSSL ECDSA签名长度超出预期

11
我正试图生成“原始”的、未编码的ECDSA签名,以便与加密芯片一起使用。目标是在主机PC上签署一些内容,然后将其发送到芯片进行验证。然而,我遇到了一点问题。我的理解是,ECDSA签名应该为64个字节(对于secp256v1)。当我使用芯片生成签名时,它确实是64个字节长。但是,当我使用openssl时,签名的长度为71个字节。签名的开始似乎是某种前缀,但我找不到任何关于它的数据。
以下是我尝试做所有事情的方式:
生成密钥:
openssl ecparam -genkey -name secp256r1 -noout -out privkeyv1.pem

生成要签名的“消息”:

echo -n "Hello World" > test.txt

我尝试了两种方法来签署这条消息。但两种方法都导致了相同的、意料之外的输出结果。

第一种方法 - 生成测试文件的sha256哈希值,然后对其进行签名:

sha256sum test.txt | cut -f 1 -d " " > hash

使用 pkutil 签名

openssl pkeyutl -sign -in hash -inkey privkeyv1.pem -out test_sig_meth1

方法2:使用openssl dgst签名

openssl dgst -sha256 -binary -sign privkeyv1.pem -out test_sig_meth2 test.txt 问题:这是xxd -p -c 256 test_sig_meth1的输出结果: 3045022000a86fb146d5f8f6c15b962640bc2d1d928f5e0f96a5924e4db2853ec8b66fb002210085431613d0a235db1adabc090cc1062a246a78941972e298423f4b3d081b48c8

这是xxd -p -c 256 test_sig_meth2的输出结果: 30450220693732cd53d9f2ba3deae213d74cdf69a00e7325a10ddc6a4445ff2b33f95e62022100b6d2561e3afba10f95247ed05f0c59620dc0913f0d798b4148e05c4116b6384e

可以看到,这两种方法都会生成一些字节,看起来像是头部字节(可能更长的30450220),但我不确定它们是用来做什么的,也不知道如何删除它们。供参考,这是在密码芯片上生成的同样方法的签名。如果去掉末尾的空字节填充,它是64字节。 4677AD09F2AF49D7445ED5D6AC7253ADC863EC6D5DB6D3CFBF9C6D3E221D0A7BA2561942524F46B590AEE749D827FBF80A961E884E3A7D85EC75FE48ADBC0BD00000000000000000000000

问题:如何使用openssl生成一个64字节的原始(未编码,没有头部)ECDSA签名,以便用于此方案?


1
我发现这两个命令的输出都是DER编码的。通过使用asn1parse -inform der命令,我能够找到组成签名(r和s)的整数。 - f41lurizer
1
这与C语言有什么关系? - alk
1
Stack Overflow是一个针对编程和开发问题的网站。这个问题似乎与编程或开发无关。请查看帮助中心的文章“我可以在这里提什么样的问题”(http://stackoverflow.com/help/on-topic)。也许[超级用户](http://superuser.com/)或[Unix和Linux堆栈交换](http://unix.stackexchange.com/)更适合提问。 - jww
1个回答

2
大多数芯片出于效率考虑,只会将和作为字节数组或八位字符串输出,其中每个和都与八位组(即密钥大小)相同。另一种方法是将和作为数字序列输出,因为最终这就是和的含义。使用ASN.1,这变成了一个INTEGER值序列。

要从这样的序列转换,您可以首先使用BER解析器进行BER解码以检索整数。然后实现I2OSP算法(整数到八位流基元),它需要值和字节/八位组中的密钥大小作为参数。该数字应为大整数形式,但由于ASN.1 BER编码的整数也是大整数,因此这没问题。基本上,如果数字太小,则必须左填充零字节。然后连接数字。

我不会介绍将字节数组转换为整数的OS2IP。请注意,如果您以BER格式对其进行编码,则整数不应左填充零字节。因此仍需要一些诡计。

因此,尽管签名形式发生了变化,但签名仍然有效;您可以在两种形式之间简单转换,只要使用正确的库进行操作,签名仍将验证。

我能够使用openssl asn1parse -inform der -in test_sig_meth2将密钥格式转换为r和s值。谢谢! - f41lurizer
@f41lurizer 好的,但不要忘记将其转换为八位字符串。这通常只是一个连接...直到更短的r或s值会让你感到困扰。 - Maarten Bodewes
我不确定您所说的将其转换为八位字符串是什么意思。这基本上只是将字符串进行十六进制编码,还是我漏掉了什么? - f41lurizer
一个八位字节串只是一个字节数组。Octet = byte, string = array。十六进制编码是为了使这些字节可读。不要将字节的表示与字节本身混淆。因此,如果您有一个256位曲线,则应编码为32个字节,如果您有一个521(不是打印错误),则应通过左填充大端编码的数字来编码为66个字节。 - Maarten Bodewes
因此,尽管签名形式改变,但签名仍然有效;您只需在一种形式和另一种形式之间进行转换,只要使用正确的库来完成工作,签名仍将验证。相关信息,请参见ECDSA用OpenSSL签名,在Crypto++中验证。 它在ASN.1 / DER(OpenSSL、Java和其他几个)和P1363格式(Crypto++和其他几个)之间进行转换。 .Net是个例外;它使用XML编码格式。 - jww

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接