GnuPG Encryption for Email, XML, and Others in a VB.NET Wrapper
page 1 of 1
Published: 23 Jan 2006
Unedited - Community Contributed
Abstract
For email there is only one security standard that is highly used--PGP or the public domain version GnuPG. I recently had to apply GnuPG to a commercial email program and found that nowhere on the Internet was this asymmetric encryption available for .NET usage. It was quite a challenge coding one, so I thought it worthy of an article to save people from what I had to go through to get there. Full source code is available and discussed with links to more.
by Terry Voss
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 23829/ 27

Introduction

I am assuming in this article some knowledge of PGP, GnuPG, or asymmetric encryption:
If you don't have this prerequisite, you may garner it at: http://www.gnupg.org.

PGP or GnuPG (the public domain near equivalent) are important encryptions because they are standards that are being used in email and secure XML transfers and other areas. They were originally developed in the Linux/C language environ but can be used in Windows and .NET easily with a wrapper that is easily modified to support any features you want to use.

Wrapper Source GnuPg Source Link to FTP Link for 4.2.1 Version

Commands/Options Supported by this Article's Wrapper

1.   Initial Key Generation

2.   Signing and Encryption

3.   Decryption

4.   Importing of a Friend's Public Key to Your Public Key Ring

5.   Exporting of Your Public Key to a Friend's Public Key Ring

These features will support secure signing and encryption with any number of clients. This wrapper is easily modifiable to support any portion of the many commands and options available in the GnuPG interface.

Looking at the Code for Initial Generation of Keys

GnuPG is asymmetric encryption so there will be a private key that you must secure for yourself and a public key that you will want to disseminate on your website or by emailing to allow people to send encrypted items to you. To accomplish this, I had to play around quite a bit to find the proper options allowing automatic generation so that my commercial email program can generate for each client with no knowledge of GnuPG. I would not suggest changing the options on this command. You can change the size of your subkey to 2048 for more security, but slower generation times. Generation took about eight seconds in my environment.

Notes:

1. User is my source of input for password etc, you need to come up with your own here.
2. You may want to expire your keys, etc.
3. Note that when you redirect stdin, and/or stdout, and/or stderr you must not use shellexecute or have the window open, except to test for errors.
4. The reason I use stdin here to supply parameters versus a file parameter, which is allowed, is for added security of not having the passphrase sitting in a file that could be hacked.

Code Listing #1

  Public Sub GenerateInitialKeys(ByVal path As String)
    Dim user As SetupxEntity = Util.User
    Dim paramContents As String = String.Empty
    paramContents &= "%echo Generating keys..." & CrLf
    paramContents &= "Key-Type: DSA" & CrLf
    paramContents &= "Subkey-Type: ELG-E" & CrLf
    paramContents &= "Subkey-Length: 1024" & CrLf
    paramContents &= "Passphrase: tester999" & CrLf '& user.Password.Trim & CrLf
    paramContents &= "Key-Length: 1024" & CrLf
    paramContents &= "Name-Real: " & Me.GetNameReal() & CrLf
    paramContents &= "Name-Email: " & user.Email.Trim & CrLf
    paramContents &= "Name-Comment: autogenerated" & CrLf
    paramContents &= "Expire-Date: 0" & CrLf
    paramContents &= "%commit" & CrLf
    paramContents &= "%echo Completed Successfully" & CrLf
    Dim gpgOptions As String 
    gpgOptions = "--homedir . “
    gpgOptions &= “--no-tty “
    gpgOptions &= “--status-fd=2 “
    gpgOptions &= “--no-secmem-warning “
    gpgOptions &= “--batch --gen-key"
    Dim gpgExecutable As String = path & "gpg.exe"
    Dim pinfo As New ProcessStartInfo(gpgExecutable, gpgOptions)
    pinfo.WorkingDirectory = path
    pinfo.CreateNoWindow = True
    pinfo.UseShellExecute = False
    ' Redirect stdin to input parameters, stderr in case of errors
    pinfo.RedirectStandardInput = True
    pinfo.RedirectStandardError = True
    _processObject = Process.Start(pinfo)
    _processObject.StandardInput.WriteLine(paramContents)
    _processObject.StandardInput.Flush()
    _processObject.StandardInput.Close()
    _errorString = ""
    Dim errorEntry As New ThreadStart(AddressOf StandardErrorReader)
    Dim errorThread As New Thread(errorEntry)
    errorThread.Start()
    If _processObject.WaitForExit(ProcessTimeOutMilliseconds) Then
      If Not errorThread.Join(ProcessTimeOutMilliseconds / 2) Then
        errorThread.Abort()
      End If
    Else
      ' Process timeout: PGP hung somewhere... kill it (as well as the threads!)
      _outputString = ""
      _errorString = "Timed out after " 
_errorString &= ProcessTimeOutMilliseconds.ToString & " milliseconds"
      _processObject.Kill()
      If errorThread.IsAlive Then
        errorThread.Abort()
      End If
    End If
    ' Check results and prepare output
    _exitcode = _processObject.ExitCode
    If Not _exitcode = 0 Then
      If _errorString = "" Then
        _errorString = "GPGNET: [" 
  _errorString &= _processObject.ExitCode.ToString() & "]: Unknown error"
      End If
      Throw New GnupgException(_errorString)
    End If
  End Sub

Looking at the Code for BuildingOptions

The ExecuteCommand method is examined next, and you will see that that method handles both encryption and decryption. I could have easily created an Encryption method and a Decryption method but wanted to show how options could be built in a BuildOptions method called by ExecuteCommand to use properties to set and use many of the powerfull commands of GnuPG.

In the BuildOptions method, property values are examined to build an option string fed to ExecuteCommand. I put some unused cases in there to show how you might be able to unify all GnuPG functionality within one ExecuteCommand method if you wanted to.

The --armor option means ASCII versus binary, and –trust-model always means take the simple version of trust so that we don’t have to get overly concerned about levels of trust. We will be trusting visually by seeing the emails sender and thinking yes, I am receiving encrypted from that person.

Code Listing#2

  Protected Function BuildOptions() As String
    Dim optionsBuilder As New StringBuilder("", 255)
    Dim recipientNeeded As Boolean = False
    Dim passphraseNeeded As Boolean = False
    If _homedirectory IsNot Nothing And Not _homedirectory = "" Then
      optionsBuilder.Append("--homedir " & Quote)
      optionsBuilder.Append(_homedirectory)
      optionsBuilder.Append(Quote & " ")
    End If
    If _yes Then optionsBuilder.Append("--yes ")
    If _batch Then optionsBuilder.Append("--batch ")
    Select Case _command
      Case Commands.SignAndEncrypt
        optionsBuilder.Append("--sign ")
        optionsBuilder.Append("--encrypt --armor ")
        If _trust Then
          optionsBuilder.Append("--trust-model always ")
        End If
        recipientNeeded = True
        passphraseNeeded = True
      Case Commands.Decrypt
        optionsBuilder.Append("--decrypt ")
        If _trust Then
          optionsBuilder.Append("--trust-model always ")
        End If
      Case Commands.Import
        optionsBuilder.Append("--import ")
      Case Commands.Export
        optionsBuilder.Append("--armor --export " & Util.User.Email.Trim)
      Case Commands.Genkey
        optionsBuilder.Append("--batch --gen-key ")
    End Select
    If _recipient IsNot Nothing And Not _recipient = "" Then
      optionsBuilder.Append("--recipient ")
      optionsBuilder.Append(_recipient)
      optionsBuilder.Append(" ")
    Else
      If recipientNeeded Then
        Throw New GnupgException("GPGNET: Missing 'recipient' parameter")
      End If
    End If
    If _originator IsNot Nothing And Not _originator = "" Then
      optionsBuilder.Append("--default-key ")
      optionsBuilder.Append(_originator)
      optionsBuilder.Append(" ")
    End If
    If _passphrase Is Nothing Or _passphrase = "" Then
      If passphraseNeeded Then
        Throw New GnupgException("GPGNET: Missing 'passphrase' parameter")
      End If
    End If
    If _passphrase IsNot Nothing And Not _passphrase = "" Then
      optionsBuilder.Append("--passphrase-fd ")
      optionsBuilder.Append(_passphrasefd)
      optionsBuilder.Append(" ")
    Else
      If passphraseNeeded _
And (_passphrase Is Nothing Or Not _passphrase = ""Then
        Throw New GnupgException("GPGNET: Missing 'passphrase' parameter")
      End If
    End If
    Select Case Verbose
      Case VerboseLevel.NoVerbose
        optionsBuilder.Append("--no-verbose ")
      Case VerboseLevel.Verbose
        optionsBuilder.Append("--verbose ")
      Case VerboseLevel.VeryVerbose
        optionsBuilder.Append("--verbose --verbose ")
    End Select
    Return optionsBuilder.ToString
  End Function

Looking at the Code for the ExecuteCommand Method

Here, the gpgOptions string is built dynamically from BuildOptions. Note the inline comments.

Code Listing#3

  Public Sub ExecuteCommand(ByVal inputText As StringByRef outputtext As String)
    outputtext = ""
    Dim gpgOptions As String = BuildOptions()
    Dim gpgExecutable As String = Util.GetGpgPath & "\gpg.exe"
    Dim pinfo As New ProcessStartInfo(gpgExecutable, gpgOptions)
    pinfo.WorkingDirectory = Util.GetGpgPath & "\"
    pinfo.CreateNoWindow = True
    pinfo.UseShellExecute = False
    ' Redirect everything: stdin for passphrase, stdout for encrypted, stderr
    pinfo.RedirectStandardInput = True 
    pinfo.RedirectStandardOutput = True
    pinfo.RedirectStandardError = True
    _processObject = Process.Start(pinfo)
    If _passphrase IsNot Nothing And Not _passphrase = "" Then
      ' write the passphrase into std input
      _processObject.StandardInput.WriteLine(_passphrase)
      _processObject.StandardInput.Flush()
    End If
    ' write the input for decryption or encryption
   _processObject.StandardInput.WriteLine(inputText)
    _processObject.StandardInput.Flush()
    _processObject.StandardInput.Close()
    _outputString = ""
    _errorString = ""
    ' Create two threads to read both output/error streams w/o deadlock
    Dim outputEntry As New ThreadStart(AddressOf StandardOutputReader)
    Dim outputThread As New Thread(outputEntry)
    outputThread.Start()
    Dim errorEntry As New ThreadStart(AddressOf StandardErrorReader)
    Dim errorThread As New Thread(errorEntry)
    errorThread.Start()
    If _processObject.WaitForExit(ProcessTimeOutMilliseconds) Then
      ' process exited before timeout. 
      ‘ Wait for the threads to complete reading output/error (but use a timeout)
      If Not outputThread.Join(ProcessTimeOutMilliseconds / 2) Then
        outputThread.Abort()
      End If
      If Not errorThread.Join(ProcessTimeOutMilliseconds / 2) Then
        errorThread.Abort()
      End If
    Else
      ' Process timeout: PGP hung somewhere... kill it (as well as the threads!)
      _outputString = ""
      _errorString = "Timed out after " & ProcessTimeOutMilliseconds.ToString 
      _errorString &= " milliseconds"
      _processObject.Kill()
      If outputThread.IsAlive Then
        outputThread.Abort()
      End If
      If errorThread.IsAlive Then
        errorThread.Abort()
      End If
    End If
    ' Check results and prepare output
    _exitcode = _processObject.ExitCode
    If _exitcode = 0 Then
      outputtext = _outputString
    Else
      If _errorString = "" Then
        _errorString = "GPGNET: [" & _processObject.ExitCode.ToString() 
        _errorString &= "]: Unknown error"
      End If
      Throw New GnupgException(_errorString)
      ' note custom exception adds to GnuPG's already rich error output
    End If  
    End Sub

Looking at the Code for Using the GnuPG Class to Handle the Main Functions

Since the code for Import and Export are very similar to those shown above, let’s consider how to use the class. The path is very important for all GnuPG commands and is fed to the –homedir parameter plus other places as well.

First, we need to generate the initial keys.

Code Listing#4

  Private Sub genkey1_Click(ByVal sender As System.ObjectByVal e As _ System.EventArgs) _
     Handles genkey1.Click
    Dim gpg As New GnuPG()
    If MessageBox.Show(gpg.GetNameReal & " and   " & Util.User.Email.Trim & CrLf _ 
    & "Is this okay?""Important Keys will be created with Name and Email below",_ 
    MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation) = 1 Then
      Me.setupstatus.Text = "Starting Key Generation: Please wait..."
      Application.DoEvents()
      Dim outputText As String = String.Empty
      Dim path As String = Util.GetGpgPath() & "\"
      gpg.GenerateInitialKeys(path)
      Thread.Sleep(12000) 
      Me.setupstatus.Text = "Key Generation: complete."
    End If
    End Sub

Second, we will use the keys to encrypt a message to email self since that is the only person our public key we just created will encrypt to.

Code Listing#5

  Private Function EncryptBody(ByVal body As StringAs String
    Dim inputText As String = body
    Dim outputText As String = ""
    Try
      Dim gpg As New GnuPG()
      gpg.Homedirectory = Util.GetGpgPath
      gpg.Trust = True
      gpg.Verbose = VerboseLevel.NoVerbose
      gpg.Passphrase = Util.User.Password.Trim
      gpg.Originator = Util.User.Email.Trim
      gpg.Recipient = eaddr.Text.Trim
      gpg.Command = Commands.SignAndEncrypt
      gpg.ExecuteCommand(inputText, outputText)
      status.Text = "Encryption Succeeded"
    Catch gpge As GnupgException
      status.Text = gpge.Message.Replace(CrLf, "")
    End Try
    Return outputText
  End Function

Thirdly, we will decrypt the message that arrives back in our email inbox with the following code. Note that I have handled decryption and importation of a public key with two cases here since receiving a public key or an encrypted message differ by a header type.

Code Listing#6

  Private Sub edecrypt_Click(ByVal sender As System.ObjectByVal e As_ 
    System.EventArgs) Handles edecrypt.Click
    Dim phrase As New Regex("-----.*?-----")
    Dim matchs As MatchCollection = phrase.Matches(browser.DocumentText)
    If matchs.Count > 0 Then
      Dim firstMatch As String = matchs(0).ToString
      Select Case firstMatch
        Case "-----BEGIN PGP PUBLIC KEY BLOCK-----"
          Dim pkHeader As string = string.empty
          pkHeader &= "-----BEGIN PGP PUBLIC KEY BLOCK-----.*?”
          pkHeader &= “END PGP PUBLIC KEY BLOCK-----"
          Dim body As New Regex(pkHeader)          
          Dim publicKey As String = body.Match(browser.DocumentText).ToString
          publicKey = publicKey.Replace("<br>", CrLf) ‘content is in browser
          Try
            Dim gpg As New GnuPG()
            gpg.Homedirectory = Util.GetGpgPath
            gpg.Command = Commands.Import
            gpg.Import(publicKey)
            status.Text = "Public Key imported"
          Catch gpge As GnupgException
            status.Text = gpge.Message.Replace(CrLf, "")
          End Try
        Case "-----BEGIN PGP MESSAGE-----"
          Dim msgHeader As string = string.empty
          msgHeader &= "-----BEGIN PGP MESSAGE-----.*?”
          msgHeader &= “-----END PGP MESSAGE-----"
          Dim body As New Regex(msgHeader)
          Dim encryptedMsg As String = _
          body.Match(browser.DocumentText).ToString.Replace("<br>", CrLf)
          Dim decryptedMsg As String = String.Empty
          Try
            Dim gpg As New GnuPG
            gpg.Homedirectory = Util.GetGpgPath
            gpg.Trust = True
            gpg.Passphrase = Util.User.Password.Trim
            gpg.Verbose = VerboseLevel.NoVerbose
            gpg.Command = Commands.Decrypt
            gpg.ExecuteCommand(encryptedMsg, decryptedMsg)
            browser.DocumentText = Me.GetHtmlPage(decryptedMsg)
            status.Text = "Message Decrypted"
          Catch gpge As GnupgException
            status.Text = gpge.Message.Replace(CrLf, ", ")
          End Try
        Case Else
          status.Text = "Unknown Request"
      End Select
    Else
      status.Text = "Nothing to Decrypt"
    End If
  End Sub

Fourthly, we will export our public key so that people can send encrypted mail to us. I had a lot of problems getting other Linux-based email programs to accept my generated public key, but it turned out to be simply that the Linux computers were on GMT time and were seeing my keys as having been created in the future so they would not accept the key. Security is everything in this world.

Note that the Export method has one input variable that is passed ByRef, so we can modify the variable with our thread output.

Code Listing#6

  Private Sub export_Click(ByVal sender As System.ObjectByVal e As _ 
    System.EventArgs) Handles export.Click
    Dim outputText As String = ""
    Try
      Dim gpg As New GnuPG()
      gpg.Homedirectory = Util.GetGpgPath
      gpg.Command = Commands.Export
      gpg.Export(outputText)
      status.Text = "Export Succeeded"
    Catch gpge As GnupgException
      status.Text = gpge.Message.Replace(CrLf, "")
    End Try
    eintro.Text = outputText
    subject.Text = "My Public Key"
    Me.useeintro.Checked = True
    Me.usecompose.Checked = False
  End Sub

Conclusion

The way you would modify this wrapper is to play with the gpg.exe execution from the command line prompt. It is important to note that when you are prompted for input, sometimes you need to enter the info and then tell gpg.exe that you are done with a Linux Ctrl-D, which is Ctrl-Z plus enter in Windows. Without this info, you may have major problems completing entry of successful tests. Once you have successful tests, with certain options selected (using the GPG manual of options) you know how to modify my wrapper's BuildOptions method to add the options that worked for you on the command line.

Notes

If you get in a situation where gpg.exe does a nice thing, but with the same options, your wrapper doesn't work, try commenting out the line:

pinfo.CreateNoWindow = True

This enables you to see what is happening in the command line window. Install to a directory like c:\gnupg. Use Start, Run, CMD, cd\, cd gnupg, cls, gpg and press return to run the gpg.exe executable once to create some startup files. Now, copy gpg.exe and these key ring files to your application to prepare for your initial generation.

This article relies upon the following article:
Using the Gnu Privacy Guard (GnuPG/PGP) within ASP.NET [v1.0]
Thanks to the Author: Emmanuel KARTMANN
(The above article supported two areas in C# of the five areas I support in VB.NET.)

 



User Comments

Title: how to read GnuPG verify comments   
Name: Eliran
Date: 2010-06-06 5:04:59 AM
Comment:
How can I read the GnuPG verify comment.
It send no output to the stream?!
Title: Thank You   
Name: Jeremy
Date: 2009-10-22 3:50:41 PM
Comment:
Thank You for this article and the sample code. It helped me accomplish what I needed.
Title: juan   
Name: juan
Date: 2009-04-01 4:43:30 AM
Comment:
not useful! you should create much simple sample! pff!
Title: code plagiarism   
Name: nik
Date: 2008-08-05 10:57:48 AM
Comment:
I dont see any mention of the C# wrapper on which this code is based which was written in 2002

http://www.codeproject.com/KB/security/gnupgdotnet.aspx
Title: Encrypt problem   
Name: Howard
Date: 2008-07-01 4:01:40 AM
Comment:
I am having same problem as JT & Julian.
gpg: No Valid OpenPGP data found
gpg: processing message falied: eof
Please help.....
Thanks!
Title: How To delete a KEy?   
Name: Anand
Date: 2008-06-03 6:28:43 AM
Comment:
Hi,

Is there is any way to delete the key
Title: how we can execute dos command in vb.net without showing command window   
Name: Bhramarbar Malakar
Date: 2008-04-02 4:08:53 AM
Comment:
pls send me solution for this immediately

@malakar_bh@hotmail.com
Title: Updated Link   
Name: Brendan
Date: 2008-01-02 10:54:43 AM
Comment:
I've updated the link to point to the website's download page. I hope that helps.
Title: Invalid Link from article   
Name: VI
Date: 2008-01-02 9:01:41 AM
Comment:
The link to "GnuPg Source Link to FTP Link for 4.2.1 Version" is no longer valid.
Title: Any Answer to Julian Redwood's Question?   
Name: JT
Date: 2007-03-20 1:43:53 PM
Comment:
Hello
I have almost the same question as Julian. I keep getting
gpg: No Valid OpenPGP data found
gpg: processing message falied: eof
using the wrapper even though the command line decrypts the file without problem.
Thanks!
JT
Title: SSCDAL   
Name: anonymous email
Date: 2007-02-14 4:35:49 AM
Comment:
remove sscDal from the code and add all needed path with server.mappath instead.
Title: Question   
Name: Scott Du
Date: 2006-11-22 1:18:42 PM
Comment:
what is SscDal.entityclasses in imports section and Regex in 'Dim phrase As New Regex'? thanks!
Title: Help   
Name: Julian Redwood
Date: 2006-08-29 2:57:26 AM
Comment:
I keep on getting the following error message even though I can successfully encrypt a file via the command line, any ideas what might be the problem ?

gpg: No Valid OpenPGP data found
gpg: processing message falied: eof
Title: hi   
Name: PROF' RAMLEE
Date: 2006-01-31 3:26:54 AM
Comment:
It's good

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-29 7:55:19 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search