Read DER private key in C# using BouncyCastle
Asked Answered
K

2

4

I am trying to read an RSA private key into .Net using BouncyCastle to test data I have previously encrypted. The encrypted data is working fine using the public key and Bouncy Castle and I have also used the same private key as below (which is DER format) to successfully decrypt my data in a PHP application but I don't know why I can't create the private key in .Net to do the same thing:

byte[] privatekey = File.ReadAllBytes(@"C:\Users\Luke\privkey.der");
var rsaKeyParameters = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privatekey);

The second line throws an exception:

"unknown object in factory: DerInteger\r\nParameter name: obj"

I also tried using a stream instead of a byte array and the same error occurs. The key pair was created using OpenSSL and as mentioned, decryption works in PHP using openssl_private_decrypt() and the same key as in the .Net code. I also tried a PEM format of the same key and that also didn't work (but I don't think BC supports PEM directly anyway)

Has anyone done this before? Thanks

Kellner answered 28/11, 2013 at 15:8 Comment(5)
Could you show us the DER format? If it is a text file then it isn't DER, but a PEM encoding of the DER. In that case you first need to remove the ASCII armour (as PEM also is called).Creeps
It is in binary format so it obviously won't paste into here. I used an online javascript checker and it was happy with the key and showed all the contents.Kellner
I understand Lukos, but if we don't know the format of the private key then we cannot see what is wrong, unless we encountered exactly this issue ourselves (or possibly by going through the source code). If this forum does not work, you may want to give the Bouncy Castle mailinglist a try...Creeps
I'm not sure what you are asking since I cannot paste the binary of the file into a text editor. It was generated using openssl genrsa -out privkey.der -outform der 2048. The decryption that works in PHP then had the der key converted to pem, also using openssl.Kellner
let us continue this discussion in chatCreeps
K
8

The problem was that I had assumed PublicKeyFactory and PrivateKeyFactory were complimentary since they are in the same namespace. They are not!

To decode the private key, I needed the following alternative code:

var privKeyObj = Asn1Object.FromStream(privatekey);
var privStruct = new RsaPrivateKeyStructure((Asn1Sequence)privKeyObj);

// Conversion from BouncyCastle to .Net framework types
var rsaParameters = new RSAParameters();
rsaParameters.Modulus = privStruct.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = privStruct.PublicExponent.ToByteArrayUnsigned();
rsaParameters.D = privStruct.PrivateExponent.ToByteArrayUnsigned();
rsaParameters.P = privStruct.Prime1.ToByteArrayUnsigned();
rsaParameters.Q = privStruct.Prime2.ToByteArrayUnsigned();
rsaParameters.DP = privStruct.Exponent1.ToByteArrayUnsigned();
rsaParameters.DQ = privStruct.Exponent2.ToByteArrayUnsigned();
rsaParameters.InverseQ = privStruct.Coefficient.ToByteArrayUnsigned();
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParameters);
return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(ciphertext), true));

A BIG thankyou to owlstead for their help.

Kellner answered 29/11, 2013 at 11:38 Comment(2)
PrivateKeyFactory is used with keys formatted as ASN.1 PrivateKeyInfo, which basically means there's some headers to self-describe what type of key is in it. RSA private keys are often stored without this, just directly as an RsaPrivateKeyStructure, as you have discovered. FYI, I added new utility methods to BC's DotNetUtilities class for conversions from RsaPrivateKeyStructure to RSAParameters and RSA(CryptoServiceProvider).Ameeameer
Just in case anyone runs into the same issue as me, I was getting Bad Data exceptions (very helpful) on the ImportParameters call. I noticed that DP was only 63 bytes while the others were 64 or 128. I added a zero byte to the start of DP and everything worked.Retouch
W
0

I ended writing a function that reads from a Stream and automatically detects if the private key is either PEM or DER encoded, and if it's use PKCS1 or PKCS8 formats. It follows:

public static RsaPrivateCrtKeyParameters ReadPrivateKey(Stream stream)
{
    int firstByte = stream.ReadByte();
    if (firstByte < 0)
        throw new Exception($"File reached eof");

    stream.Position = 0;

    // First byte of DER is 0x30 https://mcmap.net/q/358589/-rsa-pubkey-file-type-detection
    bool isPem = firstByte != 0x30;
    if (isPem)
    {
        using (var strReader = new StreamReader(stream))
        {
            var pemReader = new PemReader(strReader);
            while (strReader.Peek() != -1)
            {
                var obj = pemReader.ReadObject();
                var privatekey = obj as RsaPrivateCrtKeyParameters;
                if (privatekey != null)
                {
                    // It's a PKCS#8 encoded private key
                    return privatekey;
                }
                else
                {
                    // When reading a PKCS#1 encoded private key BC wants
                    // to parse a key pair instead, but the private
                    // parameter seems to be correct
                    var pair = obj as AsymmetricCipherKeyPair;
                    if (pair != null && pair.Private != null)
                    {
                        return (RsaPrivateCrtKeyParameters)pair.Private;
                    }
                }
            }
        }
    }
    else
    {
        var privKeyObj = Asn1Object.FromStream(stream);
        var sequence = (Asn1Sequence)privKeyObj;
        if (sequence[2] is Asn1OctetString)
        {
            // It's a PKCS#8 encoded private key
            AsymmetricKeyParameter keyPair = PrivateKeyFactory.CreateKey(PrivateKeyInfo.GetInstance(privKeyObj));
            return (RsaPrivateCrtKeyParameters)keyPair;
        }
        else
        {
            // Assume it's a PKCS#1 encoded private key
            var privStruct = RsaPrivateKeyStructure.GetInstance(privKeyObj);
            return new RsaPrivateCrtKeyParameters(privStruct);
        }
    }

    throw new Exception("Could not read private key");
}
Walkyrie answered 13/3 at 18:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.