如何存储/检索 RSA 公钥/私钥

Posted

技术标签:

【中文标题】如何存储/检索 RSA 公钥/私钥【英文标题】:How to store/retrieve RSA public/private key 【发布时间】:2010-11-14 16:16:46 【问题描述】:

我想使用 RSA 公钥加密。存储或检索私钥和公钥的最佳方式是什么? XML 在这里是个好主意吗?

如何获得钥匙?

RSAParameters privateKey = RSA.ExportParameters(true);
RSAParameters publicKey = RSA.ExportParameters(false);

因为 RSAParameters 有以下成员:D, DP, DQ, Exponent, InverseQ, Modulus, P, Q

哪一个是关键?

【问题讨论】:

【参考方案1】:

我想@Ian Boyd 的回答并不准确,格式应该是 SSH2,而不是 OpenSSH,因为为 SSH2 定义的 RFC4716,OpenSSH 格式是专有的:

注意:OpenSSH 使用四个带空格的破折号 (---- ) 而不是五个破折号且没有空格 (-----)。

【讨论】:

【参考方案2】:

我成功的是将密钥存储为 XML。 RSACryptoServiceProvider 中有两种方法:ToXmlString 和 FromXmlString。 ToXmlString 将返回一个 XML 字符串,该字符串要么仅包含公钥数据,要么包含公钥和私钥数据,具体取决于您设置其参数的方式。当提供仅包含公钥数据或同时包含公钥和私钥数据的 XML 字符串时,FromXmlString 方法将使用适当的密钥数据填充 RSACryptoServiceProvider。

【讨论】:

很抱歉提出这个问题,但为什么要将私钥存储在 XML 中?使用 RSA 密钥容器是不是有点过头了? @LOSTCODER XML 的价值在于至少有 7 个其他标准来存储 RSA 密钥材料。至少使用 XML,您可以将值(modulusexponent 用于公钥、DPQDPDQInverseQ)简单地视为 base64 编码字节数据。从那里您可以将其转换回 PKCS#1、PKCS#7、PKCS#8、PFX、证书、OpenSSH、OpenSSL 等。简而言之:XML 是可移植的,而其他一切都是复杂的专有(例如 ASN.1 DER, which doesn't even have well defined standards)【参考方案3】:

我想指出一些事情,作为对 ala 的评论的回应,询问是否:

公钥 = 模数 + 指数

这完全正确。有几种方法可以存储这个exponent + modulus。对标准的第一次尝试是在 RFC 3447(公钥加密标准 (PKCS) #1:RSA Cryptography Specifications Version 2.1)中,它定义了一个名为 RSAPublicKey 的公钥结构:

RSAPublicKey ::= SEQUENCE 
      modulus           INTEGER,  -- n
      publicExponent    INTEGER   -- e
  

同样的 RFC 继续声明您应该使用 DER flavor of ASN.1 encoding 来存储公钥。我有一个示例公钥:

publicExponent:65537 (按照惯例,所有 RSA 公钥都使用 65537 作为其指数) 模数0xDC 67 FA F4 9E F2 72 1D 45 2C B4 80 79 06 A0 94 27 50 8209 DD 67 CE 57 B8 6C 4A 4F 40 9F D2 D1 69 FB 995D 85 0C 07 A1 F9 47 1B 56 16 6E F6 7F B9 CF 2A 58 36 37 99 29 AA 4F A8 12 E8 4F C7 82 2B 9D 72 2A 9C DE 6F C2 EE 12 6D CF F0 F2 B8 C4 DD 7C 5C 1A C8 17 51 A9 AC DF 08 22 04 9D 2B D7 F9 4B 09 DE 9A EB 5C 51 1A D8 F8 F9 56 9E F8 FB 37 9B 3F D3 74 65 24 0D FF 34 75 57 A4 F5 BF 55

此公钥的 DER ASN.1 编码为:

30 81 89          ;SEQUENCE (0x89 bytes = 137 bytes)
|  02 81 81       ;INTEGER (0x81 bytes = 129 bytes)
|  |  00          ;leading zero of INTEGER
|  |  DC 67 FA
|  |  F4 9E F2 72 1D 45 2C B4  80 79 06 A0 94 27 50 82
|  |  09 DD 67 CE 57 B8 6C 4A  4F 40 9F D2 D1 69 FB 99
|  |  5D 85 0C 07 A1 F9 47 1B  56 16 6E F6 7F B9 CF 2A
|  |  58 36 37 99 29 AA 4F A8  12 E8 4F C7 82 2B 9D 72
|  |  2A 9C DE 6F C2 EE 12 6D  CF F0 F2 B8 C4 DD 7C 5C
|  |  1A C8 17 51 A9 AC DF 08  22 04 9D 2B D7 F9 4B 09
|  |  DE 9A EB 5C 51 1A D8 F8  F9 56 9E F8 FB 37 9B 3F
|  |  D3 74 65 24 0D FF 34 75  57 A4 F5 BF 55
|  02 03          ;INTEGER (0x03 = 3 bytes)
|  |  01 00 01    ;hex for 65537. see it?

如果你把上面的整个 DER ASN.1 编码为modulus+exponent:

30 81 89 02 81 81 00 直流 67 发 F4 9E F2 72 1D 45 2C B4 80 79 06 A0 94 27 50 82 09 DD 67 CE 57 B8 6C 4A 4F 40 9F D2 D1 69 FB 99 5D 85 0C 07 A1 F9 47 1B 56 16 6E F6 7F B9 CF 2A 58 36 37 99 29 AA 4F A8 12 E8 4F C7 82 2B 9D 72 2A 9C DE 6F C2 EE 12 6D CF F0 F2 B8 C4 DD 7C 5C 1A C8 17 51 A9 AC DF 08 22 04 9D 2B D7 F9 4B 09 DE 9A EB 5C 51 1A D8 F8 F9 56 9E F8 FB 37 9B 3F D3 74 65 24 0D FF 34 75 57 A4 F5 BF 55 02 03 01 00 01

然后你 PEM 对其进行编码(即 base64):

MIGJAoGBANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXY
UMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE
3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dV
ek9b9VAgMBAAE=

将base64编码的数据包装在:

-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXY
UMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE
3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dV
ek9b9VAgMBAAE=
-----END RSA PUBLIC KEY-----

这就是您获得 PEM DER ASN.1 PKCS#1 RSA 公钥的方式。


下一个标准是RFC 4716(安全外壳 (SSH) 公钥文件格式)。它们在指数和模数之前包含一个算法标识符 (ssh-rsa):

string    "ssh-rsa"
mpint     e
mpint     n

他们不想使用 DER ASN.1 编码(因为它非常复杂),而是选择了 4 字节长度前缀

00000007                 ;7 byte algorithm identifier
73 73 68 2d 72 73 61     ;"ssh-rsa"
00000003                 ;3 byte exponent
01 00 01                 ;hex for 65,537 
00000080                 ;128 byte modulus
DC 67 FA F4 9E F2 72 1D  45 2C B4 80 79 06 A0 94 
27 50 82 09 DD 67 CE 57  B8 6C 4A 4F 40 9F D2 D1 
69 FB 99 5D 85 0C 07 A1  F9 47 1B 56 16 6E F6 7F 
B9 CF 2A 58 36 37 99 29  AA 4F A8 12 E8 4F C7 82 
2B 9D 72 2A 9C DE 6F C2  EE 12 6D CF F0 F2 B8 C4 
DD 7C 5C 1A C8 17 51 A9  AC DF 08 22 04 9D 2B D7 
F9 4B 09 DE 9A EB 5C 51  1A D8 F8 F9 56 9E F8 FB 
37 9B 3F D3 74 65 24 0D  FF 34 75 57 A4 F5 BF 55

获取上面的整个字节序列并对其进行base-64编码:

AAAAB3NzaC1yc2EAAAADAQABAAAAgNxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hs
Sk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4S
bc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80
dVek9b9V

并将其包装在 OpenSSH 标头和预告片中:

---- BEGIN SSH2 PUBLIC KEY ----
AAAAB3NzaC1yc2EAAAADAQABAAAAgNxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hs
Sk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4S
bc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80
dVek9b9V
---- END SSH2 PUBLIC KEY ----

注意:OpenSSH 使用四个带有空格的破折号 (---- ),而不是五个破折号和没有空格的 (-----)。


下一个标准是RFC 2459(Internet X.509 公钥基础结构证书和 CRL 配置文件)。他们采用 PKCS#1 公钥格式:

RSAPublicKey ::= SEQUENCE 
      modulus           INTEGER,  -- n
      publicExponent    INTEGER   -- e
  

并将其扩展为包含算法标识符前缀(如果您想使用公钥加密算法其他而不是 RSA):

SubjectPublicKeyInfo  ::=  SEQUENCE  
    algorithm            AlgorithmIdentifier,
    subjectPublicKey     RSAPublicKey 

RSA 的“算法标识符”1.2.840.113549.1.1.1,来自:

1 - ISO 分配的 OID 1.2 - ISO 成员机构 1.2.840 - 美国 1.2.840.113549-RSADSI 1.2.840.113549.1 - PKCS 1.2.840.113549.1.1 - PKCS-1

X.509 是一个糟糕的标准,它定义了一种将OID 编码为十六进制的极其复杂的方法,但最终 X.509 SubjectPublicKeyInfo RSA 公钥的 DER ASN.1 编码是:

30 81 9F            ;SEQUENCE (0x9f bytes = 159 bytes)
|  30 0D            ;SEQUENCE (0x0d bytes = 13 bytes)
|  |  06 09         ;OBJECT_IDENTIFIER (0x09 = 9 bytes)
|  |  2A 86 48 86   ;Hex encoding of 1.2.840.113549.1.1
|  |  F7 0D 01 01 01
|  |  05 00         ;NULL (0 bytes)
|  03 81 8D 00      ;BIT STRING (0x8d bytes = 141 bytes)
|  |  30 81 89          ;SEQUENCE (0x89 bytes = 137 bytes)
|  |  |  02 81 81       ;INTEGER (0x81 bytes = 129 bytes)
|  |  |  00          ;leading zero of INTEGER
|  |  |  DC 67 FA
|  |  |  F4 9E F2 72 1D 45 2C B4  80 79 06 A0 94 27 50 82
|  |  |  09 DD 67 CE 57 B8 6C 4A  4F 40 9F D2 D1 69 FB 99
|  |  |  5D 85 0C 07 A1 F9 47 1B  56 16 6E F6 7F B9 CF 2A
|  |  |  58 36 37 99 29 AA 4F A8  12 E8 4F C7 82 2B 9D 72
|  |  |  2A 9C DE 6F C2 EE 12 6D  CF F0 F2 B8 C4 DD 7C 5C
|  |  |  1A C8 17 51 A9 AC DF 08  22 04 9D 2B D7 F9 4B 09
|  |  |  DE 9A EB 5C 51 1A D8 F8  F9 56 9E F8 FB 37 9B 3F
|  |  |  D3 74 65 24 0D FF 34 75  57 A4 F5 BF 55
|  |  02 03          ;INTEGER (0x03 = 3 bytes)
|  |  |  01 00 01    ;hex for 65537. see it?

您可以在解码的 ASN.1 中看到他们如何在旧的 RSAPublicKey 前加上 OBJECT_IDENTIFIER

采用上述字节和 PEM(即 base-64)对其进行编码:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcZ/r0nvJyHUUstIB5BqCUJ1CC
Cd1nzle4bEpPQJ/S0Wn7mV2FDAeh+UcbVhZu9n+5zypYNjeZKapPqBLoT8eCK51y
Kpzeb8LuEm3P8PK4xN18XBrIF1GprN8IIgSdK9f5SwnemutcURrY+PlWnvj7N5s/
03RlJA3/NHVXpPW/VQIDAQAB

然后标准是用类似于 RSA PKCS#1 的标头包装它,但没有“RSA”(因为它可能是 RSA 以外的东西):

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcZ/r0nvJyHUUstIB5BqCUJ1CC
Cd1nzle4bEpPQJ/S0Wn7mV2FDAeh+UcbVhZu9n+5zypYNjeZKapPqBLoT8eCK51y
Kpzeb8LuEm3P8PK4xN18XBrIF1GprN8IIgSdK9f5SwnemutcURrY+PlWnvj7N5s/
03RlJA3/NHVXpPW/VQIDAQAB
-----END PUBLIC KEY-----

这就是您发明 X.509 SubjectPublicKeyInfo/OpenSSL PEM 公钥格式的方式。


这不会停止 RSA 公钥的标准格式列表。接下来是 OpenSSH 使用的专有公钥格式:

SSH-RSA AAAAB3NzaC1yc2EAAAADAQABAAAAgNxn + vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hs Sk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk + oEuhPx4IrnXIqnN5vwu4Sbc / w8rjE3XxcGsgXUams3wgiBJ0r1 / lLCd6a61xRGtj4 + VAE + Ps3mz / TdGUkDf80dVek9b9V P>

其实就是上面的 SSH 公钥格式,只是前缀为ssh-rsa,而不是包裹在---- BEGIN SSH2 PUBLIC KEY ----/---- END SSH2 PUBLIC KEY ----中。


这就是XML RSAKeyValue public key 的易用性所在:

指数0x 010001 base64 编码为AQAB 模数0x 00 dc 67 fa f4 9e f2 72 1d 45 2c b4 80 79 06 a0 94 27 50 82 09 dd 67 ce 57 b8 6c 4a 4f 40 9f d2 d1 69 fb 99 5d 85 0c 07 a1 f9 47 1b 56 16 6e f6 7f b9 cf 2a 58 36 37 99 29 aa 4f a8 12 e8 4f c7 82 2b 9d 72 2a 9c de 6f c2 ee 12 6d cf f0 f2 b8 c4 dd 7c 5c 1a c8 17 51 a9 ac df 08 22 04 9d 2b d7 f9 4b 09 de 9a eb 5c 51 1a d8 f8 f9 56 9e f8 fb 37 9b 3f d3 74 65 24 0d ff 34 75 57 a4 f5 bf 55 base64 编码为ANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dVek9b9V

这意味着 XML 是:

<RSAKeyValue>
   <Modulus>ANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dVek9b9V</Modulus>
   <Exponent>AQAB</Exponent>
</RSAKeyValue>

简单得多。缺点是它不能很好地包装、复制、粘贴(即 Xml 不如用户友好):

-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXY
UMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE
3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dV
ek9b9VAgMBAAE=
-----END RSA PUBLIC KEY-----

但它是一种很好的中性存储格式。

另见

Translator, Binary: 非常适合对 base64 数据进行解码和编码 ASN.1 JavaScript decoder:非常适合解码 ASN.1 编码的十六进制数据(您从 Translator, Binary 获得 Microsoft ASN.1 Documentation:描述用于 ASN.1 结构的可分辨编码规则 (DER)(您在其他任何地方都找不到更好的文档集;我认为微软的不仅仅是真正的文档)

【讨论】:

很好的答案!不准确回答相关问题,但它是关于 RSA 密钥的广泛而可靠的信息来源。令人印象深刻的工作:o 我能问个问题吗?为什么模数由这么多字符组成?它是以特定方式编码的一些大数字吗?谢谢! @kevinze 是的,它是一个以特定方式编码的 数字。它以 hexadecimal 格式显示,而不是 decimal 格式。与 78 (decimal) = 4E (hexadecimal) 和 `65,535(十进制)= FFFF(十六进制)一样,您看到的真正长的值(以十六进制显示,每 2 个字符有一个空格)是一个非常大的十进制数。 @ kevinze实际上在十进制的公共密钥是的 154,774,478,177,095,248,394,968,828,543,369,801,032,226,937,226,535,865,231,262,824,893,513,573,019,304,152,154,974,259,955,740,337,204,606,655,133,945,162,319,470,662,684,517,274,530,901,497,375,379,716,962,851,415,879,364,453,962,123,395,223,899,051,919,634,994,929,603,613,704,222,239,797,911,292,193,776,910,691,509,004,328,773,391,280,872,757,318,122,152,217,457,361,921,195,935,350,223,751,896,771,182,421 i>的。如果我必须给它起个名字,那就是“一亿五千四百万googol googol googol” 很好地解释了格式之间的关系,但有一点:PEM 使用 5 个破折号 不等于。 SSH2 确实将其更改为 4 和 1 个空格,但不会更改字符。【参考方案4】:

XML 在这里是个好主意吗?

通常私钥存储在 HSM 的/智能卡中。这提供了良好的安全性。

【讨论】:

你可能是对的,但我需要使用软件实现私钥/公钥,你认为最好的方法是什么? 这是我未来项目的要求之一。在我目前的版本中,我们使用了 HSM。我最初的读数显示 MS 有安全的存储来存储密钥。您可以从这些安全存储中访问密钥。 MS 的其他替代品是 [Link][1] [1]:trac.opendnssec.org/wiki/HSM/SoftTokens【参考方案5】:

使用现有的标准格式,例如 PEM。您的加密库应提供从 PEM 格式的文件加载和保存密钥的功能。

指数和模数是公钥。 D 和 Modulus 是私钥。其他值允许私钥持有者更快地计算。

【讨论】:

所以公钥 = 指数 + 模数(连接在一起?) 指数和模数作为两个独立的值,以任何有意义的方式存储。 PEM 格式使用 ASN1 存储这些值(在 PKCS#1 标准中定义为“SubjectPublicKeyInfo”格式),然后 base64 对结果进行编码。【参考方案6】:

公钥由模数和指数标识。私钥由其他成员识别。

【讨论】:

以上是关于如何存储/检索 RSA 公钥/私钥的主要内容,如果未能解决你的问题,请参考以下文章

如何使用openssl生成RSA公钥和私钥对 / 蓝讯

如何计算 RSA 私钥 (D) 的公钥 (E)? [复制]

如何有效地恢复 RSA 中的公钥或私钥?

加密中 公钥和私钥如何获得?

存储 RSA 私钥 Android

如何在linux里为ca生成一个私钥