2017-01-23 37 views
3

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

概括地说,以创建从构成在PEM文件中的公共密钥的字节的RSACryptoServiceProvider对象,我必须(目前2048使用SHA256,具体地),然后导入RSAParameters创建指定密钥大小的对象对象与ExponentModulus集。我这样做;

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个字节。

我该如何改变这个来正确地挑出一组公钥字节的模数?我试图理解ANS标准,但运气很好,找到了我需要的东西。我不需要(或者有时间)解码整个公钥结构,只需一直得到Modulus字节。指数比较容易,因为它总是最后几个字节,尽管其长度也可能不同。

任何帮助表示赞赏。

回答

8

您不需要导出现有参数,然后重新导入它们之上。这会迫使你的机器生成一个RSA密钥,然后将其丢弃。因此,为构造函数指定一个密钥大小并不重要(如果您不使用密钥,它通常不会生成一个密钥大小)。

公钥文件是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 

该标识序列( 0x10)与CONSTRUCTED位集(0x20),这意味着它包含其他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-rsaEncryptionhttp://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编码为[标签] [长度] [未使用位数]。未使用的比特基本上总是零。所以这是一个比特序列,长度为0x8E字节,并且所有这些都被使用。

从技术上讲,我们应该在那里停下来,因为CONSTRUCTED没有设置。但是由于我们碰巧知道该结构的格式,我们把值,好像构建的位被设置反正:

30 81 8A 

这里又是我们的朋友流构建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 

前导为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-信息的网站叫我们看看RFC 2313,我们看到

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

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

这样说,我们读到的第一个整数为模值,第二个是(公共)指数。

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 
} 
+0

这是一个非常令人印象深刻的回应!我会尽力根据提供的信息实施,谢谢! – DiskJunky

-1

PEM文件只是一系列base64编码的DER文件,而.net允许直接导入DER文件,所以你可以这样做(我假设你只使用公钥,因为你声明你只使用它):

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

X509Certificate2 cert = new X509Certificate2(certBytes); 
RSACryptoServiceProvider publicKeyProvider = 
(RSACryptoServiceProvider)cert.PublicKey.Key; 
+0

,只要它是那么容易:-)如果我通过公钥到构造,它导致了'CryptographicException' '找不到请求的对象' – DiskJunky

+0

它应该是这么简单,但似乎X509Certificate2要求DER文件包含私钥... – Gusman

+0

作为替代方案,使用Bouncy Castle,它具有本地支持导入PEM文件 – Gusman

相关问题