Saturday, 6 June 2009

PGP Encryption with C#

Recently at work, I had to come up with a deployment framework for a legacy app we use. The initial requirement was to simply copy the app from a network share onto users desktops. There was also a requirement to provide a level of security around the deployment itself, so to me, an anonymous network share didnt seem to be the correct way of releasing this app... A much more simpler way to provide at least a small level of security would be make the deployment framework download the app as an archive from a webserver on the domain. This, at least, means that only users with authorisation to access the domain would be able to download the app, and they would only be able to download it whilst they were logged in.

Then I was told that there will be some instances that field workers will need to download parts of the app whilst they are out and about doing whatever it is they do. I had already planned to compress the app as an archive, but now I needed a way to secure the archive itself. One of my colleagues suggested that I put a password on the archive, its not a daft suggestion to make, it would imply that only people with the password would be able to extract the data from the archive itself. But, I pointed out that this doesnt prove that the archive was one created by us - all it does it password protect an archive. Plus, we would need to communicate the password to those that needed it in a secure manner, which in turn would mean we couldnt use the same password all the time etc. I asked if we already use some form of secure signature and got told we use PGP - result. I explained to my colleagues that yes, password protecting the archive would mean that only password holders could extract from the archive, but this wouldnt mean they are authorised to hold the password in the first place. I also explained that the password could be cracked, even if it was sufficiently complex, a persistant attacker could spend a lot of time trying to crack it.

With this in mind, I went on to say that as we already encrypt data with PGP, we could do the same thing here with the archives we are planning to create. This way, people using our app (or archives in general) would be able to see with great certainty that they did originate from us and that it hasnt been substituted by a third party.

This is part of a .Net project, so any encryption would ideally take place using the .Net framework. However, right now there is no PGP support in .Net. There is support for cryptograhy in general, for instance RSA is employed as well as DPAPI, but we already use PGP and already have public keys. So it would make more sense to me for us to use these for encryption. To do this, I am going to be using the C# cryptography libraries available from The Legion of the Bouncy Castle: http://www.bouncycastle.org/csharp/ . There are not a lot of good examples of how to use this out on the Internet, the best I could find to outline the concepts involved was here. Unfortunately, the formatting of the page makes it a little hard to understand what is happening - which is the important part

Lets get down to encrypting a file and compressing it to an archive. I am going to assume that if you are searching for PGP encryption methods in C# that you have already generated a set of PGP keys. If you havent, then I can suggest that you nip over to http://www.gnupg.org/ and download a copy of GnuPG to generate some for you (it's what I used to test through my app). The first step we need to take is to understand what we need to get the key values. We need to know the path to the public and private keys, the password for the public key to encrypt with and we need to know the key ID we want to target. The key ID is a 64bit signed integer, using GnuPG you can get this value from the command line using the command:

--list-keys --with-colons <userid>
Simply replace the user ID with the user ID attached to the key (this will probably be the email address used when generating the key itself). You will get something that looks like this back:

pub:u:1024:1:33575CB4BB8193FD:2009-06-06:::u:Your Name(RSA Sign only test) <your@email.address>::scESC:
sub:u:1024:1:31B23C6C6681F46B:2009-06-06::::::e:

The key ID is the bit in bold (or should be bold, but not quite so well - it's the fith column along). This is the key ID in hexidecimal, in the code I am about to show you it needs to be in decimal format, so you can either convert it in c# with the following:

int decAgain = int.Parse(hexValue, System.Globalization.NumberStyles.HexNumber);
Or you can use a tool to convert it for you like this one: http://www.parkenet.com/apl/HexDecConverter.html, or if you happen to know what it is anyway (like I did), you can just type it in etc.

I used RSA encryption when generating my key. I used this as it would appear to be the best method for creating keys right now, there are not many examples of how to use PGP encryption on the Internet at the moment, and the peices of code that are here and there seem to use older standards.

Right, now we know our key ID etc, we can start writing the code. I created a class called PGPKeys to do all my key related work for me. First up, we need to declare all our variables etc:
/// <summary>
/// long representing the key ID we want to target.
/// </summary>
private long _keyID;
/// <summary>
/// string to represent the path to the private key file.
/// </summary>
private string _privKeyPath;
/// <summary>
/// string to represent the path to the public key file.
/// </summary>
private string _pubKeyPath;
/// <summary>
/// string to represent the passphrase used with PGP.
/// </summary>
private string _password;
public PgpPublicKey PGPPublicKey { get; private set; }
public PgpPrivateKey PGPPrivateKey { get; private set; }
public PgpSecretKey PGPSecretKey { get; private set; }

Nice and simple. Next up, I want to be able to validate the values I am planning to use:

/// <summary>
/// Constructor to manage variables for us.
/// </summary>
/// <param name="pubKeyPath">string representing the public key path</param>
/// <param name="privKeyPath">string representing the private key path</param>
/// <param name="password">string representing the passphrase</param>
/// <param name="keyID">long representing the key ID we want to use</param>
public PGPKeys(string pubKeyPath, string privKeyPath, string password, long keyID)
{
if (!File.Exists(pubKeyPath))
{
throw new ArgumentNullException("Could not find the public key at " + pubKeyPath);
}
else
{
_pubKeyPath = pubKeyPath;
}
if (!File.Exists(privKeyPath))
{
throw new ArgumentNullException("Could not find the private key at " + privKeyPath);
}
else
{
_privKeyPath = privKeyPath;
}
if (String.IsNullOrEmpty(password))
{
throw new ArgumentNullException("The password must not be null");
}
else
{
_password = password;
}
if (keyID == 0)
{
throw new ArgumentNullException("The key ID must not be null");
}
else
{
_keyID = keyID;
}
PGPPublicKey = getPublicKey(_pubKeyPath);
PGPSecretKey = getSecretKey(_privKeyPath);
PGPPrivateKey = getPrivateKey(_password);
}
You can see here where the public, private and secret keys are going to be held. Simple so far, but now the work begins. I think one of the tough things to get your head around when using the Bouncy Castle libs is that everything is a set of streams that have things done to them. This is a slight over simplification, but the following bits of code will demonstrate this process. Lets look at getting the public key value:
/// <summary>
/// private method to get the public key value.
/// </summary>
/// <param name="_pubKeyPath">string representing the public key path</param>
/// <returns>a PGPPublicKey</returns>
private PgpPublicKey getPublicKey(string _pubKeyPath)
{
PgpPublicKey pubKey;
using (Stream keyin = File.OpenRead(_pubKeyPath))
using (Stream s = PgpUtilities.GetDecoderStream(keyin))
{
PgpPublicKeyRingBundle pubKeyBundle = new PgpPublicKeyRingBundle(s);
pubKey = pubKeyBundle.GetPublicKey(_keyID);
if (pubKey == null)
throw new Exception("The public key value is null!");
}
return pubKey;
}
This is the on ramp with PGP, getting the public key value represented in the code itself. It also shows how Bouncy Castle likes to work with streams, the first stream here opens the file in which the key is stored, the second stream then goes on to get the value from it. With that done, lets get the secret key:
/// <summary>
/// private method to get the secret key value.
/// </summary>
/// <param name="_privKeyPath">string representing the private key path</param>
/// <returns>a PGPSecretKey</returns>
private PgpSecretKey getSecretKey(string _privKeyPath)
{
PgpSecretKey secKey;
using (Stream keyin = File.OpenRead(_privKeyPath))
using (Stream s = PgpUtilities.GetDecoderStream(keyin))
{
PgpSecretKeyRingBundle secKeyBundle = new PgpSecretKeyRingBundle(s);
secKey = secKeyBundle.GetSecretKey(_keyID);
if (secKey == null)
throw new Exception("The secret key value is null!");
}
return secKey;
}

Here we pretty much do the same thing as before, but this time we are looking at the private key file path. Dont get confused here, this method gets the secret key value, to do this we point it at the private key file and supply a key ID. To get the private key itself, we use the following:

/// <summary>
/// private method to get the private key value.
/// </summary>
/// <returns>a PGPPrivateKey</returns>
private PgpPrivateKey getPrivateKey(string _password)
{
PgpPrivateKey privKey = PGPSecretKey.ExtractPrivateKey(_password.ToCharArray());
if (privKey == null)
{
return null;
}
else
{
return privKey;
}
}
This one is a lot smaller, but with it we get our private key. And with that, we are done with our PGPKeys class, lets move on to encrypting, signing and compressing using these keys. Remember what I said about streams? Well, this is how it comes into play, lets encrypt some data:
private Stream encrypt(Stream output)
{
PgpEncryptedDataGenerator pgpEncDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Twofish, new SecureRandom());
pgpEncDataGen.AddMethod(_pgpKeys.PGPPublicKey);
Stream encryptedOutput = pgpEncDataGen.Open(output, new byte[BufferSize]);
return encryptedOutput;
}
Thats it done, this will encrypt a stream only. On its own, this isnt so useful, you can see where I am specifying the encryption algorythm (I decided to use TwoFish, apparently this is a very secure algorythm) and where the public key value is applied. If you just hold in your mind for a moment that this represents a stream of encrypted data at its conclusion, we can look at how it is compressed to an archive:
private Stream compress(Stream output)
{
PgpCompressedDataGenerator pgpCompDataGen = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
Stream compressedEncryptedOut = pgpCompDataGen.Open(output);
return compressedEncryptedOut;
}

The stream we just emcrypted is now passed to this method which compresses it to a zip - right now though, this is still a stream, nothing more. The next thing we need to do is apply the literal data to this stream:
private Stream literalOutput(Stream compressedOut, FileInfo file)
{
PgpLiteralDataGenerator pgpLiteralDataGen = new PgpLiteralDataGenerator();
Stream literal = pgpLiteralDataGen.Open(compressedOut, PgpLiteralData.Binary, file);
return literal;
}

Now we have a stream that has been encrypted, compressed and had its literal data applied to it. I am going to point out here that if you dont want to create a PGP encrypted file, but instead just want a PGP encrypted stream, then you could just use the encrypt method. Anyway, now we are ready to write and sign our archive:
private static void writeAndSign(Stream ouput, Stream literalout, FileStream inputFile, PgpSignatureGenerator sigGen)
{
int length = 0;
byte[] buf = new byte[BufferSize];
while ((length = inputFile.Read(buf, 0, buf.Length)) > 0)
{
literalout.Write(buf, 0, length);
sigGen.Update(buf, 0, length);
}
sigGen.Generate().Encode(ouput);
}

The last thing we want to do here, is sign the encrypted data itself:
private PgpSignatureGenerator sigGen(Stream compressedOut)
{
const bool Iscritical = false;
const bool IsNested = false;
PublicKeyAlgorithmTag tag = _pgpKeys.PGPSecretKey.PublicKey.Algorithm;
PgpSignatureGenerator pgpSigGen = new PgpSignatureGenerator(tag, HashAlgorithmTag.Sha1);
pgpSigGen.InitSign(PgpSignature.BinaryDocument, _pgpKeys.PGPPrivateKey);
foreach (string userID in _pgpKeys.PGPSecretKey.PublicKey.GetUserIds())
{
PgpSignatureSubpacketGenerator subPackGen = new PgpSignatureSubpacketGenerator();
subPackGen.SetSignerUserId(Iscritical, userID);
pgpSigGen.SetHashedSubpackets(subPackGen.Generate());
break;
}
pgpSigGen.GenerateOnePassVersion(IsNested).Encode(compressedOut);
return pgpSigGen;
}

Now we are able to encrypt, compress, sign and write our chosen file to an archive. All we need to do now is tie it up nicely:

public void EncryptSignAndZip(Stream output, FileInfo unencryptedinput)
{
if (output == null)
{
throw new ArgumentNullException("The output stream cannot be null");
}
if (unencryptedinput == null)
{
throw new ArgumentNullException("You must supply a filename to encrypt");
}
if (!File.Exists(unencryptedinput.FullName))
{
throw new ArgumentNullException(unencryptedinput + " does not exist");
}
using (Stream encryptedout = encrypt(output))
using (Stream compressedOut = compress(encryptedout))
{
PgpSignatureGenerator signature = sigGen(compressedOut);
using (Stream literalOut = literalOutput(compressedOut, unencryptedinput))
using (FileStream inputfile = unencryptedinput.OpenRead())
{
writeAndSign(compressedOut, literalOut, inputfile, signature);
}
}
}

Again, I am validating the arguments as they go in to make sure that they are not null values. You can also see here how Bouncy Castle works with the streams we are creating. However, thats it - these classes will create an encrypted zip file for you with what ever you want in them. To make use of this class, I used this in testing:
string pubKeyPath = @"C:\GnuPG\pubring.gpg";
string privKeyPath = @"C:\GnuPG\secring.gpg";
string password = "password";
long _keyID = 3699527550217851901;
PGPEncrpyt test = new PGPEncrpyt(pubKeyPath, privKeyPath, password, _keyID);
FileInfo myFile = new FileInfo(@"C:\test\app.config");
FileStream mystream = new FileStream(@"C:\test\somefile.zip", FileMode.Create, FileAccess.Write);
test.EncryptSignAndZip(mystream, myFile);

And thats it, I using the code above I was able to create an encrypted zip, which I was able to decrypt and extract using GnuPG. I have written the code to decrypt it yet as at this time, I dont really need it - the files I am encrypting will normally be sent on to other organisations etc. One thing I may make use of in future is the ability to encrypt just a stream - this may become very useful for me when it comes to sending data over a network to other parts of the business. The complete code is as follows, PGPKeys:

using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.IO;
using Org.BouncyCastle.Bcpg;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Tools.PGP.Crypto
{
/// <summary>
/// Nested class to get information on out PGP Keys
/// </summary>
public class PGPKeys
{
/// <summary>
/// long representing the key ID we want to target.
/// </summary>
private long _keyID;
/// <summary>
/// string to represent the path to the private key file.
/// </summary>
private string _privKeyPath;
/// <summary>
/// string to represent the path to the public key file.
/// </summary>
private string _pubKeyPath;
/// <summary>
/// string to represent the passphrase used with PGP.
/// </summary>
private string _password;
public PgpPublicKey PGPPublicKey { get; private set; }
public PgpPrivateKey PGPPrivateKey { get; private set; }
public PgpSecretKey PGPSecretKey { get; private set; }
/// <summary>
/// Constructor to manage variables for us.
/// </summary>
/// <param name="pubKeyPath">string representing the public key path</param>
/// <param name="privKeyPath">string representing the private key path</param>
/// <param name="password">string representing the passphrase</param>
/// <param name="keyID">long representing the key ID we want to use</param>
public PGPKeys(string pubKeyPath, string privKeyPath, string password, long keyID)
{
if (!File.Exists(pubKeyPath))
{
throw new ArgumentNullException("Could not find the public key at " + pubKeyPath);
}
else
{
_pubKeyPath = pubKeyPath;
}
if (!File.Exists(privKeyPath))
{
throw new ArgumentNullException("Could not find the private key at " + privKeyPath);
}
else
{
_privKeyPath = privKeyPath;
}
if (String.IsNullOrEmpty(password))
{
throw new ArgumentNullException("The password must not be null");
}
else
{
_password = password;
}
if (keyID == 0)
{
throw new ArgumentNullException("The key ID must not be null");
}
else
{
_keyID = keyID;
}
PGPPublicKey = getPublicKey(_pubKeyPath);
PGPSecretKey = getSecretKey(_privKeyPath);
PGPPrivateKey = getPrivateKey(_password);
}
/// <summary>
/// private method to get the public key value.
/// </summary>
/// <param name="_pubKeyPath">string representing the public key path</param>
/// <returns>a PGPPublicKey</returns>
private PgpPublicKey getPublicKey(string _pubKeyPath)
{
PgpPublicKey pubKey;
using (Stream keyin = File.OpenRead(_pubKeyPath))
using (Stream s = PgpUtilities.GetDecoderStream(keyin))
{
PgpPublicKeyRingBundle pubKeyBundle = new PgpPublicKeyRingBundle(s);
pubKey = pubKeyBundle.GetPublicKey(_keyID);
if (pubKey == null)
throw new Exception("The public key value is null!");
}
return pubKey;
}
/// <summary>
/// private method to get the secret key value.
/// </summary>
/// <param name="_privKeyPath">string representing the private key path</param>
/// <returns>a PGPSecretKey</returns>
private PgpSecretKey getSecretKey(string _privKeyPath)
{
PgpSecretKey secKey;
using (Stream keyin = File.OpenRead(_privKeyPath))
using (Stream s = PgpUtilities.GetDecoderStream(keyin))
{
PgpSecretKeyRingBundle secKeyBundle = new PgpSecretKeyRingBundle(s);
secKey = secKeyBundle.GetSecretKey(_keyID);
if (secKey == null)
throw new Exception("The secret key value is null!");
}
return secKey;
}
/// <summary>
/// private method to get the private key value.
/// </summary>
/// <returns>a PGPPrivateKey</returns>
private PgpPrivateKey getPrivateKey(string _password)
{
PgpPrivateKey privKey = PGPSecretKey.ExtractPrivateKey(_password.ToCharArray());
if (privKey == null)
{
return null;
}
else
{
return privKey;
}
}
}
}


And PGPEncrypt:
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.IO;
using Org.BouncyCastle.Bcpg;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Tools.PGP.Crypto
{
/// <summary>
/// Public class to encrypt and compress files to a zip archive using PGP
/// </summary>
public class PGPEncrpyt
{
/// <summary>
/// instansiate a PGPKeys object
/// </summary>
private PGPKeys _pgpKeys;
private const int BufferSize = 0x10000;
public PGPEncrpyt(string _pubKeyPath, string _privKeyPath, string _password, long _keyID)
{
_pgpKeys = new PGPKeys(_pubKeyPath, _privKeyPath, _password, _keyID);
}
private static void writeAndSign(Stream ouput, Stream literalout, FileStream inputFile, PgpSignatureGenerator sigGen)
{
int length = 0;
byte[] buf = new byte[BufferSize];
while ((length = inputFile.Read(buf, 0, buf.Length)) > 0)
{
literalout.Write(buf, 0, length);
sigGen.Update(buf, 0, length);
}
sigGen.Generate().Encode(ouput);
}
private Stream encrypt(Stream output)
{
PgpEncryptedDataGenerator pgpEncDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Twofish, new SecureRandom());
pgpEncDataGen.AddMethod(_pgpKeys.PGPPublicKey);
Stream encryptedOutput = pgpEncDataGen.Open(output, new byte[BufferSize]);
return encryptedOutput;
}
private Stream compress(Stream output)
{
PgpCompressedDataGenerator pgpCompDataGen = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
Stream compressedEncryptedOut = pgpCompDataGen.Open(output);
return compressedEncryptedOut;
}
private Stream literalOutput(Stream compressedOut, FileInfo file)
{
PgpLiteralDataGenerator pgpLiteralDataGen = new PgpLiteralDataGenerator();
Stream literal = pgpLiteralDataGen.Open(compressedOut, PgpLiteralData.Binary, file);
return literal;
}
private PgpSignatureGenerator sigGen(Stream compressedOut)
{
const bool Iscritical = false;
const bool IsNested = false;
PublicKeyAlgorithmTag tag = _pgpKeys.PGPSecretKey.PublicKey.Algorithm;
PgpSignatureGenerator pgpSigGen = new PgpSignatureGenerator(tag, HashAlgorithmTag.Sha1);
pgpSigGen.InitSign(PgpSignature.BinaryDocument, _pgpKeys.PGPPrivateKey);
foreach (string userID in _pgpKeys.PGPSecretKey.PublicKey.GetUserIds())
{
PgpSignatureSubpacketGenerator subPackGen = new PgpSignatureSubpacketGenerator();
subPackGen.SetSignerUserId(Iscritical, userID);
pgpSigGen.SetHashedSubpackets(subPackGen.Generate());
break;
}
pgpSigGen.GenerateOnePassVersion(IsNested).Encode(compressedOut);
return pgpSigGen;
}
public void EncryptSignAndZip(Stream output, FileInfo unencryptedinput)
{
if (output == null)
{
throw new ArgumentNullException("The output stream cannot be null");
}
if (unencryptedinput == null)
{
throw new ArgumentNullException("You must supply a filename to encrypt");
}
if (!File.Exists(unencryptedinput.FullName))
{
throw new ArgumentNullException(unencryptedinput + " does not exist");
}
using (Stream encryptedout = encrypt(output))
using (Stream compressedOut = compress(encryptedout))
{
PgpSignatureGenerator signature = sigGen(compressedOut);
using (Stream literalOut = literalOutput(compressedOut, unencryptedinput))
using (FileStream inputfile = unencryptedinput.OpenRead())
{
writeAndSign(compressedOut, literalOut, inputfile, signature);
}
}
}
}
}

5 comments:

  1. hi there, i'm having trouble getting this to work. When I create the public key using Kleopatra and I'm passing in the key ID, i'm getting the following error: The public key value is null! in the function call:

    private PgpPublicKey getPublicKey(string _pubKeyPath)
    {
    PgpPublicKey pubKey;
    using (Stream keyin = File.OpenRead(_pubKeyPath))
    {
    using (Stream s = PgpUtilities.GetDecoderStream(keyin)) {
    PgpPublicKeyRingBundle pubKeyBundle = new PgpPublicKeyRingBundle(s);
    pubKey = pubKeyBundle.GetPublicKey(_keyID);
    if (pubKey == null)
    throw new Exception("The public key value is null!");
    }
    }
    return pubKey;
    }

    Can somebody please help me. Thanks

    ReplyDelete
  2. The best way to get the I'd is to iterate through the key ring and find the key from there

    ReplyDelete
  3. great post!
    i'm still wondering how i could add multiple public keys to make the file available for multiple definded user! any solution/idea?

    ReplyDelete
  4. Nice site! I am loving it!! Will come back again ??taking you feeds also, Thanks.

    Pgp

    ReplyDelete
  5. Hi Raphael , You are such a genius. Thanks a lot for masterful sharing.

    ReplyDelete