Wednesday, September 19, 2012

Exporting RSA public key in PKCS#1 format

Recently I was working on task in which I had to generate a RSA key pair and export the public key to another application in PEM format. I implemented the task with help of BouncyCastle(code below) but the exported public key is not accepted by the other application.
static void ExportPublicKey(AsymmetricKeyParameter publicKey)
{
    var stringWriter = new StringWriter();
    var pemWriter = new PemWriter(stringWriter);
    pemWriter.WriteObject(publicKey);
    stringWriter.Flush();
    stringWriter.Close();

    File.WriteAllText(@"PublicKey.pem", stringWriter.ToString());
}
Little investigation revealed that the other application expects the public key in PKCS#1 format whereas the above code exports in PKCS#8 format(the ASN.1 structure of SubjectPublicKeyInfo). The difference between these formats is the additional field algorithm identifier in PKCS#8 format. We can clearly in there ASN.1 structure below

Also the exported PEM will have different start/end line like below

in PKCS#8
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
but in PKCS#1
-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----
Though the difference is one additional information but exporting public key without that information is not straightforward in BouncyCastle, or atleast at the time of writing. In OpenSSL(libcrypto) this is as simple as the calling the functions PEM_write_RSAPublicKey or PEM_write_bio_RSAPublicKey. In BouncyCastle the following code exports the public key in PKCS#1 format

static void ExportRsaPublicKey(AsymmetricKeyParameter publicKey)
{
    var subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey);
    var rsaPublicKeyStructure = RsaPublicKeyStructure.GetInstance(subjectPublicKeyInfo.GetPublicKey());
    var rsaPublicKeyPemBytes = Base64.Encode(rsaPublicKeyStructure.GetEncoded());

    var stringBuilder = new StringBuilder();

    stringBuilder.AppendLine("-----BEGIN RSA PUBLIC KEY-----");

    for (int i = 0; i < rsaPublicKeyPemBytes.Length; ++i)
    {
        stringBuilder.Append((char)rsaPublicKeyPemBytes[i]);

        // wraps after 64 column
        if (((i + 1) % 64) == 0)
            stringBuilder.AppendLine();
    }

    stringBuilder.AppendLine();
    stringBuilder.AppendLine("-----END RSA PUBLIC KEY-----");

    File.WriteAllText(@"RsaPublicKey.pem", stringBuilder.ToString());
}
and the code that generates RSA key pair is
static void Main(string[] args)
{
    var rsaKeyPairGenerator = new RsaKeyPairGenerator();
    rsaKeyPairGenerator.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
    var keyPair = rsaKeyPairGenerator.GenerateKeyPair();

    ExportPublicKey(keyPair.Public);
    ExportRsaPublicKey(keyPair.Public);
}

Sunday, September 16, 2012

Constructing .NET RSACryptoServiceProvider from DER bytes

In my current task I have to construct private key object from its DER bytes. In OpenSSL I can easily do this with d2i_RSAPrivateKey function but in .NET this doesn't seems to be an easy task. I hoped in BouncyCastle this can be done in single method call but it requires the following code to achieve the functionality

byte[] privateKeyDer = File.ReadAllBytes("PrivateKey.der");
var derSequence = new Asn1InputStream(privateKeyDer).ReadObject();
var privateKeyStructure = new RsaPrivateKeyStructure((Asn1Sequence)derSequence);

var privateCrtKeyParameters = 
    new RsaPrivateCrtKeyParameters(privateKeyStructure.Modulus,
                                   privateKeyStructure.PublicExponent,
                                   privateKeyStructure.PrivateExponent,
                                   privateKeyStructure.Prime1,
                                   privateKeyStructure.Prime2,
                                   privateKeyStructure.Exponent1,
                                   privateKeyStructure.Exponent2,
                                   privateKeyStructure.Coefficient);

var privateKey = DotNetUtilities.ToRSA(privateCrtKeyParameters); // returns RSA object
Please note the classes Asn1InputStream, RsaPrivateKeyStructure, RsaPrivateCrtKeyParameters and DotNetUtilities are provided by BouncyCastle.

03-Nov-2013 UPDATE:
I recently realized that it is possible for the RSA components(e.g. modulus) to have lesser bits than the actual RSA key's bit size. For example, in one case the modulus is 255 bytes(2040 bits) instead of expected 256 bytes for a 2048 bits RSA key. Some validation code inside RSACryptoServiceProvider.ImportParameters method rejects these kind of RSA key parameters with exception "Invalid Parameter" as the bytes size is lesser than expected. I don't think it is a problem with BouncyCastle's BigInteger implementation as OpenSSL also encodes these big integers with fewer bytes like the BouncyCastle. But OpenSSL accepts these big integers without any issues but it is only the RSACryptoServicer.ImportParameters method that rejects it.

We can make the ImportParameters to work with these kind of big integers by padding zeros to the big integers. You can find the implementation below, so instead of calling DotNetUtilities.ToRSA() we should call our method ToRsa() to convert the RSA key parameters to RSA object.

private static byte[] PadLeft(byte[] bytes, int sizeToPad)
{
    if (bytes.Length < sizeToPad)
    {
        // byte default value is zero, so the paddedBytes array is filled with zero by default
        var paddedBytes = new byte[sizeToPad];

        Buffer.BlockCopy(bytes, 0, paddedBytes, sizeToPad - bytes.Length, bytes.Length);
        bytes = paddedBytes;
    }
    
    return bytes;
}

public static System.Security.Cryptography.RSA ToRsa(RsaPrivateCrtKeyParameters privateKey, int bitSize)
{
    var rsaParameters = new System.Security.Cryptography.RSAParameters();
    rsaParameters.Modulus = PadLeft(privateKey.Modulus.ToByteArrayUnsigned(), bitSize / 8);
    rsaParameters.Exponent = PadLeft(privateKey.PublicExponent.ToByteArrayUnsigned(), 3);
    rsaParameters.D = PadLeft(privateKey.Exponent.ToByteArrayUnsigned(), bitSize / 8);
    rsaParameters.P = PadLeft(privateKey.P.ToByteArrayUnsigned(), bitSize / 16);
    rsaParameters.Q = PadLeft(privateKey.Q.ToByteArrayUnsigned(), bitSize / 16);
    rsaParameters.DP = PadLeft(privateKey.DP.ToByteArrayUnsigned(), bitSize / 16);
    rsaParameters.DQ = PadLeft(privateKey.DQ.ToByteArrayUnsigned(), bitSize / 16);
    rsaParameters.InverseQ = PadLeft(privateKey.QInv.ToByteArrayUnsigned(), bitSize / 16);

    var rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
    rsa.ImportParameters(rsaParameters);

    return rsa;
}