What You'll Need
You will need to download the following two components for the encryption performed in this article:
- Gpg for Windows
- Starksoft OpenPGP component for .NET (see note at end of article regarding this component).
Background
Where you need to encrypt data using PGP, GnuPG is an excellent choice to get the job done.
And with the Starksoft OpenPGP Component for .NET, we can write against an API which interacts with GnuPG to enable integration with your custom .NET apps.
Install GnuPG for Windows
The first step is to install GnuPG for Windows. Go to the GnuPG for Windows site, download the exe, and run the installer.
(Make it easier on yourself and accept the default location for where it will be installed.)
Before we write a line of code, we need to create a key for encryption/decryption.
Basic Step by Step Tutorial for GPG Usage
I am by no means an expert, so this is a very basic tutorial on usage of GPG. It’s just enough to get you up and running.
PGP encryption uses Public-key Cryptography. So, to truly test this out, you should have
two computers (either physical or virtual). The first computer (Computer A) should create a private key, then export it as a public key.
Then Computer B can use that public key to encrypt some data, which it can then transmit to Computer A. At that point, Computer A can use its private key to decrypt that data.
So, fire up Computer A first and create a private key. Do this by running the command:
gpg --gen-key
See the screenshot below for how I answered the questions that followed. You can accept defaults by pressing
Enter. A couple of questions prompt you to enter a letter.
In those cases, the default is in brackets.
Note the dialog box which is launched to capture the all-important pass-phrase. Do not forgot that pass-phrase. You will require it to decrypt anything which is encrypted with that key.
You can view your keys by entering the command:
gpg --list-keys
OK. Now that we have a private key on our key-ring, we can export it as a public key. You can do that by entering the following command,
using the comment which you input when you create your private key (you can see from the screenshot above that I used the comment "For private encrypted stuff"):
gpg --armor --output pubkey.txt --export 'For private encrypted stuff'
That will create a text file called pubkey.txt in the folder which has context vis-a-vis the Console in which you ran that command. (Or you could create it with a full
path - e.g., "D:\pubkey.txt".)
Now, on to the encryption. We will now move to Computer B. Computer B should also have GPG for Windows installed on it. To import the key (that we just exported above) onto Computer B, run:
gpg --import "D:\pubkey.txt"
And that's it. We can now move onto some code! We will use the imported key to encrypt some data. Then, at a later stage, we can take that encrypted data back
to Computer A where we will decrypt it.
Create the Contract for the Encryption Service
First off, we will create a contract for our service to clearly delineate exactly what it will do. So, create a Visual Studio solution, add a
Class Library (name it "Contracts"),
and add the following interface to the class library:
public interface IEncryptionService
{
FileInfo EncryptFile(string keyUserId, string sourceFile, string encryptedFile);
}
The EncryptFile
method will return a FileInfo
object representing the encrypted file that is created during the operation. Now, we will implement that interface.
Create the Encryption Service
Add another new project to your solution called "Service".
Create a new class called EncryptionService
which will implement our interface. Add a reference to the Contracts project which contains the
IEncryptionService
interface.
Then you will need to download the Starksoft OpenPGP component for .NET. (At the time of writing, this component is being migrated from SourceForge to CodePlex.
So, if you cannot find it on the Internet, just use the copy included with the download code for this article.)
Then, implement that IEncryptionService
interface:
public class EncryptionService : IEncryptionService
{
private GnuPG gpg = new GnuPG();
private string appPath;
public EncryptionService()
{
}
public EncryptionService(string appPath)
{
this.appPath = appPath;
}
public FileInfo EncryptFile(string keyUserId, string sourceFile, string encryptedFile)
{
if (string.IsNullOrEmpty(keyUserId))
throw new ArgumentException("keyUserId parameter is either empty or null", "keyUserId");
if (string.IsNullOrEmpty(sourceFile))
throw new ArgumentException("sourceFile parameter is either empty or null", "sourceFile");
if (string.IsNullOrEmpty(encryptedFile))
throw new ArgumentException("encryptedFile parameter is either empty or null", "encryptedFile");
using (Stream sourceFileStream = new FileStream(sourceFile, FileMode.Open))
{
using (Stream encryptedFileStream = new FileStream(encryptedFile, FileMode.Create))
{
gpg.BinaryPath = Path.GetDirectoryName(appPath);
gpg.Recipient = keyUserId;
gpg.Encrypt(sourceFileStream, encryptedFileStream);
return new FileInfo(encryptedFile);
}
}
}
}
The constructor sets the path to the gpg2 executable. If you performed a vanilla install, that path will be something like
C:\Program Files\GNU\GnuPG\pub\gpg2.exe. The encryption operation simply uses the Starksoft component to encrypt the file from the source stream to the destination stream.
Note that when specifying the BinaryPath
of the GnuPG
object, you need to assign the directory which is the parent of
that of the executable. I'm not sure why that is the case; trial
and error is how I figured that out.
The Starksoft library does have a couple of custom Exception classes (GnuPGBadPassphraseException
,
GnuPGException
), but they don’t add any special
custom members. That being the case, being mere wrappers of the System.Exception
class, we’ll let them propagate to the caller as is in our service.
So, if we run that EncryptFile
method on a file on Computer B, we will have an PGP-encrypted file which can only be decrypted by the party holding the private key. Now, we'll turn our minds to decryption.
Augment the Contract for the Encryption Service
The Encryption service so far, only contains the one method
EncryptFile
. We will now add a second method for decrypting the file:
public interface IEncryptionService
{
FileInfo DecryptFile(string encryptedSourceFile, string decryptedFile);
FileInfo EncryptFile(string keyUserId, string sourceFile, string encryptedFile);
}
The DecryptFile
method will return a FileInfo
object representing the decrypted file that is created during the operation.
Augment the Encryption Service
In the EncryptionService
we will implement the DecryptFile
method as follows:
public FileInfo DecryptFile(string encryptedSourceFile, string decryptedFile)
{
if (string.IsNullOrEmpty(encryptedSourceFile))
throw new ArgumentException("encryptedSourceFile parameter is either empty or null", "encryptedSourceFile");
if (string.IsNullOrEmpty(decryptedFile))
throw new ArgumentException("decryptedFile parameter is either empty or null", "decryptedFile");
using (FileStream encryptedSourceFileStream = new FileStream(encryptedSourceFile, FileMode.Open))
{
encryptedSourceFileStream.Position = 0;
using (FileStream decryptedFileStream = new FileStream(decryptedFile, FileMode.Create))
{
gpg.BinaryPath = Path.GetDirectoryName(appPath);
gpg.Decrypt(encryptedSourceFileStream, decryptedFileStream);
}
}
return new FileInfo(decryptedFile);
}
As you can see, we create a FileStream
object for both the encrypted source file and the resulting decryted target file. With the help of the Starksoft OpenPGP component, we can pass the two streams into the Decrypt
method of the gpg
object and this will invoke the external Gpg for Windows executable which will open a dialog that requires us to enter the passphrase which we created for this key (note that the dialog may take a few seconds to display, as GnuPG for Windows is coming in to do its part).
So, if we take our encrypted file back to Computer A and run the DecryptFile method on it, once we enter the correct passphrase, the stream will be decrypted and our original file will now be decrypted.
Things to Consider
In this article, I have outlined an approach for implementing PGP encryption by invoking a 3rd party component through C#. This raises a few questions. Why didn't I implement PGP encryption myself? Why hand over control to a 3rd party component to perform the actual encryption/decryption operations?
This is always a cost/benefit analysis:
- Ask yourself, "am I a cryptography expert". The good people who wrote GnuPG are. I am not.
- There will be a significant decrease in development time if you do not have to implement PGP yourself. Encryption of non-text data also raises the difficulty level, which you will need to consider if you have to encrypt pdfs or some such. Definite benefit here.
- However, you will be sacrificing control. When you invoke GnuPG, you are going out of process. Using Gpg for Windows, you don't even have control over the dialogs which the user needs to input data into. This will be important if you want to change the look and feel of those dialogs.
So, this approach will not be the right decision every time. But in many cases, it is a nice affordable solution to the encryption problem.
Conclusion
As noted, this is a very basic demonstration of the encryption capabilities of GnuPG and the .NET Starksoft component which enable us to interact with it.
And as mentioned earlier in the article, the Starksoft component is in the process of being migrated to CodePlex as part of the Biko project.
For the time being, the DLL in the download code for that component is fine.
I hope this article will serve as a helpful introduction to using C# for PGP encryption/decryption.