Click here to Skip to main content
15,894,284 members
Articles / Programming Languages / C#
Article

Gnu Privacy Guard (GPG/PGP) for .NET [v1.0]

Rate me:
Please Sign up or sign in to vote.
4.94/5 (17 votes)
18 Sep 20033 min read 567K   8.7K   91   134
This article provides a C# wrapper class (GnuPGWrapper) that will enable use of GnuPG (the OpenPGP Internet encryption standard) within a .NET application.

Sample Image - GnuPGDotNet.jpg

Introduction

This article presents GnuPGWrapper v1.0, a wrapper class for GnuPG.

GnuPG stands for GNU Privacy Guard and is GNU's tool for secure communication and data storage. It can be used to encrypt data and to create digital signatures. It includes an advanced key management facility and is compliant with the proposed OpenPGP Internet standard as described in RFC 2440. As such, GnuPG is a complete and free replacement for PGP (Pretty Good Privacy).

This article provides a C# wrapper class (GnuPGWrapper) that will enable use of OpenPGP Internet encryption standard within a .NET world. It is shipped with a demo ASP.NET Web Form (GnuPG.aspx) which calls the wrapper class.

Installation

Prerequisites

  • Complete .NET Environment, e.g. Windows XP Professional + IIS 5.0 + .NET Framework SDK
  • GnuPG for Windows (more about GnuPG)

Procedure

Background

GnuPG ships as a command line program (gpg.exe) acting as a filter (reads from standard input and writes into standard output). Although suitable for scripting on UNIX systems (where calling a command line program from sh or bash is easy), it's pretty hard to integrate this in a production .NET environment.

The GnuPG Wrapper executes the command line program (gpg.exe) in a different process, redirects standard input (stdin), standard output (stdout) and standard error (stderr) streams, and monitors the streams to fetch the results of the encryption/signing operation.

Please note that you must have INSTALLED GnuPG AND generated/imported the appropriate keys before using this class. Refer to the GnuPG manual to do this...

Using the code

In order to use the wrapper class, you need to proceed as follows:

  1. Create an instance of the class
  2. Set the command property to the requested command (SignAndEncrypt, Encrypt, Decrypt, Sign, Verify)
  3. Optionally, set parameters for the command (home directory, originator, recipients, etc...)
  4. Call the ExecuteCommand method with input/output strings variables

The next sections show sample source code for the most command operation (SignAndEncrypt, Decrypt, Verify).

Encrypt and Sign

C#
// Reference My GnuPG wrapping class
using Emmanuel.Cryptography.GnuPG;

// Create GnuPG wrapping class
GnuPGWrapper gpg = new GnuPGWrapper();

// Set command
gpg.command = Commands.SignAndEncrypt;

// Set some parameters from on Web.Config file
gpg.homedirectory = Server.MapPath
       (ConfigurationSettings.AppSettings["homedirectory"]);
gpg.passphrase = ConfigurationSettings.AppSettings["passphrase"];

// Set other parameters from Web Controls
gpg.originator = FromTextBox.Text;
gpg.recipient = ToTextBox.Text;

// Declare input/output variables
// (input is also read from a Web control)
string inputText = MessageTextBox.Text;
string outputText = "";

// Execute GnuPG
gpg.ExecuteCommand(inputText, out outputText);

// Display output text
OutputTextBox.Text = outputText;
OutputTextBox.Visible = true;
ErrorMessage.Visible = false;
ExitCodeLabel.Text = gpg.exitcode.ToString();

Decrypt

C#
using Emmanuel.Cryptography.GnuPG;

GnuPGWrapper gpg = new GnuPGWrapper();

gpg.homedirectory = "C:\Inetpub\wwwroot\GnuPGDotNet\GnuPG"
gpg.passphrase = "My passphrase is so cool I can't remember it"
gpg.command = Commands.Decrypt;

// Execute GnuPG
string outputText = "";
gpg.ExecuteCommand("This is a test message.", out outputText);

// Display output text
[...].

Verify

C#
using Emmanuel.Cryptography.GnuPG;
GnuPGWrapper gpg = new GnuPGWrapper();

gpg.homedirectory = "C:\Inetpub\wwwroot\GnuPGDotNet\GnuPG"
gpg.passphrase = "My passphrase is so cool I can't remember it"
gpg.originator = "me@mycompany.com";
gpg.command = Commands.Verify;

// Execute GnuPG
string outputText = "";
gpg.ExecuteCommand("This is a test message.", out outputText);

// Display output text
[...].

Error handling

Error handling is done via a specific Exception class; method ExecuteCommand raises this exception whenever an error occurs. You calling application can handle this exception as follows:

C#
using Emmanuel.Cryptography.GnuPG;

    try 
    {

        GnuPGWrapper gpg = new GnuPGWrapper();

        gpg.homedirectory = "C:\Inetpub\wwwroot\GnuPGDotNet\GnuPG"
        gpg.passphrase = "My passphrase is so cool I can't remember it"
        gpg.originator = "me@mycompany.com";
        gpg.recipient = "you@yourcompany.com";
        gpg.command = Commands.SignAndEncrypt;

        // Execute GnuPG
        string outputText = "";
        gpg.ExecuteCommand("This is a test message.", out outputText);

        // Display output text
        [...]

    }
    catch (GnuPGException gpge)
    {
        // Display error message
        // Contains a clear text error message, 
        // either from the wrapper or from gpg.exe itself
        ErrorMessage.Text = gpge.Message; 
    }

Points of interest

The GnuPG wrapper:

  • Doesn't use any temporary files to store results; it directly uses streams/pipes.
  • Uses multiple threads to read data from standard input and standard error, preventing any deadlocks.
  • Uses configurable timeouts to prevent blocking calling applications in case of a system/program/process crash
  • Uses a configurable passphrase, which can be stored in a local configuration file (Web.Config) to prevent disclosure of the phrase

About GnuPG and PGP

This class has been developed and tested with GnuPG v1.2.0 (MingW32).

You can check the command line manual page for gpg.exe.

For more about GNU, please refer to http://www.gnu.org/. For more about GnuPG, please refer to http://www.gnupg.org/. For more about OpenPGP (RFC 2440), please refer to http://www.gnupg.org/rfc2440.html. For more about PGP, please refer to http://www.pgpi.org/.

History

  • Date posted: October 30th, 2002
  • Updated: September 19th, 2003

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
France France
Fell into computer software at the age of 11, founder of 3 startups, and now manager of an independent software vendor (ISV) labelled proSDK (www.prosdk.com)... And still a freeware writer and technical article author!

Comments and Discussions

 
GeneralRe: Fix for size limitation on Standard Input Pin
varunkorpol24-Jun-09 12:04
varunkorpol24-Jun-09 12:04 
GeneralRe: Fix for size limitation on Standard Input Pin
robroe24-Jun-09 23:42
robroe24-Jun-09 23:42 
QuestionBinary? Pin
Rick Elliott18-Apr-05 19:09
Rick Elliott18-Apr-05 19:09 
QuestionHow to encrypt by existing public key? Pin
bask190728-Feb-05 6:08
bask190728-Feb-05 6:08 
AnswerRe: How to encrypt by existing public key? Pin
Anonymous29-May-05 5:28
Anonymous29-May-05 5:28 
Generalimportint a given public key Pin
AMOS200021-Feb-05 12:01
AMOS200021-Feb-05 12:01 
GeneralSign and Encrypt Pin
Jim Pauciello12-Nov-04 8:47
Jim Pauciello12-Nov-04 8:47 
GeneralProblem extending wrapper to include decrypt-files Pin
virsum28-Oct-04 5:54
virsum28-Oct-04 5:54 
Hello Mr. Kartmann,

First of all, thanks so much for posting your wrapper at The Code Project. My company is being asked to interface with a client who will be sending us XML files via PGP, and GPG and your wrapper have helped me enormously in my coding efforts.

I am a rather new developer, so I apologize if any of this is tedious. I reviewed your C# code an decided to try to insert a modified copy of the ExecuteCommand method into my own application, but when I debug it I consistently get a timeout error on the application. I am calling the gpg.exe from a Windows network location (drive letter mapping), and I confirmed that the location appears in my system environment variables, in the regedit file, and preceding the executable call. When I try to run it in debug mode and step through the code, I always get up to WaitForExit() and then it times out. When I switch the CreateNoWindow property to false so I can see the console when I debug, I find that the first command executes successfully, and the prompt for password comes up, but the StandardInput override of my password somehow never gets written to the process.

Below is a sample of the code I am using. As you can see, I have tried several permutations already, such as commenting out the StandardOutput override (since the standard output in this case is the write to file) and adding a \r\n to the end of the passphrase to try to force a return. Is there something else I am glaringly missing here?

Your thoughts?

Respectfully,

Kerry


Code:


public string DecodeFile( string file)
{
try
{
string outputText = "";

string gpgOptions = " --homedir\"M:\\406\\XMLImport\\InProgress\"" +
//" --o \"" + fl.FileLocation + inProg + file.Substring(0, file.Length - 4) + "\"" +
" --decrypt-files \"" + fl.FileLocation + inProg + file + "\"" ;
string gpgExecutable = fl.FileLocation + inProg + "gpg.exe";

// Create startinfo object
ProcessStartInfo pInfo = new ProcessStartInfo(gpgExecutable, gpgOptions);
pInfo.WorkingDirectory = "M:\\406\\XMLImport\\InProgress";
pInfo.CreateNoWindow = false;
pInfo.UseShellExecute = false;
// Redirect everything:
// stdin to send the passphrase, stdout to get encrypted message, stderr in case of errors...
pInfo.RedirectStandardInput = true;

/*pInfo.RedirectStandardOutput = true;*/
pInfo.RedirectStandardError = true;
_processObject = Process.Start(pInfo);

// Send pass phrase, if any
_processObject.StandardInput.WriteLine("Verbum Caro Factum Est\r\n");
_processObject.StandardInput.Flush();

// Send input text
_processObject.StandardInput.Close();

// Create two threads to read both output/error streams without creating a deadlock
//ThreadStart outputEntry = new ThreadStart(StandardOutputReader);
//Thread outputThread = new Thread(outputEntry);
//outputThread.Start();
ThreadStart errorEntry = new ThreadStart(StandardErrorReader);
Thread errorThread = new Thread(errorEntry);
errorThread.Start();

if (_processObject.WaitForExit(60000))
{
// Process exited before timeout...
// Wait for the threads to complete reading output/error (but use a timeout!)
/*if (!outputThread.Join(30000))
{
outputThread.Abort();
}*/
if (!errorThread.Join(30000))
{
errorThread.Abort();
}
}
else
{
// Process timeout: PGP hung somewhere... kill it (as well as the threads!)
_outputString = "";
_errorString = "Timed out after 60 seconds";
_processObject.Kill();
/*if (outputThread.IsAlive)
{
outputThread.Abort();
}*/
if (errorThread.IsAlive)
{
errorThread.Abort();
}
}

// Check results and prepare output
int exitcode = _processObject.ExitCode;
if (exitcode == 0)
{
outputText = _outputString;
}
else
{
if (_errorString == "")
{
_errorString = "GPGNET: [" + _processObject.ExitCode.ToString() + "]: Unknown error";
}
throw new Exception("DecodeFiles(): GPG Code: " + _errorString);
}
return outputText;
}
catch (Exception e)
{
throw new Exception("DecodeFiles(): " + e.Message);
}
}

GeneralRe: Problem extending wrapper to include decrypt-files Pin
virsum30-Oct-04 4:00
virsum30-Oct-04 4:00 
GeneralRe: Problem extending wrapper to include decrypt-files Pin
Farishm24-Jan-05 4:45
Farishm24-Jan-05 4:45 
Generalproblem Pin
Jim Pauciello19-Oct-04 8:45
Jim Pauciello19-Oct-04 8:45 
GeneralRe: problem Pin
Emmanuel Kartmann20-Oct-04 0:15
Emmanuel Kartmann20-Oct-04 0:15 
GeneralRe: problem Pin
Jim Pauciello21-Oct-04 5:19
Jim Pauciello21-Oct-04 5:19 
GeneralRe: problem Pin
Emmanuel Kartmann21-Oct-04 5:27
Emmanuel Kartmann21-Oct-04 5:27 
GeneralProblem decrypting when string read in from file Pin
Paulj9113-Sep-04 19:13
Paulj9113-Sep-04 19:13 
GeneralProblem decrypting message using Wrapper Pin
jtseung2-Sep-04 18:00
jtseung2-Sep-04 18:00 
GeneralRe: Problem decrypting message using Wrapper Pin
Emmanuel Kartmann2-Sep-04 21:29
Emmanuel Kartmann2-Sep-04 21:29 
GeneralRe: Problem decrypting message using Wrapper Pin
jtseung9-Sep-04 0:19
jtseung9-Sep-04 0:19 
GeneralUsing GnuPG Wrapper Pin
Jose Betances22-Apr-04 4:46
Jose Betances22-Apr-04 4:46 
GeneralRe: Using GnuPG Wrapper Pin
jtseung2-Sep-04 18:03
jtseung2-Sep-04 18:03 
GeneralRe: Using GnuPG Wrapper Pin
Anonymous3-Sep-04 2:38
Anonymous3-Sep-04 2:38 
Generalclearsign Pin
protecweb27-Feb-04 3:59
protecweb27-Feb-04 3:59 
GeneralRe: clearsign Pin
Emmanuel Kartmann27-Feb-04 7:29
Emmanuel Kartmann27-Feb-04 7:29 
GeneralDownload and decrypt/encrypt Pin
bevans197521-Oct-03 8:18
bevans197521-Oct-03 8:18 
QuestionSize limition on Standard Input? Pin
dlgauthier27-Aug-03 3:53
dlgauthier27-Aug-03 3:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.