如何将 ECDSA 密钥转换为 PEM 格式
Posted
技术标签:
【中文标题】如何将 ECDSA 密钥转换为 PEM 格式【英文标题】:How to convert an ECDSA key to PEM format 【发布时间】:2018-01-04 18:17:01 【问题描述】:我有一个 myetherwallet 的私人原始密钥和一个 passphrase “testwallet”,现在我正在尝试按照这个答案使用 OpenSSL 将其转换为 PEM 格式。
echo "a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57" | xxd -r -p - | openssl ec -inform der -pubin -noout -passin pass:testwallet -text
但是出现了这个错误:
read EC key
unable to load Key
140084694296480:error:0D06B08E:asn1 encoding routines:ASN1_D2I_READ_BIO:not enough data:a_d2i_fp.c:247:
更新: 我没有公钥,我想生成它,所以以后我也可以生成以太坊地址对应..
【问题讨论】:
【参考方案1】:您声称您的原始密钥采用 OpenSSL 的 DER 格式,但事实并非如此。此外,您声称私钥是公钥,但事实并非如此,并声称它是密码加密的,这两种方式都是错误的:公钥从不加密,而私钥在 OpenSSL 的“传统”又名“传统”算法中是特定的DER 格式(对于 ECC,由SECG SEC1 定义)无法加密。 (PKCS8 格式的 OTOH 私钥可以在 DER 或 PEM 中进行密码加密,虽然 PEM 更方便。而 FWIW PKCS12 格式始终是密码加密的,并且始终是 DER。)
ECC(ECDSA、ECDH、ECMQV 等)密钥始终相对于某个“曲线”(更准确地说,是具有已识别生成器又名基点的曲线上的素数阶子组)。对于比特币,这是 secp256k1,但您的问题并没有说它仅限于比特币,而且这个答案需要使用其他曲线对其他应用程序进行修改。
如果您也有公钥(作为未压缩点),您可以简单地使用来自https://bitcoin.stackexchange.com/questions/66594/signing-transaction-with-ssl-private-key-to-pem 的解决方案。连接十六进制字符串:
a pre_string : 30740201010420
the privkey : (32 bytes as 64 hexits)
a mid_string : a00706052b8104000aa144034200 (identifies secp256k1)
the pubkey : (65 bytes as 130 hexits)
然后将十六进制转换为二进制并读取为 DER,或者将十六进制(可能通过二进制)转换为 base64 并用-----BEGIN/END EC PRIVATE KEY-----
行包装以使其成为 PEM。
如果您没有公钥,您可以稍微修改一下。连接十六进制字符串
302e0201010420 privkey_32bytes_64hexits a00706052b8104000a
并转换为二进制,然后读入openssl ec -inform d
。注意 OpenSSL 将从给定曲线的私钥派生公钥,但实际上不会将其存储在 PEM 输出中,因此不能保证使用 OpenSSL 以外的软件进行读取。您可能需要使用openssl ec -text [-noout]
(方便时在 PEM 或 DER 输入上)获取公钥值,然后返回并创建包含上述公钥的更完整的编码。
补充:由于您似乎没有理解答案中的用词,所以我会尽可能详细地说明这一点。
值a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57
是以十六进制表示的原始私钥。 secp256k1 私有值是 32 字节的二进制;当二进制以十六进制表示时,每个字节占用两个十六进制数字,因此 32 个字节占用 64 个十六进制数字。所有这些值都是原始私钥。没有任何由 25 个数字或 25 个字节组成的部分具有任何有用的意义。不要取这个值的任何 25-anything 部分。
要构造 没有公钥的私钥的 OpenSSL/SECG 表示,请将表示私钥的十六进制字符串(全部,未经修改)放在其他两个十六进制字符串之间我显示为第二个选项:
302e0201010420 a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57 a00706052b8104000a
然后将这个组合的十六进制字符串转换为二进制,并将结果读入openssl ec -inform d
:
$ echo 302e0201010420 a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57 a00706052b8104000a | xxd -r -p >48101258.1
$ openssl ec -inform d <48101258.1
read EC key
writing EC key
-----BEGIN EC PRIVATE KEY-----
MC4CAQEEIKFAvVB6VzYOL6UDKYwDWFTw3LJIvtq756FNs5IKqs9XoAcGBSuBBAAK
-----END EC PRIVATE KEY-----
结果是 PEM 格式 -- 但 PEM 格式不包括您指定的公钥。要查看包含派生公钥的字段,请添加-text
;要仅查看字段而不查看 PEM 输出,请添加 -noout
:
$ openssl ec -inform d <48101258.1 -text -noout
read EC key
Private-Key: (256 bit)
priv:
a1:40:bd:50:7a:57:36:0e:2f:a5:03:29:8c:03:58:
54:f0:dc:b2:48:be:da:bb:e7:a1:4d:b3:92:0a:aa:
cf:57
pub:
04:20:ea:6d:8c:e7:bc:bb:48:33:69:b2:91:1c:75:
e5:60:2a:34:28:be:44:96:e9:7f:14:ad:52:fd:4a:
6a:a0:e3:60:83:9c:6e:db:32:2a:22:55:7c:70:1e:
d0:fa:1e:06:cf:57:4f:be:17:bd:6a:85:51:69:c5:
65:96:72:cf:a9
ASN1 OID: secp256k1
现在,如果您想要一个 PEM 格式的密钥包括公钥,请使用 both 十六进制字符串作为私钥(全部 64 位)和新显示的公钥的十六进制值,并将它们插入我的 first 选项。另请注意,ECC 公钥是一个曲线点,可以有两种形式,压缩或未压缩;此处生成的表单是未压缩的。如果您需要压缩,我稍后会添加。未压缩形式的 secp256k1 点为 65 个字节,以十六进制表示为 130 个十六进制数字。 (openssl ec
格式为 4 行,每行 15 个字节,剩余 5 个字节。)
$ echo 30740201010420 a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57 a00706052b8104000aa144034200 \
> 04:20:ea:6d:8c:e7:bc:bb:48:33:69:b2:91:1c:75: e5:60:2a:34:28:be:44:96:e9:7f:14:ad:52:fd:4a: \
> 6a:a0:e3:60:83:9c:6e:db:32:2a:22:55:7c:70:1e: d0:fa:1e:06:cf:57:4f:be:17:bd:6a:85:51:69:c5: \
> 65:96:72:cf:a9 | xxd -r -p >48101258.2
$ # note xxd -r -p ignores the colons; other hex programs may need them removed instead
$ openssl ec -inform d <48101258.2
read EC key
writing EC key
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIKFAvVB6VzYOL6UDKYwDWFTw3LJIvtq756FNs5IKqs9XoAcGBSuBBAAK
oUQDQgAEIOptjOe8u0gzabKRHHXlYCo0KL5Elul/FK1S/UpqoONgg5xu2zIqIlV8
cB7Q+h4Gz1dPvhe9aoVRacVllnLPqQ==
-----END EC PRIVATE KEY-----
为 DavidS 添加了 2019-02:如 k06a's answer 中正确显示的那样
我的中字符串的第一部分(或我的私有选项的整个后缀)a00706052b8104000a
是一个上下文标记和长度 a007
用于 OID 标记和长度 0605
包含 2b8104000a
这是1.3.132.0.10 which is secp256k1 和
我的中间字符串 a144034200
的其余部分是一个上下文标签和长度,其中包含一个 BITSTRING 的标签长度和未使用位标头,它是作为未压缩点的原始公钥。
要改为 secp256r1 aka P-256 或 prime256v1,您需要将 AlgId.OID 更改为 1.2.840.10045.3.1.7,其编码为 a00a 0608 2a8648ce3d030107
。 p256r1 的 privatekey 和 publickey 值与 p256k1 的大小相同,但 AlgId 更长,因此您还需要更改外部 SEQUENCE 给出的长度
30770201010420 privatekey32bytes # note 77
a00a06082a8648ce3d030107 a144034200 publicpoint65bytes
【讨论】:
我不得不承认我现在迷路了,我的理解是这个密钥的最后25个字符a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57
是私钥吗?
@HAßdøµ '原始'私钥,通常由比特币(和相关)钱包使用,因为曲线是隐含的,实际上是 32 个字节,但你有它以十六进制表示为 64 个十六进制数字(hexits );其他表示是可能的,但十六进制最容易操作。所有 64 个十六进制(或 32 个字节)都是原始密钥,该表示中没有其他内容;这就是为什么您需要添加其他部分来构建 OpenSSL 使用的 SECG 格式。
那么作为第一步,我如何提取私有数据并将其导入 OpenSSL?我尝试取最后 25 个字符,将其转换为二进制然后 base64,与 -----BEGIN
和 -----END
连接,但它仍然无法被 OPENSSL 接受。
@HAßdøµ 我不知道你是从哪里得到这个“25 字符”的,但请看大大扩展的答案。
@dave_thompson_085,你是怎么找到a00706052b8104000aa144034200
的?我需要同样的东西,但对于 secp256r1。【参考方案2】:
Elliptic Curve Private Key Format:
ECPrivateKey ::= SEQUENCE
version INTEGER ecPrivkeyVer1(1) (ecPrivkeyVer1),
privateKey OCTET STRING,
parameters [0] ECParameters NamedCurve OPTIONAL,
publicKey [1] BIT STRING OPTIONAL
所以publicKey
是OPTIONAL
理论上可以忽略。
这是我的 DER secp256k1 私钥的示例:
30740201 01042092 E768CB72 0DC16924 27D156DB 39630748 0D1507B9 A4958450
2574B9A0 922F4BA0 0706052B 8104000A A1440342 00041954 9737B704 D1789A57
82E3430E 8259F904 71326081 054854D2 A5D096F9 686D05B0 30D98BA3 C60C056E
204CEF61 C0AC5B53 A9A6B9A0 5AFF9DA2 6CA4B65B 2E84
试图分解:
$ openssl asn1parse -inform DER -in <(echo "30740201 01042092 E768CB72 0DC16924 27D156DB 39630748 0D1507B9 A4958450 2574B9A0 922F4BA0 0706052B 8104000A A1440342 00041954 9737B704 D1789A57 82E3430E 8259F904 71326081 054854D2 A5D096F9 686D05B0 30D98BA3 C60C056E 204CEF61 C0AC5B53 A9A6B9A0 5AFF9DA2 6CA4B65B 2E84" | xxd -r -p)
ASN.1解析结果:
0:d=0 hl=2 l= 116 cons: SEQUENCE
2:d=1 hl=2 l= 1 prim: INTEGER :01
5:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:92E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4B
39:d=1 hl=2 l= 7 cons: cont [ 0 ]
41:d=2 hl=2 l= 5 prim: OBJECT :secp256k1
48:d=1 hl=2 l= 68 cons: cont [ 1 ]
50:d=2 hl=2 l= 66 prim: BIT STRING
详细(见https://bitcoin.stackexchange.com/a/66622/22979):
30 - ASN.1
74 - Length of all following bytes (116 bytes)
02 - Type (integer)
01 - Length of integer (1 byte)
01 - Value of integer (1)
04 - Type (octet string)
20 - Length of string (32 bytes)
92E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4B - Private Key
A0 - Tag 0
07 - Length of tag (7 bytes)
06 - Type (Object ID)
05 - Length of the Object ID (5 bytes)
2b 81 04 00 0a - The object ID of the curve secp256k1
A1 - Tag 1
44 - Length of tag (68 bytes)
03 - Type – Bit string
42 - Length of the bit string (66 bytes)
00 - ???
04 - Uncompressed Public Key
19549737B704D1789A5782E3430E8259F90471326081054854D2A5D096F9686D - Public Key X coord
05B030D98BA3C60C056E204CEF61C0AC5B53A9A6B9A05AFF9DA26CA4B65B2E84 - Public Key Y coord
我删除了公钥对象并将 ASN.1 长度从 116 字节 (0x74) 固定为 46 字节 (0x2e):
$ openssl asn1parse -inform DER -in <(echo "302E020101042092E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4BA00706052B8104000A" | xxd -r -p)
得到结果:
0:d=0 hl=2 l= 46 cons: SEQUENCE
2:d=1 hl=2 l= 1 prim: INTEGER :01
5:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:92E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4B
39:d=1 hl=2 l= 7 cons: cont [ 0 ]
41:d=2 hl=2 l= 5 prim: OBJECT :secp256k1
尝试获取公钥:
$ openssl ec -inform DER -in <(echo "302E020101042092E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4BA00706052B8104000A" | xxd -r -p)
结果:
read EC key
writing EC key
-----BEGIN EC PRIVATE KEY-----
MC4CAQEEIJLnaMtyDcFpJCfRVts5YwdIDRUHuaSVhFAldLmgki9LoAcGBSuBBAAK
-----END EC PRIVATE KEY-----
再试一次:
$ openssl ec -inform DER -text -in <(echo "302E020101042092E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4BA00706052B8104000A" | xxd -r -p)
结果:
read EC key
Segmentation fault: 11
我使用的是 OSX 系统 openssl
- 看起来是 LibreSSL 2.2.7
。
添加: 向 LibreSSL 报告了一个错误:https://github.com/libressl-portable/portable/issues/395 更新:在最新的 macOS 10.15.1 中预装的 openssl (LibreSSL 2.8.3) 已修复此错误。
然后我安装了最新的openssl:brew install openssl
/usr/local/Cellar/openssl/1.0.2n/bin/openssl ec -inform DER -text -noout -in <(echo "302E020101042092E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4BA00706052B8104000A" | xxd -r -p)
得到:
read EC key
Private-Key: (256 bit)
priv:
00:92:e7:68:cb:72:0d:c1:69:24:27:d1:56:db:39:
63:07:48:0d:15:07:b9:a4:95:84:50:25:74:b9:a0:
92:2f:4b
pub:
04:19:54:97:37:b7:04:d1:78:9a:57:82:e3:43:0e:
82:59:f9:04:71:32:60:81:05:48:54:d2:a5:d0:96:
f9:68:6d:05:b0:30:d9:8b:a3:c6:0c:05:6e:20:4c:
ef:61:c0:ac:5b:53:a9:a6:b9:a0:5a:ff:9d:a2:6c:
a4:b6:5b:2e:84
ASN1 OID: secp256k1
最终解决方案:
$ /usr/local/Cellar/openssl/1.0.2n/bin/openssl ec -inform DER -text -noout -in <(cat <(echo -n "302e0201010420") <(echo -n "***") <(echo -n "a00706052b8104000a") | xxd -r -p) 2>/dev/null | tail -6 | head -5 | sed 's/[ :]//g' | tr -d '\n' && echo
用十六进制私钥替换***
。
【讨论】:
因为我现在才注意到,03 42 和公钥(未压缩点)之间的 00 是 BITSTRING 中未使用的又名填充位的数量;见security.stackexchange.com/questions/129490/…以上是关于如何将 ECDSA 密钥转换为 PEM 格式的主要内容,如果未能解决你的问题,请参考以下文章