从公钥正确创建RSACryptoServiceProvider [英] Correctly create RSACryptoServiceProvider from public key

查看:110
本文介绍了从公钥正确创建RSACryptoServiceProvider的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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密钥,然后将其丢弃.因此,为构造函数指定一个keysize无关紧要(如果您不使用该密钥,通常不会生成一个...).

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

我们现在可以进行一些解析:

30

这标识已将CONSTRUCTED位置1(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是对象标识符,具有0x09字节有效负载. OID的编码有点不直观,但是等效于文本表示形式1.2.840.113549.1.1.1,即id-rsaEncryption(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 

前导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值,第二个是(公共)指数.

DER编码为big-endian,也是RSAParameters编码,但是对于RSAParameters,您需要从Modulus中删除前导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
}

I'm currently trying to create an RSACryptoServiceProvider object solely from a decoded PEM file. After several days of searching, I did manage to wrangle a working solution but it's not one that would be production ready.

In a nutshell, in order to create an RSACryptoServiceProvider object from the bytes that make up the public key in a PEM file, I must create the object specifying the keysize (currently 2048 using SHA256, specifically) and then importing a RSAParameters object with the Exponent and Modulus set. I'm doing this as so;

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);

While this works, it's not viable to assume that the deserializedPublicKey will be exactly 270 bytes and that the modulus I need is found at position 9 and always be 256 bytes in length.

How do I change this to correctly pick out the Modulus and Exponent byte given a set of public key bytes? I've tried to make sense of the ASN.1 standard but with little luck finding what I need from it - the standard(s) are somewhat byzantine.

Any help is appreciated.

解决方案

You don't need to export existing parameters then re-import over top of them. That forces your machine to generate an RSA key then throw it away. So specifying a keysize to the constructor doesn't matter (if you don't use the key it won't generate one... usually).

The public key file is a DER encoded blob.

-----BEGIN PUBLIC KEY-----
MIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggC8rLGlNJ17NaWArDs5mOsV6/kA
7LMpvx91cXoAshmcihjXkbWSt+xSvVry2w07Y18FlXU9/3unyYctv34yJt70SgfK
Vo0QF5ksK0G/5ew1cIJM8fSxWRn+1RP9pWIEryA0otCP8EwsyknRaPoD+i+jL8zT
SEwV8KLlRnx2/HYLVQkCAwEAAQ==
-----END PUBLIC KEY-----

If you take the contents inside the PEM armor, it's a Base64-encoded byte array.

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 defines how to read things encoded under Basic Encoding Rules (BER), Canonical Encoding Rules (CER, which I've never seen explicitly used), and Distinguished Encoding Rules (DER). For the most part CER restricts BER and DER restricts CER, making DER the easiest to read. (ITU-T X.680 describes Abstract Syntax Notation One (ASN.1), which is the grammar that DER is a binary encoding for)

We can do a bit of parsing now:

30

This identifies a SEQUENCE (0x10) with the CONSTRUCTED bit set (0x20), which means that it contains other DER/tagged values. (SEQUENCE is always CONSTRUCTED in DER)

81 A0

This next part is a length. Since it has the high bit set (> 0x7F) the first byte is a "length length" value. It indicates that the true length is encoded in the next 1 byte(s) (lengthLength & 0x7F). Therefore the contents of this SEQUENCE are 160 bytes total. (In this case, "the rest of the data", but the SEQUENCE could have been contained within something else). So let's read the contents:

30 0D

We see our CONSTRUCTED SEQUENCE again (0x30), with a length value of 0x0D, so we have a 13 byte payload.

06 09 2A 86 48 86 F7 0D 01 01 01 05 00 

The 06 is OBJECT IDENTIFIER, with a 0x09 byte payload. OID has a slightly non-intuitive encoding, but this one is equivalent to the text representation 1.2.840.113549.1.1.1, which is id-rsaEncryption (http://www.oid-info.com/get/1.2.840.113549.1.1.1).

This still leaves us with two bytes (05 00) which we see is a NULL (with a 0 byte payload, because, well, it's NULL).

So so far we have

SEQUENCE
  SEQUENCE
    OID 1.2.840.113549.1.1.1
    NULL
  143 more bytes.

Continuing on:

03 81 8E 00

The 03 means BIT STRING. BIT STRING is encoded as [tag] [length] [number of unused bits]. The unused bits is essentially always zero. So this is a sequence of bits, 0x8E bytes long, and all of them are used.

Technically we should stop there, because CONSTRUCTED wasn't set. But since we happen to know the format of this structure, we treat the value as if the CONSTRUCTED bit was set anyways:

30 81 8A

Here's our friend CONSTRUCTED SEQUENCE again, 0x8A payload bytes, which conveniently corresponds to "everything that's left".

02 81 82

02 identifies an INTEGER, and this one has 0x82 payload bytes:

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 

The leading 0x00 would be a violation of DER, except the next byte has the high bit set. This means that the 0x00 was there to keep the sign bit from being set, making this a positive number.

02 03 01 00 01

Another INTEGER, 3 bytes, value 01 00 01. And we're done.

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

Harvesting https://tools.ietf.org/html/rfc5280 we see that this looks a lot like a SubjectPublicKeyInfo structure:

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

Of course, it doesn't know what the RSA public key format is. But the oid-info site told us to check out RFC 2313, where we see

An RSA public key shall have ASN.1 type RSAPublicKey:

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

So that says that the first INTEGER we read is the Modulus value, and the second is (public)Exponent.

The DER encoding is big-endian, which is also the RSAParameters encoding, but for RSAParameters you need to remove leading 0x00 values from Modulus.

While that isn't as easy as giving you the code to do it, it should be fairly straightforward to write a parser for RSA keys given this information. I'd recommend that you write it as internal static RSAParameters ReadRsaPublicKey(...), and then you just need to do

RSAParameters rsaParameters = ReadRsaPublicKey(...);

using (RSA rsa = RSA.Create())
{
    rsa.ImportParameters(rsaParameters);
    // things you want to do with the key go here
}

这篇关于从公钥正确创建RSACryptoServiceProvider的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆