openssl - 使用 openssl 验证 ECDSA 签名类型时 ASN1 编码例程错误
问题描述
我正在尝试验证外部方提供给我们的 SHA256 ECDSA 数字签名。他们已经在内部验证了他们的签名过程,但我们的尝试没有成功。我们在 openssl 验证期间反复收到asn1 encoding routines
错误,但我看不出签名或我们的流程有什么问题。
这是测试设置...公钥(pubkey.pem):
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOorVp0M8xien/r1/1Ln7TkSpzzcX
BL/MGRz66J1HSlEgBD5FwwpO1vo6jf/9azcrrrDdCi2NH9/cSDfv5D8gTA==
-----END PUBLIC KEY-----
被签名的消息是明文字符串:
HELLO
数字签名(signature.sig):
JJhwReHev8cxOsNKCR5t/Ee3WU9c7tkf9RuGNamXdpXQu9OL8ZKnsrblCO7vEmOXGKGrk6NsgA5JZpQhXO3A1Q==
我们采取的一般方法是:
# create message file
echo "HELLO" > hello.txt
#VERIFY
openssl dgst -sha256 -verify pubkey.pem -signature signature.sig hello.txt
响应是
Error Verifying Data
4655195756:error:0DFFF09B:asn1 encoding routines:CRYPTO_internal:too long:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/crypto/asn1/asn1_lib.c:143:
4655195756:error:0DFFF066:asn1 encoding routines:CRYPTO_internal:bad object header:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/crypto/asn1/tasn_dec.c:1113:
4655195756:error:0DFFF03A:asn1 encoding routines:CRYPTO_internal:nested asn1 error:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/crypto/asn1/tasn_dec.c:306:Type=ECDSA_SIG
或者,我们对签名进行了 base64 编码,base64 -D signature.sig > signature.bin
但得到了相同的错误响应。我也尝试过使用openssl pkeyutl
,但这也会导致asn1 encoding routines
错误。使用 ans1parse 解析签名产生:
openssl asn1parse -in signature.bin
Error: offset too large
显然,数字签名的格式我没有处理,但我看不到问题所在。
解决方案
您的 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
出于比较目的,我根据您的公钥使用的相同曲线创建了一个新的 ECDSA 私钥 (P-256):
$ 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:
$ 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 |\...#".U....|..Y|
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
which 告诉我们有一个序列开始。下一个字节是44
剩余数据的长度。接下来是02
整数的标记,然后是20
整数的长度(等于十进制的 32)。接下来的 32 个字节是整数(r
值)。然后我们有另一个02
字节(整数)和20
(长度为 32),后面是 32 个字节的s
值。
因此,如果我们将字节添加30 44 02 20
到二进制签名数据的前面,然后是前 32 个字节的数据,然后是02 20
接下来的 32 个字节,我们应该得到我们想要的......
...除了不幸的是它不是那么简单。s
你的价值有一个复杂的地方。您会注意到它以 byte 开头d0
。该字节设置了其最重要的位 - 在整数的 DER 编码中表示整数值为负。这不是我们想要的。为了解决这个问题,我们必须00
在值的前面添加一个额外的字节s
。
这样做会改变总长度,因此我们现在必须将这些字节添加到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”arg 会抑制输出中的换行符。也许换行符不应该包含在要为签名消化的数据中。所以,尝试一下:
$ openssl dgst -sha256 -verify pubkey.pem -signature signature2.bin hello2.txt
Verified OK
成功!