Encrypting a BouncyCastle RSA Key Pair and storing in a SQL2008 database

15,636

Solution 1

For reasons that should be clear, default (and perhaps inadvertent) serialization does not play well with private keys which should only be written out in very limited situations.

BouncyCastle has support for PKCS#8, which is the relevant standard for "serializing" private keys. There are ASN.1 structures called PrivateKeyInfo and EncryptedPrivateKeyInfo. Since they are in ASN.1 there are standard ways to serialize/deserialize them. As the name suggests, one stores the key in plaintext, the other encrypts the key based on a password.

For the public keys - these would not ordinarily be encrypted. BC supports the X.509 standard format of SubjectPublicKeyInfo for serializing them.

In the C# build, the high-level classes to look at would be:

  • Org.BouncyCastle.Security.PrivateKeyFactory
  • Org.BouncyCastle.Security.PublicKeyFactory
  • Org.BouncyCastle.Pkcs.EncryptedPrivateKeyInfoFactory
  • Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory
  • Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory

Solution 2

As long as the object is marked as serializable, one way to convert an object to a byte array is to use the BinaryFormatter class in .Net.

You will need to add this using statement to your code file:

using System.Runtime.Serialization.Formatters.Binary;

A binary formatter can output your class to a stream. As you intend to convert your object to a byte array, you can use a System.IO.MemoryStream as temporary storage.

MemoryStream memStream = new MemoryStream();

You can then create a new binary formatter.

BinaryFormatter formatter = new BinarryFomatter();

and use this to serialize your object.

formatter.Serialize(memStream, someObject);

To get the bytes you can use:

return memStream.ToArray();

To deserialize the byte array you need to write the bytes to a memory stream.

memStream.Write(arrBytes, 0, arrBytes.Length);

Return to the beginning of the stream.

memStream.Seek(0, SeekOrigin.Begin);

Then use the formatter to recreate the object.

Object obj = (Object)formatter.Deserialize(memStream);

If you are already using encryption functions you should be able to encrypt the created byte array quite easily before storing it in the database.

Hopefully that will help you in the right direction. If you are lucky, the BouncyCastle objects will be marked as serializable, if not you will need some extra code. Later, I will get a chance to look at the BouncyCastle librarys to be able to test this and will post more code if necessary.


... I have never used BouncyCastle before. After some testing, it appears that the public and private key objects are not serializable, so you will need to convert these objects into something that is!

It appears that the public and private keys expose properties as various BouncyCastle.Math.BigInteger values. (The keys can also be constructed from these BigIntegers). Further, BigIntegers have a ToByteArray() function and can also be constructed from a byte array. Very useful..

Knowing that you can break each key into BigIntegers and these in turn to a byte array and that the reverse is also possible, you a way to store all these in a serializable object. A simple struct or class would do e.g.

[Serializable]
private struct CipherPrivateKey
{
    public byte[] modulus;
    public byte[] publicExponent;
    public byte[] privateExponent;
    public byte[] p;
    public byte[] q;
    public byte[] dP;
    public byte[] dQ;
    public byte[] qInv;
}

[Serializable]
private struct CipherPublicKey
{
    public bool isPrivate;
    public byte[] modulus;
    public byte[] exponent;
}

This gives us a pair of easy to use serializable objects.

The AsymmetricCipherKeyPair exposes the Public and Private keys as AsymmetricKeyParameter objects. To get at the more detailed properties you will need to cast these to the following:

keyPair.Public to BouncyCastle.Crypto.Parameters.RsaKeyParameters keyPair.Private to BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters

The following functions will convert these to the structs to declared earlier:

private static CipherPublicKey getCipherPublicKey(Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters cPublic)
{
    CipherPublicKey cpub = new CipherPublicKey();
    cpub.modulus = cPublic.Modulus.ToByteArray();
    cpub.exponent = cPublic.Exponent.ToByteArray();
    return cpub;
}
private static CipherPrivateKey getCipherPrivateKey(Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters cPrivate)
{
    CipherPrivateKey cpri = new CipherPrivateKey();
    cpri.dP = cPrivate.DP.ToByteArray();
    cpri.dQ = cPrivate.DQ.ToByteArray();
    cpri.modulus = cPrivate.Modulus.ToByteArray();
    cpri.p = cPrivate.P.ToByteArray();
    cpri.privateExponent = cPrivate.Exponent.ToByteArray();
    cpri.publicExponent = cPrivate.PublicExponent.ToByteArray();
    cpri.q = cPrivate.Q.ToByteArray();
    cpri.qInv = cPrivate.QInv.ToByteArray();
    return cpri;
}

Using the binary formatter mentioned earlier, we can convert the serializable objects we have just created to a byte array.

CipherPublicKey cpub = getCipherPublicKey((Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)keypair.Public);
MemoryStream memStream = new MemoryStream();
BinaryFormatter formatter = new BinarryFomatter();
formatter.Serialize(memStream, cpub);
return memStream.ToArray();

Desierializing is then just the inverse as described earlier. Once you have either the public or private structs deserialized you can use the BouncyCastle contructors to recreate the keys. These functions demonstrate this.

private static Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters recreateASymCipherPublicKey(CipherPublicKey cPublicKey)
{
    Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters key;
    key = new Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters(
            cPublicKey.isPrivate,
            createBigInteger(cPublicKey.modulus),
            createBigInteger(cPublicKey.exponent));
    return key;
}

private static Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters recreateASymCipherPrivateKey(CipherPrivateKey cPrivateKey)
{
    Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters key;
    key = new Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters(
            createBigInteger(cPrivateKey.modulus),
            createBigInteger(cPrivateKey.publicExponent),
            createBigInteger(cPrivateKey.privateExponent),
            createBigInteger(cPrivateKey.p),
            createBigInteger(cPrivateKey.q),
            createBigInteger(cPrivateKey.dP),
            createBigInteger(cPrivateKey.dQ),
            createBigInteger(cPrivateKey.qInv));
    return key;
}

If you need to recreate the original key pair for any reason:

AsymmetricKeyParameter publ = (AsymmetricKeyParameter)recreateASymCipherPublicKey(cKeyPair.publicKey);
AsymmetricKeyParameter priv = (AsymmetricKeyParameter)recreateASymCipherPrivateKey(cKeyPair.privateKey);
AsymmetricCipherKeyPair keyPair = new AsymmetricCipherKeyPair(publ, priv);

Hopefully that all makes sense! The code samples should help you on your way.

Solution 3

The correct approach is to use Peters' suggestion.

I have included a small C# code sample below :

var keyPair = GetKeypair();

PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);                        
byte[] serializedKey = privateKeyInfo.ToAsn1Object().GetDerEncoded();

AsymmetricKeyParameter deserializedKey1 = PrivateKeyFactory.CreateKey(serializedKey);
Assert.AreEqual(keyPair.Private, deserializedKey1);

AsymmetricKeyParameter deserializedKey2 = PrivateKeyFactory.CreateKey(privateKeyInfo);            
Assert.AreEqual(keyPair.Private, deserializedKey2);

The sample uses the Bouncy Castle API. Note that the sample does NOT encrypt the key. The CreatePrivateKeyInfo method is overloaded to allow the use of a password as protection of the key.

Solution 4

Regarding the second part of your question, the data type that should be used for storing the key would be VARBINARY(256).

Back to the first part of your question, you actually have the option of having SQL Server handle the encryption for you. Granted, whether or not you would want to do this would be a matter of what your application requirements are, but I'll go over it in case it's an option.

We'll be pretty basic here and just use symmetric keys and Triple-DES.

First, the database has a master key which is used to protect certificates and asymmetric keys. The master key is encrypted with Triple-DES.

CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'supersecretpassword'  

SQL Server 2005/2008 can generate their own X.509 certificates used to protect the keys used to encrypt the actual data.

CREATE CERTIFICATE ExampleCertificate 
     WITH SUBJECT = 'thisisjustsomemetadata'

There are a lot of options for encrypting symmetric keys (certificates, passwords, other keys), as well as many supported algorithms. But for this example, we'll use our certificate.

CREATE SYMMETRIC KEY ExampleKey
     WITH ALGORITHM = TRIPLE_DES  
     ENCRYPTION BY CERTIFICATE EncryptTestCert 

The key needs to be decrypted using the same method with which it was encrypted. In our case, this would be the certificate we created.

 DECLARE @Value VARCHAR(50)
 SET @Value = 'supersecretdata!'

 OPEN SYMMETRIC KEY ExampleKey DECRYPTION BY CERTIFICATE ExampleCertificate  
     UPDATE SomeTable  
     SET SomeColumn = ENCRYPTBYKEY(KEY_GUID('ExampleKey'), @Value)

Decryption is just as straightforward.

OPEN SYMMETRIC KEY ExampleKey DECRYPTION BY CERTIFICATE ExampleCertificate  
     SELECT CONVERT(VARCHAR(50),DECRYPTBYKEY(SomeColumn)) AS DecryptedData 
     FROM SomeTable 

Hopefully this solved your problem, or at least opened you up to alternative solutions (though someone who's had experience doing encryption in C# apps could probably find the fault in your above code). If you have requirements that necessitate that the data can't even go over the wire to the SQL Server in plain-text, obviously this is a no-go (well, you can actually create SSL connections to SQL Server...).

Share:
15,636
TravisPUK
Author by

TravisPUK

I try to code, sometimes successfully. NodeJS, MongoDB, WPF, DAX, M, Python, Go, SQL, VB.NET, JavaScript, HTML.

Updated on June 21, 2022

Comments

  • TravisPUK
    TravisPUK over 1 year

    I have a function that generates a BouncyCastle RSA key pair. I need to encrypt the private key and then store the encrypted private and public keys into separate SQL2008 database fields.

    I am using the following to get the keypair:

    private static AsymmetricCipherKeyPair createASymRandomCipher()
    {
        RsaKeyPairGenerator r = new RsaKeyPairGenerator();
        r.Init(new KeyGenerationParameters(new SecureRandom(), 1024));
        AsymmetricCipherKeyPair keys = r.GenerateKeyPair();
        return keys; 
    }
    

    This is returning the keys fine, but I am not sure how I can then encrypt the private key and subsequently store it in the database.

    This is what I am currently using the encrypt the data (incorrectly?):

    public static byte[] encBytes2(AsymmetricKeyParameter keyParam, byte[] Key, byte[] IV)
    {
        MemoryStream ms = new MemoryStream();
        Rijndael rjdAlg = Rijndael.Create();
        rjdAlg.Key = Key;
        rjdAlg.IV = IV;
        CryptoStream cs = new CryptoStream(ms, rjdAlg.CreateEncryptor(), CryptoStreamMode.Write);
        byte[] keyBytes = System.Text.Encoding.Unicode.GetBytes(keyParam.ToString());
        cs.Write(keyBytes, 0, keyBytes.Length);
        cs.Close();
        byte[] encryptedData = ms.ToArray();
        return encryptedData;
    }
    

    Obviously the keyBytes setting where I am converting keyParam.ToString() is not correct as it only converts the KeyParameter name, not the actual value. I am submitting to this function the previous key pair return of keys.Private.

    The other question is as I am not encrypting the Public Key what format should I be storing this in the SQL2008 database, nvarchar(256) or other?

    Any help would be greatly appreciated.

  • TravisPUK
    TravisPUK over 14 years
    Lazy: Thanks for your response. I did recall that SQL2008 could do its own encryption, but knew nothing about it, thanks for that information. The function I am building needs to do the encryption and decryption on the client based on the operator's keys so I cannot send the clear text across the wire. So whist this did not solve my problem, it did certainly open my eyes to some alternatives. ;)
  • TravisPUK
    TravisPUK over 14 years
    @James, thanks for this. It will certainly put me in the right direction. As for the BouncyCastle objects being serializable, I am not sure. I will also investigate this. I am using encryption functions already so will look at applying this.
  • TravisPUK
    TravisPUK over 14 years
    Hey cool, that looks like it does the trick nicely. Thanks James, really appreciate the help!
  • Henrik Aasted Sørensen
    Henrik Aasted Sørensen about 13 years
    I think this comment is actually the correct answer to this question. There is a reason the AsymmetricKeyParameter class is not serializable. The solution outlined in the selected answer is a security problem waiting to happen.
  • TravisPUK
    TravisPUK about 13 years
    Thanks Henrik, I will look into it.
  • AC88
    AC88 over 12 years
    Thanks for the explicit example - I was looking for PrivateKeyFactory.CreateKey(byte[]) forever!
  • JamPickle
    JamPickle about 12 years
    While my answer shows it can be done it does not mean that it should be done, Peter's suggestion is much better and as Henrik suggests serializing in this way could cause security issues.
  • daveBM
    daveBM almost 10 years
    I am getting an error :attempt to use non-PBE algorithm with PBE EncryptedPrivateKeyInfo generation - Anyone know what algorithm I should use? I am currently using PBEWithSHAAnd3KeyTripleDES