你不需要导出现有参数,然后再在其上重新导入。这会强制你的计算机生成一个RSA密钥,然后丢弃它。因此,在构造函数中指定密钥大小并不重要(如果你不使用密钥,它不会生成密钥...通常)。公钥文件是DER编码的二进制大对象。
-----BEGIN PUBLIC KEY-----
MIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggC8rLGlNJ17NaWArDs5mOsV6/kA
7LMpvx91cXoAshmcihjXkbWSt+xSvVry2w07Y18FlXU9/3unyYctv34yJt70SgfK
Vo0QF5ksK0G/5ew1cIJM8fSxWRn+1RP9pWIEryA0otCP8EwsyknRaPoD+i+jL8zT
SEwV8KLlRnx2/HYLVQkCAwEAAQ==
-----END PUBLIC KEY-----
如果您获取PEM装甲内部的内容,那么它是一个Base64编码的字节数组。
30 81 A0 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01
05 00 03 81 8E 00 30 81 8A 02 81 82 00 BC AC B1
A5 34 9D 7B 35 A5 80 AC 3B 39 98 EB 15 EB F9 00
EC B3 29 BF 1F 75 71 7A 00 B2 19 9C 8A 18 D7 91
B5 92 B7 EC 52 BD 5A F2 DB 0D 3B 63 5F 05 95 75
3D FF 7B A7 C9 87 2D BF 7E 32 26 DE F4 4A 07 CA
56 8D 10 17 99 2C 2B 41 BF E5 EC 35 70 82 4C F1
F4 B1 59 19 FE D5 13 FD A5 62 04 AF 20 34 A2 D0
8F F0 4C 2C CA 49 D1 68 FA 03 FA 2F A3 2F CC D3
48 4C 15 F0 A2 E5 46 7C 76 FC 76 0B 55 09 02 03
01 00 01
ITU-T X.690定义了如何读取使用基本编码规则(BER)、规范编码规则(CER,我从未看到明确使用)和专用编码规则(DER)编码的内容。在大多数情况下,CER限制了BER,而DER限制了CER,使得DER最容易阅读。(ITU-T X.680描述了抽象语法符号一(ASN.1),它是DER的二进制编码语法)
现在我们可以进行一些解析:
30
这段文字指的是一个带有构造位设置(0x20)的序列(0x10),这意味着它包含其他DER/标记值。(在DER中,SEQUENCE始终是CONSTRUCTED)。
81 A0
下一部分是一个长度。因为它的高位被设置了(> 0x7F),第一个字节是“长度长度”值。它表示真实长度编码在接下来的1个字节中(lengthLength & 0x7F
)。因此,这个SEQUENCE的内容总共有160个字节。(在这种情况下,“其余数据”,但SEQUENCE可能包含在其他东西中)。所以让我们读取内容:
30 0D
我们再次看到我们的“CONSTRUCTED SEQUENCE”(
0x30
),长度值为
0x0D
,因此我们有一个13字节的有效负载。
06 09 2A 86 48 86 F7 0D 01 01 01 05 00
这里的06
是一个对象标识符,带有一个0x09
字节的有效载荷。OID的编码略微不直观,但这个等同于文本表示1.2.840.113549.1.1.1
,也就是id-rsaEncryption
(http://www.oid-info.com/get/1.2.840.113549.1.1.1)。
这样我们还剩下两个字节(05 00
),我们可以看到它是一个NULL(有效载荷为0字节,因为它是NULL)。
到目前为止,我们有:
SEQUENCE
SEQUENCE
OID 1.2.840.113549.1.1.1
NULL
143 more bytes.
继续:
03 81 8E 00
"03"代表比特字符串。比特字符串的编码格式为[标签][长度][未使用位数]。未使用位数通常为零。因此,这是一个由位组成的序列,长度为0x8E字节,并且所有位都被使用。
从技术上讲,我们应该在此停止,因为CONSTRUCTED未设置。但由于我们已经知道了这个结构的格式,所以我们将该值视为如果CONSTRUCTED位已经设置:"
30 81 8A
这里再次出现我们的好朋友CONSTRUCTED SEQUENCE,0x8A
字节的有效负载,便于对应“剩下的所有内容”。
02 81 82
02
表示一个整数,这个整数有 0x82
字节的有效载荷:
00 BC AC B1 A5 34 9D 7B 35 A5 80 AC 3B 39 98 EB
15 EB F9 00 EC B3 29 BF 1F 75 71 7A 00 B2 19 9C
8A 18 D7 91 B5 92 B7 EC 52 BD 5A F2 DB 0D 3B 63
5F 05 95 75 3D FF 7B A7 C9 87 2D BF 7E 32 26 DE
F4 4A 07 CA 56 8D 10 17 99 2C 2B 41 BF E5 EC 35
70 82 4C F1 F4 B1 59 19 FE D5 13 FD A5 62 04 AF
20 34 A2 D0 8F F0 4C 2C CA 49 D1 68 FA 03 FA 2F
A3 2F CC D3 48 4C 15 F0 A2 E5 46 7C 76 FC 76 0B
55 09
在 DER 编码中,前导的 0x00 会被视为一种违规情况,除非下一个字节设置了最高位。这意味着 0x00 的存在是为了防止符号位被设置,从而使其成为正数。
02 03 01 00 01
另一个整数,3个字节,值为01 00 01
。我们完成了。
SEQUENCE
SEQUENCE
OID 1.2.840.113549.1.1.1
NULL
BIT STRING
SEQUENCE
INTEGER 00 BC AC ... 0B 55 09
INTEGER 01 00 01
阅读https://www.rfc-editor.org/rfc/rfc5280,我们可以看到这与SubjectPublicKeyInfo
结构非常相似:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
当然,它不知道什么是RSA公钥格式。但oid-info网站告诉我们要查看RFC 2313,在那里我们可以看到:
An RSA public key shall have ASN.1 type RSAPublicKey:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER,
publicExponent INTEGER
这段话的意思是第一个整数是模数,第二个整数是(公共)指数。
DER编码采用大端序,这也是RSAParameters编码方式,但对于RSAParameters,您需要从模数中删除前导的0x00
值。
虽然没有直接给出代码,但是根据这些信息编写RSA密钥的解析器应该相当简单。我建议您将其编写为internal static RSAParameters ReadRsaPublicKey(...)
,然后只需执行以下操作即可。
RSAParameters rsaParameters = ReadRsaPublicKey(...);
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
// things you want to do with the key go here
}
key.ImportSubjectPublicKeyInfo(derBytes, out int bytesRead)
。但是,该答案仍然解释了该方法正在做什么。 - bartonjs