Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security / encryption

PGP Encryption with C#

4.94/5 (23 votes)
6 Jan 2013CPOL7 min read 253K   6.7K  
Encrypting and Decrypting Files with GnuPG

What You'll Need

You will need to download the following two components for the encryption performed in this article:

  1. Gpg for Windows
  2. 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.

Key Creation

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:

C#
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:

C#
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)
    {
        // check parameters
        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");
        // Create streams - one for the unencrypted source file and one for the decrypted destination file
        using (Stream sourceFileStream = new FileStream(sourceFile, FileMode.Open))
        {
            using (Stream encryptedFileStream = new FileStream(encryptedFile, FileMode.Create))
            {
                //  Specify the directory containing gpg.exe (not sure why).
                gpg.BinaryPath = Path.GetDirectoryName(appPath);
                gpg.Recipient = keyUserId;
                //  Perform encryption
                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 (GnuPGBadPassphraseExceptionGnuPGException), 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:
C#
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:

C#
public FileInfo DecryptFile(string encryptedSourceFile, string decryptedFile)
{
    // check parameters
    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))
    {
        //  make sure the stream is at the start.
        encryptedSourceFileStream.Position = 0;
 
        using (FileStream decryptedFileStream = new FileStream(decryptedFile, FileMode.Create))
        {
            //  Specify the directory containing gpg.exe (again, not sure why).
            gpg.BinaryPath = Path.GetDirectoryName(appPath);
 
            //  Decrypt
            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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)