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

62

我想使用RSA公钥加密。如何最好地存储或检索私钥和公钥?在这里使用XML是否是一个好主意?

如何获取这些密钥?

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

RSAParameters有以下成员:D、DP、DQ、Exponent、InverseQ、Modulus、P和Q

哪一个是密钥?

6个回答

228

我想回应ala的评论,他问:

公钥 = 模数 + 指数

这是完全正确的。有几种存储这个 指数 + 模数 的方法。第一次尝试制定标准是在RFC 3447公钥密码学标准(PKCS) #1:RSA密码规范版本2.1)中,它定义了一个名为RSAPublicKey的公钥结构:

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

同一份RFC继续声明,您应该使用DER类型的ASN.1编码来存储公钥。我有一个样本公钥:
  • publicExponent: 65537 (所有RSA公钥使用65537作为指数的惯例)
  • modulus: 0xDC 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

该公钥的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 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 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 4716Secure Shell(SSH)公钥文件格式)。在指数和模数之前,它们包括算法标识符(ssh-rsa):
string    "ssh-rsa"
mpint     e
mpint     n

他们不想使用DER ASN.1编码(因为它非常复杂),取而代之的是四字节长度前缀

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

将上述整个字节序列进行Base64编码:

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互联网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分配的OIDs
    • 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

实际上,这就是上面提到的SSH公钥格式,但是加上了ssh-rsa前缀,而不是用---- BEGIN SSH2 PUBLIC KEY ----/---- END SSH2 PUBLIC KEY ----包装。


这就是XML RSAKeyValue公钥的易用性所在:

  • 底数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-----

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

另请参阅

  • 翻译器,二进制:用于解码和编码base64数据的好工具
  • ASN.1 JavaScript解码器:用于解码ASN.1编码的十六进制数据(从翻译器,二进制获取)
  • Microsoft ASN.1文档:描述了用于ASN.1结构的Distinguished Encoding Rules(DER)(您不会在任何其他地方找到更好的文档集;我认为Microsoft的文档不仅是真正的文档)

16
非常好的答案!虽然没有直接回答相关问题,但是提供了关于RSA密钥的广泛且可靠的信息来源。真是令人印象深刻的工作 :o - Nicolas Voron
3
我可以问一个问题吗?为什么模数由这么多字符组成?它是以某种特定方式编码的一个大数字吗?谢谢! - Kevin Lee
1
@kevinze 是的,它是以一种特定的方式编码的数字。 它以十六进制格式显示,而不是十进制格式。 就像 78(十进制)= 4E(十六进制)65,535(十进制)= FFFF(十六进制) 一样,您看到的非常长的值(以十六进制显示,每2个字符有一个空格)是一个十进制中非常大的数字。 - Ian Boyd
1
@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。如果我必须给它一个名字,那就是“一百五十四亿亿亿”。 - Ian Boyd
3
关于格式之间的关系,解释得很好,但是有一点需要更正:PEM使用的是5个短横线(-),而不是等号。SSH2将其变为4个短横线和一个空格,但并未改变字符。 - dave_thompson_085
显示剩余5条评论

49

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


1
抱歉提出这个问题,但为什么要将私钥存储在XML中?使用RSA密钥容器是否有些过度设计? - Ε Г И І И О
10
XML的价值在于,至少有其他7种标准可以用来存储RSA密钥材料。至少通过XML,您可以将公钥(modulusexponent)和私钥(DPQDPDQInverseQ)的值简单地表示为Base64编码的字节数据。从那里,您可以将其转换回PKCS#1、PKCS#7、PKCS#8、PFX、证书、OpenSSH、OpenSSL等格式。简而言之:XML是可移植的,而其他所有格式都是复杂专有的(例如ASN.1 DER,它甚至没有明确定义的标准)。 - Ian Boyd
虽然Ian Boyd的论文很棒,但它太笼统了,没有回答被问到的问题(他也承认了)。然而,Joe Kuemerle在这里给出的答案解决了这个问题。如果您将公钥或私钥存储为XML,则可以通过以下方式检索它:
  1. csp = new RSACryptoServiceProvider();
  2. csp.FromXmlString(_publicKey); 其中_publicKey是从某个地方检索到的字符串(数据库和文件是最常见的来源)
- pianocomposer

2

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

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


1
那么公钥 = 指数 + 模数(拼接在一起吗?) - ala
1
指数和模数作为两个独立的值,以任何有意义的方式存储。 PEM格式使用ASN1存储这些值(在PKCS#1标准中定义为“SubjectPublicKeyInfo”格式),然后对结果进行base64编码。 - caf

0

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

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


0

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


-1
XML在这里是一个好主意吗?
通常,私钥存储在HSM /智能卡中。这提供了良好的安全性。

你可能是对的,但我需要使用软件实现私钥/公钥,你认为最好的方法是什么? - ala
这是我未来项目的要求之一。在我的当前项目中,我们使用了HSM。我的初步阅读显示,MS有安全存储来存储密钥。您可以从这些安全存储中访问密钥。 MS的其他替代方案包括链接 - Raj

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