Create RSACryptoServiceProvider correctly from public key

I am currently trying to create an RSACryptoServiceProvider object exclusively from a decoded PEM file. After several days of searching, I managed to interrupt the working solution, but this is not the one that would be ready for production.

In a nutshell, in order to create an RSACryptoServiceProvider object from the bytes that make up the public key in the PEM file, I have to create an object that defines the key (currently 2048 using SHA256 in particular), and then import the RSAParameters object with the Exponent and Modulus settings. I do it like this:

 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 is unsafe to assume that deserializedPublicKey will be exactly 270 bytes and that the module I need will be at position 9 and will always be 256 bytes long.

How can I change this to correctly select the bytes of the module and exponent specified by the public key byte set? I tried to understand the ASN.1 standard, but with little success, to find what I need from it - the standard is somewhat Byzantine.

Any help is appreciated.

+6
source share
3 answers

You do not need to export existing parameters and then re-import over them. This forces your machine to generate an RSA key and then throw it away. Therefore, specifying a key on a constructor does not matter (if you do not use a key, it will not generate one ... usually).

The public key file is a coded BLOB code.

 -----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 is a byte-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 in accordance with the Basic Encoding Rules (BER), Canonical Encoding Rules (CER, which I have never seen explicitly) and "Excellent Encoding Rules" (DER). For the most part, CER BER and DER restrictions limit CER, making DER the easiest to read. ( ITU-T X.680 describes One 's abstract syntactic notation (ASN.1), which is the grammar that DER represents binary encoding)

Now we can make out a little:

 30 

This identifies SEQUENCE (0x10) with the CONSTRUCTED bit (0x20) set, which means that it contains other DER / tagged values. (SEQUENCE ALWAYS DESIGN IN DER)

 81 A0 

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

 30 0D 

We again see our 0x30 SEQUENCE ( 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 

06 - OBJECT IDENTIFIER, with a payload of 0x09 bytes. The OID has slightly non-intuitive encoding, but this is equivalent to the textual 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 ).

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

So, so far we have

 SEQUENCE SEQUENCE OID 1.2.840.113549.1.1.1 NULL 143 more bytes. 

Continuation:

 03 81 8E 00 

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

Technically, we have to stay there because CONSTRUCTED has not been installed. But since we know the format of this structure, we process the value as if the CONSTRUCTED bit was set like this:

 30 81 8A 

Here our friend CONSTRUCTED SEQUENCE again, 0x8A bytes of payload, which conveniently matches "all that is left."

 02 81 82 

02 identifies INTEGER, and it 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 will be a DER violation, except that the next byte has a high bit. This means that 0x00 was there to preserve the sign bit, which makes it a positive number.

 02 03 01 00 01 

Another INTEGER, 3 bytes, value 01 00 01 . And you'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 is very similar to the 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, he does not know what the RSA public key format is. But oid-info 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, we say that the first INTEGER that we read is the value of the module, and the second is (public) Exponent.

DER encoding is widescreen, which is also RSAParameters encoding, but for RSAP parameters you need to remove the 0x00 leading values ​​from the module.

Although it is not as simple as giving you the code to do this, it should be simple enough to write an RSA key parser based on this information. I would recommend 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 } 
+11
source

After a lot of time, searching and bartonjs, an outstanding answer, the code for this in the end is straightforward, although a little unintuitive for anyone who is not familiar with the public key structure.

The PEM public key can describe various types of keys, not just RSA, and not something like new RSACryptoServiceProvider(pemBytes) , we need to analyze the PEM based on its ASN.1 structure / syntax, and then tells us this is the RSA key (this there may be a number of others). Knowing this

 const string rsaOid = "1.2.840.113549.1.1.1"; // found under System.Security.Cryptography.CngLightup.RsaOid but it marked as private Oid oid = new Oid(rsaOid); AsnEncodedData keyValue = new AsnEncodedData(publicKeyBytes); // see question AsnEncodedData keyParam = new AsnEncodedData(new byte[] { 05, 00 }); // ASN.1 code for NULL PublicKey pubKeyRdr = new PublicKey(oid, keyParam, keyValue); var rsaCryptoServiceProvider = (RSACryptoServiceProvider)pubKeyRdr.Key; 

NOTE: The above code is not ready for production! You will need to install appropriate defenders around the creation of the object (for example, the public key may not be RSA), cast in RSACryptoServiceProvider , etc. The sample code here is short to illustrate that this can be done quite cleanly.

How do I understand that? Looking through the cryptography namespace in ILSpy, I noticed AsnEncodedData that called using bartonjs . Having done more research, I came across this message (see friend?). This tried to determine the specific key size, but it creates the required RSACryptoServiceProvider along the way.

I leave bartonjs answer as Accepted, and rightly so. The above code is the result of this research, and I leave it here so that others who want to do the same can do it purely without any arrays copying the hacks like mine in my OP.

In addition, for decoding and testing purposes, you can check if your public key is collapsible using the ASN.1 decoder here .

UPDATE

This is on the .NET roadmap to make it easier with ASN.1 Analysis for Core 2.1.0.

+2
source

PEM files are just a series of base64 and .net encoded DER files that allow you to import DER files directly, so you can do something like this (I assume that you only use the public key as you declare that you only use it)

 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
source

Source: https://habr.com/ru/post/1014331/


All Articles