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

How to convert an ECDSA key to PEM format

我有一个 myetherwallet 的原始私钥,带有 密码 "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:

更新: 我没有 public 密钥,相反我想生成它以便稍后我也可以生成对应的以太坊地址..

您声称您的原始密钥是 OpenSSL 的 DER 格式,但事实并非如此。您还声称私钥是 public 密钥,但事实并非如此,并声称它是 password-encrypted 这两种方式都是错误的:public keys are never encrypted and private keys in OpenSSL's 'traditional' 又名 'legacy' algorithm-specific DER 格式(对于 ECC,由 SECG SEC1 定义)无法加密。 (PKCS8 格式的 OTOH 私钥在 DER 或 PEM 中可以是 password-encrypted,尽管 PEM 更方便。FWIW PKCS12 格式始终是 password-encrypted,并且始终是 DER。)

ECC(ECDSA、ECDH、ECMQV 等)密钥总是相对于一些'curve'(更确切地说,prime-order曲线上的子群带有一个确定的生成器 aka 基点)。对于比特币,这是 secp256k1,但你的问题并没有说它仅限于比特币,这个答案需要修改以用于使用其他曲线的其他应用程序。

如果您还有 public 键(作为未压缩点),您可以简单地使用 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。

如果你没有public键,你可以稍微修改一下。连接十六进制字符串

302e0201010420 privkey_32bytes_64hexits a00706052b8104000a 

并转换为二进制,然后读入 openssl ec -inform d 。注意 OpenSSL 将从给定曲线的私钥派生出 public 密钥,但实际上不会将其存储在 PEM 输出中,因此不能保证使用 OpenSSL 以外的软件进行读取。您可能需要使用 openssl ec -text [-noout](在方便的 PEM 或 DER 输入上)来获取 public 键值,然后返回并创建包含 public 键的更完整的编码,如上.


已添加:由于您似乎没有理解答案中的词语,我会尽可能详细地说明。

a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57 是以十六进制表示的原始私钥。 secp256k1 私有值是 32 字节的二进制;当二进制以十六进制表示时,每个字节占用两个十六进制数字,因此 32 个字节占用 64 个十六进制数字。所有这些值都是原始私钥。没有任何由 25 位数字或 25 个字节组成的部分具有任何有用的含义。不要取该值的任何 25 位部分。

要构建没有 public 密钥 的 私钥的 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 格式不包括 public 密钥,您表示需要。要查看包含派生的 public 键的字段,请添加 -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-format 键 包括 public 键 ,请取 both 十六进制字符串对于私钥(所有 64 位数字)和 public 密钥的 newly-shown 十六进制值,并将它们插入我的 first 选项。另请注意,ECC public 密钥是一个曲线点,可以有两种形式,压缩或未压缩;此处生成的表格是未压缩的。如果您需要压缩,我稍后会添加。未压缩形式的 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

中正确显示
  • 我的中弦的第一部分(或我的 private-only 选项的整个后缀)a00706052b8104000a 是一个 context-tag 和长度 a007一个 OID 标签和长度 0605 包含 2b8104000a1.3.132.0.10 which is secp256k1

  • 我的 midstring 的其余部分 a144034200 是上下文标记和包含标记长度的长度,unused-bits header 是原始的 BITSTRING public作为未压缩点的密钥。

要执行 secp256r1 又名 P-256 或 prime256v1,您需要将 AlgId.OID 更改为 1.2.840.10045.3.1.7,编码为 a00a 0608 2a8648ce3d030107. p256r1 的私钥和 publickey 值与 p256k1 的大小相同,但 AlgId 更长,因此您还需要更改外部 SEQUENCE 的长度 giving

30770201010420 privatekey32bytes # note 77 
a00a06082a8648ce3d030107 a144034200 publicpoint65bytes 

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
}

所以publicKeyOPTIONAL理论上可以漏掉

这是我的 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

我删除了 Public 密钥对象并将 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

正在尝试获取 Public 密钥:

$ 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

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