Click here to Skip to main content
Click here to Skip to main content

SharpPrivacy - OpenPGP for C#

, 7 Jun 2003
Rate this:
Please Sign up or sign in to vote.
SharpPrivacy is an OpenPGP implementation in C#. It can be used to encrypt and sign data, created OpenPGP compatible keys, and a lot more. This article explains how to use the library in your own .NET application or webpage to encrypt, sign, decrypt or verify OpenPGP messages.

The main SharpPrivacy menu in the tray bar.

Introduction

NOTE: This article assumes you have some basic understanding of public key cryptography.

SharpPrivacy has been developed to be an Open Source implementation of the OpenPGP standard. OpenPGP is a standard for encrypting, decrypting, signing and verifying data cryptographically. The OpenPGP protocoll is commonly used and is said to be very secure.

Unfortunatly there is no free OpenPGP implementation available that provides the user with a nice GUI to make the usage easy. There is GnuPG (which is short for Gnu Privacy Guard), that is an really full-featured and stable implementation, but it is command line based and not very easy to use.

To get rid of the lack of such a program, we decided to write our own implementation. At first we just wanted to simply write a GUI around GnuPG, but after some attempts we decided it would be both easier and better to implement the hole of OpenPGP.

The complete functionality of SharpPrivacy is rather complicated to use. In this article, we want to give you a short overview how to encrypt, sign, decrypt and verify. To give you a kick start, so to say.

Unfortunately the documentation of SharpPrivacy has not yet been finished, and I doubt it will ever be. The most important interfaces have been documented with C# XML comments. You can use the according compiler option to make the compiler create the documentation XML file when compiling the source code, and use NDoc to make a compiled HTML file.

SharpPrivacy has been compiled using the free SharpDevelop IDE by Mike Krueger. If you want to load the source code you find above, you will need SharpDevelop to load the project file in the zip archive. The program will work with Visual Studio .NET too, but you will have to create a solution for it by yourself (sorry, but I don't own a license for VS.NET).

WARNING: This program is the beta version of a security product. We STRONGLY recommend NOT to use this program for anything where security is an issue. This program has not been tested enough to be considered secure. Use this program at your own risk. We do not take responsibility for any harm done by function/malfunction of the product, in whatever form.

Background

A little background about how the project was started: Studying computer and media security in Hagenberg/Austria, we have to do a project each semester. This semester a mate and I decided we wanted to do a programming project that has to do with cryptography. Being aware of the lack of an OpenPGP implementation with a GUI, we chose that to be our project.

Neither of us had ever written a program in a .NET language, but we were both keen on learning it. So we decided for C#. We never regretted the decision and knowing java, the learning curve of C# was not at all so steep.

The project for our university will end in June 2003, but we plan to continue our efforts if we see that people like our program. Please let us know if you like what we are doing!

Using the code

So far we have not yet divided GUI code and OpenPGP functionality. Both of them are in a single assembly called SharpPrivacy.exe. We plan to make an own assembly for the OpenPGP code to make integration into other programs easier. This will come in one of the next versions of SharpPrivacy.

But let us have a look at the code itself. The code can be found in the following namespaces:

  • SharpPrivacy - All GUI code can be found here. But in this namespace, you can also find code for the keyrings (a local key repository of public and secret keys).
  • SharpPrivacy.OpenPGP - The OpenPGP functionality with all types of packets (OpenPGP has packets for different tasks, we will later see how to use the packets).
  • SharpPrivacy.Cipher - Here you can find classes for all cryptographic issues. You will find classes for asymmetrical as well as symmetrical encryption and decryption. You also find classes that are necessary for mathematical operations performed during encryption/decyption in the sub namespace Math.

Encrypting/Signing

In this section you will learn how to use the SharpPrivacy assembly to encrypt data. We assume the data is text. For easy encryption you can use the class EncryptionHelper.cs. In there are also GUI elements (like the program asking for a pass phrase) that have to be removed from the class if you want to use it for web pages. We will try to make the code more generally usable in one of the next versions of SharpPrivacy. To make more for customizations from your side, we will explain the code found in that class in detail.

Arguments of the function EncryptText:

  • strMessage - A string representing the message that is to be encrypted or signed.
  • pkrPublicKeyRing - The public keyring object, containing all local public keys (The PublicKeyRing class features the functions Load(string strPath) and Save(string strPath). A public keyring file consists of public keys in their transportable form (meaning: as you get them from other OpenPGP compatible applications).)
  • skrSecretKeyRing - The local secret key ring (only nessessary if you want to do signing. For encryption it can be new SecretKeyRing()
  • bSign - A boolean value indicating if the message should be signed additionally to encryption.
public static string EncryptText(string strMessage, 
                                 PublicKeyRing pkrPublicKeyRing,
                                 SecretKeyRing skrSecretKeyRing, 
                                 bool bSign) {
    
    // to what public keys do you want to encrypt?
    PublicKeySelector pksSelectKeys = new PublicKeySelector(pkrPublicKeyRing);
    pksSelectKeys.ShowDialog();
    TransportableSecretKey tskKey = new TransportableSecretKey();
    string strPassphrase = "";
    
    // if we want to sign, we need a passphrase for the signing key.
    if (bSign) {
        QueryPassphrase qpPassphrase = new QueryPassphrase();
        qpPassphrase.ShowMyDialog(skrSecretKeyRing);
        tskKey = qpPassphrase.SelectedKey;
        strPassphrase = qpPassphrase.Passphrase;
    }
    
    // check if a public key was selected at all!
    if (pksSelectKeys.SelectedKeys.Count == 0)
        return strMessage;
    
    Working wWorking = new Working();
    wWorking.Show();
    
    // Create a literal message
    LiteralMessage lmMessage = new LiteralMessage(DataFormatTypes.Text);
    lmMessage.Text = strMessage;
    lmMessage.TimeCreated = DateTime.Now;
    lmMessage.Filename = "";
    
    SharpPrivacy.OpenPGP.Messages.Message mEncryptionMessage = lmMessage;
    
    // if we want to sign, let's do so.
    if (bSign) {
        SignedMessage smMessage = new SignedMessage();
        smMessage.MessageSigned = lmMessage;
        SignaturePacket spPacket = new SignaturePacket();
        spPacket.Version = SignaturePacketVersionNumbers.v3;
        SecretKeyPacket skpKey = tskKey.FindKey(AsymActions.Sign);
        spPacket.KeyID = skpKey.PublicKey.KeyID;
        spPacket.HashAlgorithm = HashAlgorithms.SHA1;
        spPacket.SignatureAlgorithm = skpKey.PublicKey.Algorithm;
        spPacket.TimeCreated = DateTime.Now;
        spPacket.SignatureType = SignatureTypes.TextSignature;
        spPacket.Sign(lmMessage.Binary, skpKey, strPassphrase);
        smMessage.Signature = spPacket;
        mEncryptionMessage = smMessage;
    }
    
    // OpenPGP messages are usually encrypted, so we will do that to!
    CompressedMessage cmMessage = new CompressedMessage();
    cmMessage.Compress(mEncryptionMessage);
    
    wWorking.Progress(20);
    
    // What are the prefered algorithm of the keys that were chosen to 
    // encrypt to?
    SymAlgorithms saAlgo
                     = GetSymAlgorithmPreferences(pksSelectKeys.SelectedKeys);
    
    SymmetricallyEncryptedDataPacket sedpEncrypted
                                     = new SymmetricallyEncryptedDataPacket();
    SymmetricAlgorithm saEncrypt = CipherHelper.CreateSymAlgorithm(saAlgo);
    saEncrypt.Mode = CipherMode.OpenPGP_CFB;
    saEncrypt.GenerateKey();
    byte[] bKey = saEncrypt.Key;
    
    wWorking.Progress(10);
    // Create the ESK sequence (a set of asymmetrically encrypted session keys)
    ESKSequence esksKeys = new ESKSequence();
    try {
         esksKeys = CreateESKSequence(pksSelectKeys.SelectedKeys, 
                                      AsymActions.Encrypt, saAlgo, bKey);
    } catch (Exception e) {
        wWorking.Hide();
        MessageBox.Show("The following error occured: " + e.Message, 
                        "Error...");
        return strMessage;
    }
    
    wWorking.Progress(50);
    
    // Perform actual encryption.
    ICryptoTransform ictEncryptor = saEncrypt.CreateEncryptor();
    byte[] bMessage = cmMessage.GetEncoded();
    byte[] bOutput = new byte[bMessage.Length];
    ictEncryptor.TransformBlock(bMessage, 0, bMessage.Length, ref bOutput, 0);
    bKey.Initialize();
    
    wWorking.Progress(10);
    
    // get the encrypted message to the correct length
    int iOutLength = (saEncrypt.BlockSize >> 3) + 2 + bMessage.Length;
    sedpEncrypted.Body = new byte[iOutLength];
    Array.Copy(bOutput, 0, sedpEncrypted.Body, 0, iOutLength);
    
    // concatinate esksequence and encrypted message
    byte[] bESK = esksKeys.GetEncoded();
    byte[] bEncrypted = sedpEncrypted.Generate();
    
    byte[] bReturn = new byte[bESK.Length + bEncrypted.Length];
    bESK.CopyTo(bReturn, 0);
    bEncrypted.CopyTo(bReturn, bESK.Length);
    
    wWorking.Progress(10);
    // radix64 encode the message
    string strReturn = Radix64.Encode(bReturn, true);
    
    strReturn = Armor.WrapMessage(strReturn);
    
    wWorking.Hide();
    // DONE!!! (finally)
    return strReturn;
}

So what does the code exactly do? At first it shows a dialog where the user can choose to what public keys the message should be encrypted. This can be more than just one public key. If you choose two or more keys, each of the key owners will be able to decrypt the message.

Next we have to check whether we want to sign the message (additionally to encrypting it). If yes, we will need a pass phrase for the key (according to the OpenPGP standard, the secret key material in a secret key packet may be encrypted using a pass phrase). So we show a dialog that has 2 tasks: The user may choose with which secret key he wants to sign the message (in case there are more than just one secret keys in his local key ring), and he has to enter the pass phrase for the chosen key.

If the use has not selected a public key to encrypt to, we simply return the message and do nothing more.

Now comes a more complicated part: We need to create a literal message. Literal OpenPGP messages contain plaintext data. Literal messages also contain attributes like whether the plaintext is text data or binary, when it was created, and in what file the messages was stored before it was encrypted. We set the time created to now, tell the object that we want to encrypt text by assigning the message to the text property (there is also a binary property in the class) and leave the path empty as the text comes from somewhere we don't know (the clipboard for example).

It gets even more complicated: if we want to sign, we will need to create a signed message. A signed message consists mainly of a SignaturePacket. We create the signature packet and assign necessary values like the key Id of the signing key, the used hash algorithm, the packet version of the signature packet, the exact secret key packet used to sign the message (a transportable secret key can contain more than just one secret key. There also might be subkeys that can be used for different purposes. This makes it possible to use one key for signing and another key for encryption for example), the signature algorithm used to sign the message (this must of course fit the algorithm of the key), the time when the signature was created (now) and the type of the signature (in our case we have a signature over some text). We use the function Sign to sign the message. The function Sign needs 3 arguments: The first argument gives the data that is to be signed. In our case we want the message text in a binary form. The second argument gives the secret key packet used to sign the message, and finally the third one is the pass phrase that belongs to the secret key packet. That's it. We now simply assign the SignaturePacket to the signed message and are ready for the encryption.

Now we will compress either the signed message, or the literal message (depending on whether we chose to sign the message or not). This is pretty much standard for OpenPGP messages.

We than have to choose a symmetrical algorithm that will be used to encrypt the message. Most public keys have preferences stored in their self-signatures, and we really ought to respect the wishes of the key owners.

Next we create a symmetrically encrypted data packet. This is (almost) the final representation of the encrypted message, but let's have a detailed look at it: We create a new symmetrically encrypted data packet and also a symmetric algorithm (namely the one that we selected when reading the key preferences in the previous step). We set the encryption mode to OpenPGP_CFB which is a mutation of the standard Ciphertext Feedback Mode (CFB). We generate a new symmetrical key and also store it in the byte array bKey.

The creation of an ESK Sequence is a little bit too complicated to explain it in detail. Just that much: The function CreateESKSequence will create as a set of asymmetrically encrypted session keys. OpenPGP encrypts the symmetrical key using the asymmetrical public keys chosen in the first stop. This has quite a number of advantages: Asymmetrical encryption is rather slow, while symmetrical encryption is amazingly fast. So for longer messages this saves us a lot of time. Also we can save storage, because we only need to encrypt the actual message once using the generated symmetrical key. By encrypting only the session key, a recipient can get this session key by decrypting "his" asymmetrically encrypted session key packet, and use that session key to decrypt the entier message. I hope that was clear enough. If there are questions left, please feel free to ask in the below message board. I will be more than glad to answer your questions.

Next comes the actual symmetrical encryption. We get the encryptor and use the function TransformBlock to encrypt the message. The function GetEncoded of the compressed message that is to be encrypted will return a byte array containing the OpenPGP formatted message. This is exactly the data we want to encrypt. After the encryption has been done, we initialize the symmetrical key as not to disclose confidential data (in other words: to get it out of the computers memory).

Next we need to snip the return value of the encryption operation to the correct length. This is not done very beautifully, but we know that the encrypted message will always be the length of the plaintext, plus an additional block + 2 bytes (for the OpenPGP CFB mode).

Now we are really almost done. We get the OpenPGP encoded representation of the ESK Sequence, and the OpenPGP encoded representation of the encrypted message. We concatenate these two and encode them with Radix64 (this has nothing to do with cryptography, it is just a way to safely transport an encrypted message from one computer system to another (not all computer systems support the charset you have on your local computer). It also makes a string out of a binary message so that it can be decoded to the original binary message on another system.).

Then we simply wrap the message in OpenPGP headers (you know, the -----BEGIN PGP MESSAGE----- and -----END PGP MESSAGE----- stuff) hide the progress window (which I simply ignored in my previous explanations) and return the encrypted message.

Easy, isn't it Wink | ;-) .

Decryption/Verification

Now we want to see if we can also decrypt the stuff we encrypted earlier. Furthermore we want to validate the signature, if present. This is just as long as the encryption, but if you understood encryption and signing, it should be easy to understand decrypting and verification too.

Let's see what arguments the function DecryptAndVerify (also in EncryptionHelper.cs) needs:

  • skrSecretKeyRing - The local secret key ring
  • pkrPublicKeyRing - The public keyring object, cotaining all local public keys (The PublicKeyRing class features the functions Load(string strPath) and Save(string strPath). A public keyring file consists of public keys in their transportable form (meaning: as you get them from other OpenPGP compatible applications).)
  • bData - A byte representation of the encrypted message. This can either be the true content of an OpenPGP message, or it can be the message as string, converted to byte[] with the System.Text.Encoding.UTF8 converter (again: feel free to ask if you have questions about this).
private static void DecryptAndVerify(SecretKeyRing skrSecretKeyRing, 
                                     PublicKeyRing pkrPublicKeyRing, 
                                     byte[] bData) {
    
    string strMessage = System.Text.Encoding.UTF8.GetString(bData);
    ArmorTypes atType = new ArmorTypes();
    string strRest = "";
    string strRadix64 = Armor.RemoveArmor(strMessage, ref atType,
                                          ref strRest);
    if (strRadix64.Length > 0)
        bData = Radix64.Decode(strRadix64);
    
    SharpPrivacy.OpenPGP.Messages.Message mContent = null;
    
    if (atType == ArmorTypes.OpenPGPSignature) {
        string strSignature = "";
        string strSignedMessage = Armor.RemoveClearSignatureArmor(strMessage,
                                                ref atType, ref strSignature);
        
        strSignedMessage = Radix64.DashUnescape(strSignedMessage);
        strSignedMessage = Radix64.TrimMessage(strSignedMessage);
        SignedMessage smMessage = new SignedMessage();
        Packet[] pPackets = Packet.ParsePackets(strSignature);
        if (!(pPackets[0] is SignaturePacket)) {
            MessageBox.Show("Not a valid cleartext signature!");
            return;
        }
        smMessage.Signature = (SignaturePacket)pPackets[0];
        
        LiteralMessage lmMessage = new LiteralMessage(DataFormatTypes.Text);
        lmMessage.Text = strSignedMessage;
        smMessage.MessageSigned = lmMessage;
        
        mContent = smMessage;
    } else {
        
        // let us see what kind of message this is
        EncryptedMessage emMessage = new EncryptedMessage();
        try {
            Packet[] pPackets = Packet.ParsePackets(bData);
            emMessage.ParseMessage(pPackets);
            
            if (emMessage.SymmetricallyEncrypted) {
                // Query passphrase for symmetrically encrypted message
                QueryPassphrase qpPassphrase = new QueryPassphrase();
                qpPassphrase.ShowMyDialog();
                string strPassphrase = qpPassphrase.Passphrase;
                
                mContent = emMessage.Decrypt(strPassphrase);
                
            } else {
                ulong lKeyID = emMessage.GetFittingKeyID(skrSecretKeyRing);
                QueryPassphrase qpPassphrase = new QueryPassphrase();
                qpPassphrase.ShowMyDialog(skrSecretKeyRing.Find(lKeyID));
                string strPassphrase = qpPassphrase.Passphrase;
                
                mContent = emMessage.Decrypt(skrSecretKeyRing, strPassphrase);
            }
            
            while ((!(mContent is LiteralMessage)) && 
                   (!(mContent is SignedMessage))) {
                if (mContent is CompressedMessage) {
                    mContent = ((CompressedMessage)mContent).Uncompress();
                } else {
                    MessageBox.Show("This is not a valid OpenPGP message!");
                    return;
                }
            }
        } catch (Exception ee) {
            MessageBox.Show("There was an error decrypting your message: "
                            + ee.Message);
            return;
        }
    }
    
    LiteralMessage lmContent = new LiteralMessage();
    string strDisplay = "";
    if (mContent is SignedMessage) {
        SignedMessage smContent = (SignedMessage)mContent;
        lmContent = smContent.MessageSigned;
        strDisplay += "*** OpenPGP Signed Message ***\r\n";
        strDisplay += "*** Signature Status: " + 
                          smContent.Verify(pkrPublicKeyRing) + " ***\r\n";
        strDisplay += "*** Signing Key: " + 
                          smContent.Signature.KeyID.ToString("x") + " ***\r\n";
        strDisplay += "*** Signing Date: " + 
                          smContent.Signature.TimeCreated.ToString() +
                          "***\r\n\r\n";
    } else if (mContent is LiteralMessage) {
        lmContent = (LiteralMessage)mContent;
        strDisplay += "*** OpenPGP Encrypted Message ***\r\n\r\n";
    } else {
        MessageBox.Show("An error occured: Could not find an encrypted or " + 
                        "signed message!", "Error...");
        return;
    }
    
    if (lmContent.DataFormat == DataFormatTypes.Text) {
        strDisplay += lmContent.Text;
        strDisplay += "\r\n\r\n*** End OpenPGP Message ***\r\n";
        PlaintextViewer pvViewer = new PlaintextViewer();
        pvViewer.MessageText = strDisplay;
        pvViewer.Show();
    } else {
        if (MessageBox.Show(strDisplay, "Signature Status...", 
                            MessageBoxButtons.OKCancel, 
            MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1) == 
                                                           DialogResult.OK) {
            
            System.Windows.Forms.SaveFileDialog sfdSave = new SaveFileDialog();
            sfdSave.OverwritePrompt = true;
            sfdSave.Filter = "All Files (*.*)|*.*";
            sfdSave.FileName = lmContent.Filename;
            sfdSave.ShowDialog();
            if (sfdSave.FileName.Length > 0) {
                System.IO.FileStream fsOut = new FileStream(sfdSave.FileName, 
                                                            FileMode.CreateNew);
                System.IO.BinaryWriter bwOut = new BinaryWriter(fsOut);
                bwOut.Write(lmContent.Binary);
                bwOut.Close();
                fsOut.Close();
            }
        }
    }
}

Again, how does this stuff work?

First we need to see if we got only the content of an OpenPGP message, or the complete message as UTF8 representation. To do so we convert it back from UTF8 to string and check if we find valid OpenPGP headers (using the static function Armor.RemoveArmor).

You might wonder why this is necessary. The reason is that we can also get Cleartext signed OpenPGP messages in this function and need to handle these a little bit differently. If we have a cleartext signed message (atType == ArmorTypes.OpenPGPSignature), we will make a normal signed message out of it to make it easier to validate. However I will not go deeper into this, as it is not the primary goal of this article to explain every feature of the OpenPGP standard.

Let's better look at what happens when we do not get a cleartext signed message: First we create an empty encrypted message for later use. Then we try to parse the packets in the given message and also try to parse an encrypted message.

We look if the message was encrypted symmetrically or asymmetrically. In either case we have to ask for a pass phrase. If it was encrypted symmetrically the pass phrase is more or less directly used as key to the message, if it is an asymmetrically encrypted message we need the pass phrase to decrypt the secret key material of the secret key.

Having the encrypted message as well as the pass phrase, we can try to decrypt the message. If everything goes well, we want to see what is in the encrypted message. This can be either a literal message or a signed message, but in most cases it will be a compressed message. If it is the latter, we'll uncompress it and see if in there, there is finally a literal or signed message.

If we have a signed message, we will show stuff like the key id of the signing key, whether the signature is valid and when the signature was created. In case of an encrypted message, we decrypt it and prepare it for displaying the decrypted message.

If the encrypted message was a file, we ask the user where to save the file, otherwise we show the dialog with the signature information, and the original message.

I hope the last couple of paragraphs were not too fast. Please let me know!

Points of Interest

The SharpPrivacy code supports a lot more than just the above mentioned. For example you can also generate key pairs, manage your keys, etc., etc.

If you liked the article, please rate it. I was really trying hard to make article good, I hope it helped you. Feel free to use SharpPrivacy in your own NON commercial application. SharpPrivacy has been released to the terms of the GPL, so please respect the license.

If you have any questions, please use the below discussion board, or drop me a mail. I would prefer the latter so that other people with similar questions can read the answers below.

References

  • SharpPrivacy.net - The project homepage
  • RFC 2440 - The OpenPGP standard is specified in RFC 2440
  • Go-Mono Project - Go Mono is a project that wants to port the .NET environment to *nix systems. We borrowed quite a bit of cryptographic code from them.
  • SharpDevelop IDE - SharpPrivacy has been entierly developed using the free SharpDevelop IDE
  • MagicLibrary - MagicLibrary is a cool GUI library that adds new and good looking controls to the control repository
  • ExtendedListView - A TreeListView control that makes the display of the keys in the key manager easy. It has been written by Jon Rista
  • NDoc - A free program that creates compiled HTML help out of XML files.

History

6 June, 2003: First version of this article featuring code from SharpPrivacy version 0.1.0 (beta).

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

About the Author

gek_at

Austria Austria
My name is Daniel and I am from Austria.
When I was in High School, all I wanted to do was programming. After finishing High School, I joined a company for which I wrote a project management utility in Visual Basic 6. It was then that I realised that programming all day long was nothing I wanted to do for the rest of my life.
 
I quit the job and started to study computer and media security at polytechnical university in Hagenberg/Austria.
 
As of now, I'm still studying. I still like to program, as long as I can do something else too. Recently I switched my favorite programming language to c#. Together with a friend from university, we started a project where we implement OpenPGP (RFC2440) in c#.

Comments and Discussions

 
NewsTwo other related encryption articles in CodeProject ... PinmemberTony Selke27-Sep-07 6:59 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 8 Jun 2003
Article Copyright 2003 by gek_at
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid