你的signature.sig文件似乎是base64编码的。可以按照以下方式进行解码:
$ base64 -d signature.sig >signature.bin
让我们看看我们有什么:
$ hexdump -C signature.bin
00000000 24 98 70 45 e1 de bf c7 31 3a c3 4a 09 1e 6d fc |$.pE....1:.J..m.|
00000010 47 b7 59 4f 5c ee d9 1f f5 1b 86 35 a9 97 76 95 |G.YO\......5..v.|
00000020 d0 bb d3 8b f1 92 a7 b2 b6 e5 08 ee ef 12 63 97 |..............c.|
00000030 18 a1 ab 93 a3 6c 80 0e 49 66 94 21 5c ed c0 d5 |.....l..If.!\...|
00000040
为了比较,我基于与您的公钥相同的曲线(P-256)创建了一个新的ECDSA私钥:
$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out key.pem
然后使用它对一些数据进行了签名:
$ echo "HELLO" > hello.txt
$ openssl dgst -sha256 -sign key.pem -out hello.sig hello.txt
$ openssl asn1parse -in hello.sig -inform DER
0:d=0 hl=2 l= 68 cons: SEQUENCE
2:d=1 hl=2 l= 32 prim: INTEGER :2C1599C7765B047A2E98E2265CF6DB91232200559909D7F97CA3E859A39AC02C
36:d=1 hl=2 l= 32 prim: INTEGER :14E748DF692A8A7A2E41F984497782FF03F970DDB6591CCC68C71704B959A480
所以您会注意到这里有一个序列中的两个整数,每个整数都恰好32字节长。这对应于ECDSA_SIG ASN.1定义:
ECDSA-Sig-Value ::= SEQUENCE { r INTEGER, s INTEGER }
原始ECDSA签名由两个整数“r”和“s”组成。OpenSSL希望它们被包装在DER编码的表示中。但是,正如您已经发现的,您的签名不是有效的DER格式。它确实有64个字节长 - 这表明它由2个32个字节的整数串联而成。
为了完成此练习,我们可以使用十六进制编辑器将原始的r和s值转换为DER格式。让我们来看一下我之前创建的hello.sig文件的十六进制转储:
$ hexdump -C hello.sig
00000000 30 44 02 20 2c 15 99 c7 76 5b 04 7a 2e 98 e2 26 |0D. ,...v[.z...&|
00000010 5c f6 db 91 23 22 00 55 99 09 d7 f9 7c a3 e8 59 |\...
00000020 a3 9a c0 2c 02 20 14 e7 48 df 69 2a 8a 7a 2e 41 |...,. ..H.i*.z.A|
00000030 f9 84 49 77 82 ff 03 f9 70 dd b6 59 1c cc 68 c7 |..Iw....p..Y..h.|
00000040 17 04 b9 59 a4 80 |...Y..|
00000046
首先有一个字节是30
,表示这是一个序列。接下来的字节是44
,表示剩余数据的长度。然后是02
,表示一个整数标记,后面是20
(十进制中等于32),表示该整数的长度。接下来的32个字节就是整数(即r
值)。接着是另一个02
字节(整数)和20
(长度为32),随后是s
值的32个字节。
因此,如果我们将字节30 44 02 20
添加到二进制签名数据的前面,然后是前32个字节的数据,接着是02 20
,最后是接下来的32个字节的s
值,我们就可以得到我们想要的结果...
...但很遗憾,情况并不是那么简单。你会注意到s
值以d0
字节开头。这个字节的最高位被设置为1 - 在DER编码中表示该整数值为负数。这不是我们想要的。为了解决这个问题,我们必须在s
值的前面再加上一个00
字节。
这样做会改变总长度,因此我们现在必须在开头添加这些字节:30 45 02 20
,然后是来自签名数据的前32个字节,接着是02 21 00
,最后是签名数据的下一个32个字节。我在十六进制编辑器中完成了这个过程,得到了以下结果:
$ hexdump -C signature2.bin
00000000 30 45 02 20 24 98 70 45 e1 de bf c7 31 3a c3 4a |0E. $.pE....1:.J|
00000010 09 1e 6d fc 47 b7 59 4f 5c ee d9 1f f5 1b 86 35 |..m.G.YO\......5|
00000020 a9 97 76 95 02 21 00 d0 bb d3 8b f1 92 a7 b2 b6 |..v..!..........|
00000030 e5 08 ee ef 12 63 97 18 a1 ab 93 a3 6c 80 0e 49 |.....c......l..I|
00000040 66 94 21 5c ed c0 d5 |f.!\...|
00000047
让我们检查一下这看起来是否合理:
$ openssl asn1parse -in signature2.bin -inform DER
0:d=0 hl=2 l= 69 cons: SEQUENCE
2:d=1 hl=2 l= 32 prim: INTEGER :24987045E1DEBFC7313AC34A091E6DFC47B7594F5CEED91FF51B8635A9977695
36:d=1 hl=2 l= 33 prim: INTEGER :D0BBD38BF192A7B2B6E508EEEF12639718A1AB93A36C800E496694215CEDC0D5
现在让我们尝试验证签名:
$ openssl dgst -sha256 -verify pubkey.pem -signature signature2.bin hello.txt
Verification Failure
糟糕,进展很近,却又遥不可及。但至少我们已经解决了ASN.1错误。那么为什么它还没运作起来呢?凭直觉我试了这个:
echo -n "HELLO" > hello2.txt
echo命令中的“-n”参数可以抑制输出中的换行符。也许在用于签名处理的数据中不应包含换行符。因此,试一试:
$ openssl dgst -sha256 -verify pubkey.pem -signature signature2.bin hello2.txt
Verified OK
成功!
00
值。当然,它们不像以初始位设置为1的值那样普遍,但它们仍然相当常见(对于第一个字节,256个中有一个,对于两个字节设置为0000
的情况,65536个中有一个等)。 - Maarten Bodewes00
字节,则必须将其删除。 - Matt Caswell