如何将ECDSA密钥转换为PEM格式

12

我有一个来自我的以太坊钱包的私钥,并使用口令“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:

更新:我没有公钥,相反我想生成它,这样以后我也可以生成对应的以太坊地址。

2个回答

36
您声称您的原始密钥在OpenSSL的DER格式中,但实际上并不是。此外,您声称一个私钥是公钥,但事实并非如此,并声称它是密码加密的,这两种说法都是错误的:公钥从不被加密,而在OpenSSL的“传统”算法特定DER格式(对于ECC,在SECG SEC1中定义)中,私钥不能加密。 (另一方面,在PKCS8格式中的私钥可以使用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位的部分。

要构造没有公钥的 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 格式,但不包括公钥,而您需要公钥。要查看包括派生公钥的字段,请添加 -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格式密钥,需要将私钥的十六进制字符串(所有64个数字)和新显示的公钥的十六进制值一起,输入到我的第一个选项中。请注意,ECC公钥是一个曲线点,可以采用压缩或未压缩两种形式,此处生成的是未压缩的形式。如果您需要压缩形式,我稍后会添加。一个未压缩的secp256k1点是65个字节,以十六进制表示为130个十六进制数字。(openssl ec将其格式化为每行15个字节的4行,剩下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-----

ADDED 2019-02 for DavidS: 根据 k06a的回答,我的midstring(或仅供私人使用的选项的整个后缀)的第一部分a00706052b8104000a是上下文标签和长度a007,用于OID标记和长度0605,其中包含1.3.132.0.10,即secp256k1,而我的midstring的其余部分a144034200是一个上下文标签和长度,其中包含BITSTRING的标记长度和未使用位头,作为未压缩点的原始公钥。

要改用secp256r1或P-256或prime256v1,您需要将AlgId.OID更改为1.2.840.10045.3.1.7,它被编码为a00a06082a8648ce3d030107。 p256r1的私钥和公钥值与p256k1相同,但AlgId较长,因此您还需要更改外部SEQUENCE的长度。

30770201010420 privatekey32bytes # note 77 
a00a06082a8648ce3d030107 a144034200 publicpoint65bytes 

@HAßdøµ “原始”私钥通常由比特币(及相关)钱包使用,因为曲线是隐含的,实际上是32个字节,但您将其表示为16进制的64个十六进制数字(hexits); 其他表示法也可能存在,但是16进制最容易操作。所有64个hexits(或32个字节)都是原始密钥,在该表示中没有其他内容; 这就是为什么您需要添加其他部分来构建OpenSSL使用的SECG格式的原因。 - dave_thompson_085
作为第一步,我该如何提取私钥并将其导入OpenSSL?我尝试取最后25个字符,将其转换为二进制,然后转换为base64,与“-----BEGIN”和“-----END”连接起来,但仍无法被OPENSSL接受。 - H Aßdøµ
@HAßdøµ 我不知道你从哪里得到这个“25个字符”的信息,但请查看我的详细回答。 - dave_thompson_085
@dave_thompson_085,你是怎么得到 a00706052b8104000aa144034200 的?我需要同样的东西,但是针对 secp256r1。 - DavidS
非常棒的回答。两年后,一个陌生的灵魂真的很感激你的努力。 - salezica
显示剩余3条评论

4

椭圆曲线私钥格式:

ECPrivateKey ::= SEQUENCE {
 version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
 privateKey     OCTET STRING,
 parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
 publicKey  [1] BIT STRING OPTIONAL
}

所以publicKey可选的,理论上可以省略。

这是我的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 UPDATE:在最新的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

请用十六进制私钥替换***


2
由于我现在才注意到,在03 42和公钥(未压缩点)之间的00是BITSTRING中未使用的填充位数;请参见https://security.stackexchange.com/questions/129490/why-are-there-leading-0x00-bytes-in-the-subjectpublickey-field-of-a-der-x.509-。 - dave_thompson_085

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