Question

I need to create a RSAParameters object in .Net using a private and public key (.key and .cer file). Is it possible to do this in .Net without the use of third party software? If so, where should I be looking.

I ultimately need to extract the Exponent, Modulus, D, P, Q, DP, DQ, InverseQ from this object in order to create a keyblob for a cryptoServer.

Thanks!

Was it helpful?

Solution

".key" and ".cer" file extensions are in no way an unambiguous specification of how the keys are encoded. However, it is plausible that the ".cer" file is an X.509 certificate, which contains (among many other things) the public key; hence, you may want to use the X509Certificate and X509Certificate2 classes (in the System.Security.Cryptography.X509Certificates namespace) to decode the certificate and extract the public key.

However, you need the private key, not the public key. The MSDN documentation on X509Certificate2 is quite confusing because it uses the term "certificate" to designate either the public part (what you have in your ".cer" file) or the union of the public and private part, as a single file (under the format which MSDN describes as "PKCS7 (Authenticode)").

An encoded RSA private key usually follows the format described in PKCS#1, which is not complex, but still relies on ASN.1, whose usage requires a bit of care. Sometimes, such RSA keys are wrapped into a bigger structure which also specifies the key type (i.e. that the key is for RSA); see PKCS#8 for details. Also, both kind of key encodings are commonly presented in PEM format: that's Base64 with a header (-----BEGIN RSA PRIVATE KEY-----) and a footer. Of course, your private key could be in any format (the ".key" extension is not overly informative). Optionally, both PKCS#8 and PEM can be symmetrically encrypted (with a password-derived key). There is also the PKCS#12 format, which can be viewed as an archive format for a collection of certificates and private keys, wrapping around the previous formats; PKCS#12 includes many layers of encryption, and is known in the Microsoft world under the name of "PFX" (or "certificate file", which keeps on being confusing).

Decoding all those formats is possible with a bit of code, but at that point it is recommended to use a library which already does such work, instead of rolling your own. Bouncy Castle is the usual suspect for that job.

The OpenSSL command-line tool can help you convert between some key and certificate formats.

Edit: if your private key is in PKCS#8 DER format and is not protected by a password (PKCS#8 can do that), then you can decode it with relatively simple code. DER is a set of rules for transforming structured data into a sequence of bytes. A data element is encoded as three successive parts:

  • a tag which tells what kind of value it is
  • a length which encodes the number of bytes in the third part
  • a value which is to be interpreted as specified by the tag

hence the name "TLV" (as "Tag, Length, Value"). Some elements are themselves structures containing sub-elements, in which case the value consists in the concatenation of the encodings of the sub-elements, each with its own tag, length and value.

A tag is usually a single byte; for PKCS#8 and RSA keys, you are interested in tags 0x30 (for 'SEQUENCE', i.e. an element with sub-elements), 0x02 ('INTEGER': an integer value) and 0x04 ('OCTET STRING': a blob).

A length is encoded as either of:

  • a single byte of value n between 0 and 127 (inclusive): this encodes the length n;
  • a byte of value n equal to or greater than 129, followed by exactly n-128 bytes which encode the length in big-endian format. For instance, a length of 324 will be encoded as three bytes: 0x82 0x01 0x44. This reads as: "0x82 is 128+2, hence I must read two extra bytes; the length is 256*0x01+0x44 = 324".

For an INTEGER, the value shall be interpreted with signed, big-endian convention (first byte is most significant, and high-order bit of the first byte specifies the integer sign; for RSA, all values are positive, so the first byte shall have a value between 0 and 127). Note that System.Numerics.BigInteger, in .NET 4.0, has a constructor which can decode a bunch of bytes, but it expects them in little-endian convention, not big-endian, so you would have to reverse the order of the bytes.

The structure of PKCS#8 is:

PrivateKeyInfo ::= SEQUENCE {
        version              Version,
        privateKeyAlgorithm  AlgorithmIdentifier,
        privateKey           OCTET STRING,
        attributes           [0] Attributes OPTIONAL
}

Version ::= INTEGER { v1(0) }

That's ASN.1 notation. What must be understood here is that the object is a SEQUENCE element: it is encoded as a SEQUENCE tag (0x30), then a length (n), then a value (n bytes, exactly). The value then consists in a succession of encoded elements, each in TLV format. First element is an INTEGER, which should have numerical value 0 under normal conditions (a zero encodes as '0x02 0x01 0x00'). Second element is an AlgorithmIdentifier, which I do not detail here; it is actually a SEQUENCE and it identifies the key type (here, it should say "this is a RSA key"); just read the tag (should be 0x30), then the length, and skip the value. Third element is an OCTET STRING: a 0x04 tag, then a length m, and a value of m bytes. That's what we are interested in. That value, which is the contents of the OCTET STRING, should be extracted; we will decode it in the next paragraph. The fourth element of the PrivateKeyInfo SEQUENCE is optional (it may not be there at all, and usually will not) and can be used to encode various extensions to this format.

Suppose that you have extracted the contents of the OCTET STRING. This is a sequence of bytes, which is actually the DER encoding of a structure which, in ASN.1, looks like this:

RSAPrivateKey ::= SEQUENCE {
        version            INTEGER,
        modulus            INTEGER,  -- n
        publicExponent     INTEGER,  -- e
        privateExponent    INTEGER,  -- d
        prime1             INTEGER,  -- p
        prime2             INTEGER,  -- q
        exponent1          INTEGER,  -- d mod (p-1)
        exponent2          INTEGER,  -- d mod (q-1)
        coefficient        INTEGER,  -- (inverse of q) mod p
        otherPrimeInfos    OtherPrimeInfos OPTIONAL
                -- otherPrimeInfos must be absent if version is two-prime,
                -- present if version is multi-prime.
}

So the contents of the OCTET STRING should begin with a 0x30 (tag of SEQUENCE), then a length (r), then r bytes. Those r bytes are the encodings of nine INTEGERs. The first INTEGER should be 0; if it is not, then the RSA key has more than two prime factors, and you are doomed. The eight subsequent INTEGERs are the integers you are looking for; just decode them, and you are done. The last field (otherPrimeInfos) is optional and should be absent if your RSA key is a "normal" RSA key (with two prime factors, not three or more).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top