我目前正试图仅从解码的PEM文件创建RSACryptoServiceProvider对象。经过几天的搜索,我确实设法解决了一个可行的解决方案,但并不是一个可以立即投入生产的解决方案。

简而言之,为了从组成PEM文件中公钥的字节创建RSACryptoServiceProvider对象,我必须创建一个指定 key 大小的对象(当前使用2048个SHA256),然后使用RSAParameters导入Exponent对象和Modulus设置。我正在这样做

byte[] publicKeyBytes = Convert.FromBase64String(deserializedPublicKey.Replace("-----BEGIN PUBLIC KEY-----", "")
                                                                      .Replace("-----END PUBLIC KEY-----", ""));

// extract the modulus and exponent based on the key data
byte[] exponentData = new byte[3];
byte[] modulusData = new byte[256];
Array.Copy(publicKeyBytes, publicKeyBytes.Length - exponentData.Length, exponentData, 0, exponentData.Length);
Array.Copy(publicKeyBytes, 9, modulusData, 0, modulusData.Length);


// import the public key data (base RSA - works)
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(dwKeySize: 2048);
RSAParameters rsaParam = rsa.ExportParameters(false);
rsaParam.Modulus = modulusData;
rsaParam.Exponent = exponentData;
rsa.ImportParameters(rsaParam);

虽然这样做有效,但不能假设deserializedPublicKey恰好是270个字节,并且我需要的模数位于位置9且长度始终为256个字节。

在给定一组公钥字节的情况下,如何更改此值以正确选择模量和指数字节?我试图理解ASN.1标准,但是很幸运地从中找到了我需要的东西-该标准有点拜占庭式。

任何帮助表示赞赏。

最佳答案

您无需导出现有参数,然后在它们之上重新导入。这迫使您的计算机生成RSA key ,然后将其丢弃。因此,为构造函数指定一个keysize无关紧要(如果您不使用该 key ,通常不会生成一个...)。

公钥文件是DER编码的Blob。

-----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描述了抽象语法符号1(ASN.1),这是DER是二进制编码的语法)

我们现在可以做一些解析:
30

这标识了已设置CONSTRUCTED位(0x20)的SEQUENCE(0x10),这意味着它包含其他DER/标记的值。 (序列始终由DER构成)
81 A0

下一部分是长度。由于设置了高位(> 0x7F),因此第一个字节为“长度长度”值。它指示真实长度在接下来的1个字节中编码(lengthLength & 0x7F)。因此,此SEQUENCE的内容总计为160个字节。 (在这种情况下,“其余数据”,但SEQUENCE可能已包含在其他内容中)。因此,让我们阅读内容:
30 0D

我们再次看到我们的构造序列(0x30),其长度值为0x0D,因此我们有一个13字节的有效负载。
06 09 2A 86 48 86 F7 0D 01 01 01 05 00
06是OBJECT IDENTIFIER,带有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表示BIT STRING。 BIT STRING编码为[标签] [长度] [未使用的位数]。未使用的位基本上总是零。因此,这是一个位序列,长度为0x8E个字节,并且全部使用了它们。

从技术上讲,我们应该到此为止,因为未设置CONSTRUCTED。但是,由于我们碰巧知道此结构的格式,因此我们将其值视作还是将CONSTRUCTED位置1:
30 81 8A

这是我们的 friend 再次构造的序列,0x8A有效载荷字节,它方便地对应于“剩下的一切”。
02 81 82
02标识一个INTEGER,并且此整数具有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

前导0x00将违反DER,除非下一个字节设置了高位。这意味着0x00在那里防止符号位被设置,从而使其为正数。
02 03 01 00 01

另一个INTEGER,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://tools.ietf.org/html/rfc5280,我们看到这看起来很像SubjectPublicKeyInfo结构:
SubjectPublicKeyInfo  ::=  SEQUENCE  {
  algorithm            AlgorithmIdentifier,
  subjectPublicKey     BIT STRING  }

AlgorithmIdentifier  ::=  SEQUENCE  {
  algorithm               OBJECT IDENTIFIER,
  parameters              ANY DEFINED BY algorithm OPTIONAL  }
                            -- contains a value of the type
                            -- registered for use with the
                            -- algorithm object identifier value

当然,它不知道RSA公钥格式是什么。但是oid-info网站告诉我们检查RFC 2313,我们在其中看到了
An RSA public key shall have ASN.1 type RSAPublicKey:

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

也就是说,我们读取的第一个INTEGER是Modulus值,第二个是(public)Exponent。

DER编码为big-endian,也是RSAParameters编码,但是对于RSAParameters,您需要从Modulus中删除前导0x00值。

尽管这不像为您提供执行此操作的代码那么容易,但是在给出此信息的情况下,为RSA key 编写解析器应该相当简单。我建议您将其编写为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
}

关于c# - 从公钥正确创建RSACryptoServiceProvider,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41808094/

10-10 15:06