如何让OpenSSL生成的EC签名可被Subtle Crypto API验证?
问题
我有一个包含由SubtleCrypto.sign()的ECDSA算法和SHA512摘要生成的EC签名的小型JSON文件。现在我想把手动生成签名的流程自动化,且必须基于nginx:alpine基础镜像。我已经安装了OpenSSL,但发现SubtleCrypto.verify()需要IEEE P1363格式的签名,而OpenSSL生成的是ASN.1格式。有没有办法让OpenSSL生成的签名能在浏览器中正常验证?
注意:我在浏览器中创建了EC密钥对,使用SubtleCrypto.sign()生成签名时验证正常。随后我将私钥以PKCS#8格式导出为PEM文件,内容如下:
-----BEGIN EC PRIVATE KEY----- <Base64 encoded content here...> -----END EC PRIVATE KEY-----
我使用以下命令为仅包含{}的data.json生成Base64格式的签名:
$ openssl dgst -sha512 -sign private.pem data.json | base64 MIGHAkEZ0YthoqLqCW1ll0MvPEYdhoz+p1zxQnLt0sl0rSvD8eI0BdAeQUqS1fUlNi8O39ClI6OI JDZJqJ3OkTFkMyqzaAJCAT8koJnhqv2PUHla7EWV/qVIBb9LtPovQODl0+CsBQlkGO+TKkX3X57+ rpOZMqYv1tIOAz1m5ry0sOo0hGSe4c8X
之后我通过ASN.1解码网站获取r和s的十进制值,并参考Stack Overflow的回答转换为十六进制值:
r = 346169932049245619871744113024266741326899713498883047336224784039276978051811648336988371154157537871201508197563500632048282301939214838670217488915673960 s = 4279009059525041370739088323750483074176965727856497966083564113228214038964554024207556176396942898795827323835166902701178318351093672293528073206493204247 hex_val = 19D18B61A2A2EA096D6597432F3C461D868CFEA75CF14272EDD2C974AD2BC3F1E23405D01E414A92D5F525362F0EDFD0A523A388243649A89DCE913164332AB368013F24A099E1AAFD8F50795AEC4595FEA54805BF4BB4FA2F40E0E5D3E0AC05096418EF932A45F75F9EFEAE939932A62FD6D20E033D66E6BCB4B0EA3484649EE1CF17
得到的Base64签名为:
GdGLYaKi6gltZZdDLzxGHYaM/qdc8UJy7dLJdK0rw/HiNAXQHkFKktX1JTYvDt/QpSOjiCQ2SaidzpExZDMqs2gBPySgmeGq/Y9QeVrsRZX+pUgFv0u0+i9A4OXT4KwFCWQY75MqRfdfnv6uk5kypi/W0g4DPWbmvLSw6jSEZJ7hzxc=
但该签名在浏览器中验证失败。
解决方案
无需手动解码ASN.1格式签名,OpenSSL本身支持直接生成或转换出符合要求的IEEE P1363格式签名,具体步骤如下:
方法1:直接生成IEEE P1363格式签名
使用pkeyutl命令搭配-raw参数,可直接生成r和s拼接的P1363二进制签名,再转Base64即可:
# 生成二进制P1363格式签名,再转Base64 openssl pkeyutl -sign -in data.json -inkey private.pem -digest sha512 -raw | base64
方法2:将已有ASN.1签名转换为P1363格式
如果已经生成了ASN.1格式的签名,可通过以下步骤转换:
- 将Base64签名解码为二进制文件:
echo "MIGHAkEZ0YthoqLqCW1ll0MvPEYdhoz+p1zxQnLt0sl0rSvD8eI0BdAeQUqS1fUlNi8O39ClI6OIJDZJqJ3OkTFkMyqzaAJCAT8koJnhqv2PUHla7EWV/qVIBb9LtPovQODl0+CsBQlkGO+TKkX3X57+rpOZMqYv1tIOAz1m5ry0sOo0hGSe4c8X" | base64 -d > signature.asn1
- 用
asn1parse提取r和s的偏移量与长度:
openssl asn1parse -in signature.asn1
输出会显示r和s的offset(偏移位置)和length(长度),示例如下:
0:d=0 hl=2 l= 130 cons: SEQUENCE 2:d=1 hl=2 l= 64 prim: INTEGER :19D18B61A2A2EA096D6597432F3C461D868CFEA75CF14272EDD2C974AD2BC3F1E23405D01E414A92D5F525362F0EDFD0A523A388243649A89DCE913164332AB368 68:d=1 hl=2 l= 64 prim: INTEGER :013F24A099E1AAFD8F50795AEC4595FEA54805BF4BB4FA2F40E0E5D3E0AC05096418EF932A45F75F9EFEAE939932A62FD6D20E033D66E6BCB4B0EA3484649EE1CF17
- 提取r和s的二进制数据并拼接成P1363格式,再转Base64:
# 提取r(对应offset=2,length=64) xxd -s 2 -l 64 -p signature.asn1 | xxd -r -p > r.bin # 提取s(对应offset=68,length=64) xxd -s 68 -l 64 -p signature.asn1 | xxd -r -p > s.bin # 拼接r和s得到P1363格式签名,再转Base64 cat r.bin s.bin | base64
关键注意事项
- 确保r和s的长度与使用的椭圆曲线匹配:比如secp521r1(P-521)曲线的r/s各占66字节,secp256r1(P-256)各占32字节;如果提取的r/s长度不足,需在前面补0字节保证长度符合要求。
- 在
nginx:alpine镜像中,OpenSSL是默认预装的,xxd可通过apk add xxd快速安装,无需额外复杂依赖。
内容的提问来源于stack exchange,提问作者Hodossy Szabolcs
相关产品推荐
相关产品推荐

