02 October 2009

Implementing PGP File Encryption in ASP.Net/C# Using GnuPG

I was recently asked to implement PGP encryption for a file before sending it to a vendor.  In performing my research, I found GnuPG to be the easiest solution to understand as well as posing the least amount of risk for implementation.  The great thing about GnuPG is that you can download it and the instructions are easy to follow for encrypting files.

Unfortunately, I also found are some challenges during the implementation.  The existing documentation on the web is also confusing and is old.  This post is an attempt to update the existing documentation to 2009, provide a working coded example, and give a quick and easy roadmap for implementing PGP in an ASP.Net project.
Step 1 – Download and install GnuPG (GPG)
The main GnuPG page is http://www.gnupg.org.  The Windows download can be found here.  The version I refer to in this post is GnuPG 1.4.10b.

Installing the GnuPG software is easy as it uses a standard installer. 

Note: Be careful when installing the application on the server.  Choose a Windows account that is generic to installations.  There is a mistake in the installation process where important application data is installed in the Application Data directory of the account performing the installation. 

You can find the files by installing GnuPG on your local machine and then looking in C:\Documents and Settings\[%Windows User Name%]\Application Data\GnuPG.  Several key files are installed there and you will need to reference this directory from the ASP.Net code.  This is the biggest ‘gotcha’ for the entire process.


Update (2011-01-12): For 64-bit servers, the directory is C:\Users\[user name]\AppData\Roaming\gnupg
Step 2 – Learn GPG
An excellent introduction to GPG has been written by Brendan Kidwell and can be found here.
You will find excellent instructions for installation and use in Windows and Linux.  Pay special attention to the GPG Cheat Sheet and make sure you successfully encrypt and decrypt files from the command line before proceeding with coding.
You will need to install any public keys needed for encryption before running the code, as well as understanding how to retrieve the name of the name of the key (using –list-keys) before running the C# below.
Step 3 – Implement the PGP Object
Following is the code that I use to encrypt files.  It is heavily indebted to an article at the Code Project on decrypting files by Kurt Mang.

using System;
using System.Diagnostics;
using System.IO;
using System.Security;

/// <summary>
/// File encryption wrapper.  Executes GNU PGP as described here:
/// http://www.glump.net/howto/gpg_intro
/// And downloaded from here:
/// http://www.gnupg.org/download/index.en.html#auto-ref-2
/// </summary>
public class PGP
{
/// <summary>
/// Path to the PGP software
/// </summary>
string _pgpPath = @"C:\Program Files\GNU\GnuPG\gpg.exe";

/// <summary>
/// The path to the PGP file can be changed (if needed) 
/// or executed
/// </summary>
public string PgpPath {
get { return _pgpPath; }
set { _pgpPath = value; }
}
/// <summary>
/// The home directory argument in GnuPG
/// </summary>
string _homeDir;
/// <summary>
/// The location of the PubRing and SecRing files
/// </summary>
public string HomeDirectory {
get { return _homeDir; }
set { _homeDir = value; }
}
/// <summary>
/// Public constructor stores the home directory argument
/// </summary>
public PGP(string homeDirectory) {
HomeDirectory = homeDirectory;
}

/// <summary>
/// Encrypts the file
/// </summary>
/// <param name="keyName">Name of the encryption file</param>
/// <param name="fileFrom">Source file to be encrypted</param>
/// <param name="fileTo">Destination file (after encryption)</param>
public bool Encrypt(string keyName, string fileFrom, string fileTo) {

/// File info
FileInfo fi = new FileInfo(fileFrom);
if(!fi.Exists) {
throw new Exception("Missing file.  Cannot find the file to encrypt.");
}

/// Cannot encrypt a file if it already exists
if(File.Exists(fileTo)) {
throw new Exception("Cannot encrypt file.  File already exists");
}

/// Confirm the existence of the PGP software
if(!File.Exists(PgpPath)) {
throw new Exception("Cannot find PGP software.");
}

/// Turn off all windows for the process
ProcessStartInfo s = new ProcessStartInfo("cmd.exe");
s.CreateNoWindow = true;
s.UseShellExecute = false;
s.RedirectStandardInput = true;
s.RedirectStandardOutput = true;
s.RedirectStandardError = true;
s.WorkingDirectory = fi.DirectoryName;

/// Execute the process and wait for it to exit.  
/// NOTE: IF THE PROCESS CRASHES, it will freeze
bool processExited = false;

using(Process p = Process.Start(s)) {
/// Build the encryption arguments
string recipient = " -r \"" + keyName + "\"";
string output = " -o \"" + fileTo + "\"";
string encrypt = " -e \"" + fileFrom + "\"";
string homedir = " --homedir \"" + HomeDirectory + "\"";
string cmd = "\"" + PgpPath + "\"" + homedir + recipient + output + encrypt;

p.StandardInput.WriteLine(cmd);
p.StandardInput.Flush();
p.StandardInput.Close();
processExited = p.WaitForExit(3500);
p.Close();
}
return processExited;
}

}


A couple of things should be noted about this object:
  • The constructor takes a HomeDirectory argument.  This is the Application Settings directory where the GPG application files have been installed. 
  • Obviously the Windows account that runs the ASP.Net code needs to have access to the executable as well as the ability to run executables and create files.
  • The code checks for the existence of the GPG executable, the source file and the destination file.  The reason is that the application can hang without throwing an exception if anything goes wrong.