Tuesday, September 8, 2015

C# Bouncy Castle File Decryption

I had a hard time finding a good sample of decrypting a PGP-encrypted file in C# so I decided to write this post.

Bouncy Castle works with streams for the most part.  So first you need to open the file as a stream, then get a special decoder stream using PgpUtilities:

// Comment
using (FileStream fsEncryptedFile = File.Open(ENCRYPTED_FILENAME, FileMode.Open)) {
 using (Stream decoderStream = PgpUtilities.GetDecoderStream(fsEncryptedFile))
 {
Next we have to create a PgpObjectFactory using that stream which will provide us with PgpObjects. We're looking for a PgpEncryptedDataList so we might have to skip the first object which could be a marker:
PgpObjectFactory factory = new PgpObjectFactory(decoderStream);
PgpObject obj = factory.NextPgpObject();
if (!(obj is PgpEncryptedDataList))
{
 obj = factory.NextPgpObject(); // first object might be a PGP marker packet
}
PgpEncryptedDataList edl = obj as PgpEncryptedDataList;
With our PgpEncryptedDataList we find PgpPublicKeyEncryptedData, and find the key. Here we open a secret keyring file and find the key based on the KeyId in the data and decrypt it using a passphrase:
PgpEncryptedDataList edl = obj as PgpEncryptedDataList;
foreach (PgpPublicKeyEncryptedData data in edl.GetEncryptedDataObjects())
{
    PgpPrivateKey privateKey = null;
    using (FileStream fsKeyring = File.Open(KEYRING_FILENAME, FileMode.Open))
    {
     PgpSecretKeyRingBundle bundle = new PgpSecretKeyRingBundle(fsKeyring );
     PgpSecretKey secretKey = bundle.GetSecretKey(data.KeyId);
     privateKey = secretKey.ExtractPrivateKey(PASSPHRASE.ToCharArray());
    }
Next we use GetDataStream on our PgpPublicKeyEncryptedData and pass it our private key and create a PgpObjectFactory that will give us our data. If the object is compressed, we uncompress it:
PgpObjectFactory plainFactory = new PgpObjectFactory(data.GetDataStream(privateKey));
PgpObject message = plainFactory.NextPgpObject();
if (message is PgpCompressedData) {
 message = new PgpObjectFactory(((PgpCompressedData)message).GetDataStream()).NextPgpObject();
}
Hopefully that will give us a PgpLiteralData which is what we need to get a stream containing the plain, unencrypted data and write that to a new file:
if (message is PgpLiteralData) {
 PgpLiteralData ld = (PgpLiteralData)message;
 using (FileStream outStream = File.Create(OUTPUT_FILENAME)) {
  Stream inStream = ld.GetInputStream();
  byte [] buffer = new byte[0x100000];
  int count = inStream.Read(buffer, 0, buffer.Length);
  while (count > 0) {
   outStream.Write(buffer, 0, count);
   count = inStream.Read(buffer, 0, buffer.Length);
  }
  outStream.Close();
 }
} else {
 Console.WriteLine("ERROR: Unknown type of message in PGP file: {0}", message.GetType().FullName);
}