Insights
of |

Single Sign On with PGP 6.5.8 and Encrypt with Bouncy Castle
May 23, 2012"There are two kinds of cryptography in this world: cryptography that will stop your kid sister from reading your files, and cryptography that will stop major governments from reading your files. This book is about the latter."
--Bruce Schneier, Applied Cryptography: Protocols, Algorithms, and Source Code in C.
Recently I had to work on a project where we had to create a system that does Single Sign On (SSO). For those who are not familiar with the term SSO, it means a user logs in once, and he gains access to all systems without being prompted to log in again at each one of them. These systems are independent software systems and often exist in different platforms. The catch was that it had to be done using PGP. PGP is the standard for email and disk encryption; it is not a standard to be used for single sign on over the web (though some implementation of it could exist). Also, the end third party system with which we were integrating was written in Java and invoked PGP 6.5.8 command line version to decrypt the message and validate the signature. The system on our end was built using .NET. Our approach was first to identify what algorithms were used for compression, encryption, signature, key generation, etc. After that we ventured out to investigate how to implement PGP using .NET, and we settled on the Bouncy Castle C# API. The Bouncy Castle C# API is a great off-the-shelf open source resource that implements the Open PGP standard. It does lack good documentation, and hopefully this article will help anyone who has to work with PGP 6.5.8.
Please note that PGP 6.5.8 is not licensed to be used commercially. After saying all that, let's get started.
Terms used in this article:
I am going explain a few key terms used repeatedly in this article, and most of it you might be already aware of:
- Encryption: The method of disguising plain text in such a way that it hides its original substance.
- Cipher text: Encrypting the plain text results in unreadable gibberish which is called the cipher text.
- Decryption: The method of extracting the original text from the cipher text.
- Digital Signature: A valid digital signature provides authenticity to a message or document and gives reason to believe that the message came from a known sender.
- Bouncy Castle: An API that is available both in C#.NET and Java that implements the Open PGP Standard.
- PGP 6.5.8: PGP 6.5.8 is a freeware software version of PGP that has a command line interface. This version of the software can be found here
- Public Key & Private Key: Public key cryptography was introduced by Diffie and Martin Hellman in 1975. In public key cryptography, you create a public key which you will publish and a private key (secret key) which you will keep to yourself. Someone who wishes to send you a message will encrypt it using your public key, and it is impossible to decrypt the message without a private key. It is also important to note that it is impossible to determine the private key using the public. This phenomenon enables someone whom you have never met to send a message to you as long as they have your published public key. The usage of two sets of keys makes it asymmetric.
How PGP works:
We use a cryptographic algorithm in the process of encryption and decryption. This mathematical function works in combination with a key to provide a cipher text. The strength of your encrypted data depends on this mathematical function and the key. The algorithm, key, protocol, and standards that make it work together are called a cryptosystem, and PGP is a cryptosystem. PGP can also be thought of as a hybrid cryptosystem, as it uses both public key cryptography and conventional cryptography. The following outlines how messages are encrypted, signed, and decrypted using PGP:
How encryption works:
The sender and receiver generate two sets of keys: a public key and a private key. We generate the keys using PGP 6.5.8 command line. The sender and the receiver exchange the public keys each has generated.
- The message text is first compressed. Compression, first of all, enables easy transmission of data but also makes cryptanalysis difficult, since it is difficult to find patterns from compressed data to crack the cipher.
- A one-time session key is generated based on the user’s keystrokes and mouse movements. The session key works with a conventional algorithm to produces the cipher text from the compressed message. The use of a conventional algorithm to encrypt a message is 1,000 times faster than a public key algorithm.
- This session key is then encrypted using the receiver’s public key.
- This encrypted session key and cipher text is then transmitted to the recipient.
How decryption works:
- The encrypted message arrives at the receiver's end. The recipient decrypts the session key using his private algorithm.
- Once the session key is extracted, the cipher text is decrypted conventionally.
- The resulting data is decompressed, and you have the original plain text.
How signing works:
- After encrypting the data, you also need to add a digital signature.
- A one-way hash function is applied to your plaintext to produce a fixed length output. A one-way hash function is a function that hashes a variable length input to produce a fixed length output of bits. A hash function ensures that with even a small change to the information, an entirely new hash gets generated.
- The output generated by the hash function is called a message digest. PGP uses the sender’s private key and the message digest to generate the “signature”. This signature is sent to the recipient along with the encrypted text.
How signatures are verified:
- The message digest is retrieved using the sender's public key.
- The hash algorithm is applied to the decrypted message text, and a message digest is created.
- Both the hashes are compared. If they are the same, the signature is verified
PGP 6.5.8:
The command line version of PGP 6.5.8 for Windows was introduced around 1999. You can find and download it here.
You can find the documentation about PGP 6.5.X here.
The document is mentioned here for convenience:
Here is a document that we put together that helps you generate keys using the PGP 6.5.8 command line tool.
If you use the above document to generate keys, in the end, you will have a public key created in the public key ring file, which you can extract and communicate to the other party securely. You will have a private key created in the secret key ring file, which you can only access using a passphrase. The PGP Commandline Guide mentioned above explains the public and secret key ring concepts pretty well.
I am going to talk about each of the algorithms used in the various steps of PGP encryption and signing and then move on to talk about the actual implementation of PGP “One-Pass singing and Encryption” using Bouncy Castle.
Algorithms used in Compression, Encryption, Hashing, and Signing for PGP 6.5.8:
What is ZIP?
The only compression algorithm supported by PGP 6.5.8 is zip, and that is what we used for compression.
What is CAST5?
CAST5 is the default cipher for PGP. It is the conventional algorithm used for encryption. Other options to choose from are IDEA and 3DES.
What is DH/ElGamal?
Diffie-Hellman (DH) was the first publicly published key system and has received numerous analyses. PGP implements ElGamal, which is a public key variant of the Diffie-Hellmann Problem: if you break one you can break the other. ElGamal is also unencumbered by copyrights and patents.
What is SHA-1?
The choices offered for Hash Algorithm in PGP 6.5.8 are MD5, SHA-1, and RIPE-MD. We chose to use SHA-1. The reason is DSS uses SHA-1, and the output of SHA-1 is 160 bits. Also, on a side note, MD5 produces only 128-bit output, which is not secure for long term usage.
What is DSS?
DSS stands for Digital Signature Standard; it is the digital signature algorithm (DSA) developed by the U.S. National Security Agency (NSA) to generate a digital signature for the authentication of electronic documents and to produce a fixed length signature irrespective of the public and private key sizes.
PGP 6.5.8 Single Pass Sign and Encrypt with Bouncy Castle:
What does Single Pass Sign and Encrypt mean?
There are two ways to encrypt and sign. If you choose to “Sign and Encrypt,” you sign first and then encrypt. On the other end, decrypt and validate works in one pass.
If you instead encrypt the message first and the sign, you do two base64 encoding on the message: one from encryption and the second from signing. On the other end, the receiver has to run “Decrypt/Verify” twice. The first pass only verifies leaving the message to be decrypted.
Download the Bouncy Castle API for C#. This API comes with a list of C# examples for PGP implementation, though they lack good documentation.
Before we delve deeper, I cannot insist strongly enough on this, read through RFC 4880.
This is the OpenPGP Standard and describes the interoperability between various OpenPGP applications.
If you read RFC 4880 before implementing the code, it will make your life 100 times easier during implementation. I did the other way around, so I know.
There are a lot of subtleties in implementation that you would miss unless you read the standard. For example,
for PGP 6.5.8 you would need to implement Version 3 of the Signature Format and not Version 4.
Implementation:
Once you are armed with all the knowledge about the workings of PGP, it is a short journey to implement a fully functional system. Unfortunately, lack of proper documentation in the API makes this a difficult process. John Opnicar and this article both have excellent examples of implementing PGP with Bouncy Castle, and credits to them, I have used their examples as a base apart from the examples provided with the api.
For the implementation, we will separate key management from the actual encrypt and sign functionality. A concept that helped me in understanding Bouncy Castle classes was that they basically create a pipeline of streams. The code below is heavily commented to give an idea of what is going on:
Key Management Class
The KeyManagement class exposes functions that retrieve public and private keys that we will use for encryption and signing; the public key of course belongs to the receiver, and the private key is the sender's signing key.
using System; using System.IO; using Org.BouncyCastle.Bcpg; using Org.BouncyCastle.Bcpg.OpenPgp; using Org.BouncyCastle.Security; using System.Security.Cryptography; namespace PGPSamples.PGP658 { public class KeyManagement { public PgpPublicKey PublicKey { get; private set; } public PgpPrivateKey PrivateKey { get; private set; } public PgpSecretKey SecretKey { get; private set; } public string UserID { get; private set; } /// <summary> /// constructor /// </summary> /// <param name="publicKeyPath">Public keypath, this is the file that contains the /// public key of the recipient</param> /// <param name="privateKeyPath">Secret key ring file of the sender</param> /// <param name="passPhrase">Pass phrase to access the secret key</param> /// <param name="userid">UserId of the sender which we will use to identify the correct /// secret key from the secret key ring</param> public KeyManagement(string publicKeyPath, string privateKeyPath, string passPhrase, string userid) { if (!File.Exists(publicKeyPath)) throw new CryptographicException("Public key could not be found"); if (!File.Exists(privateKeyPath)) throw new CryptographicException("Private key could not be found"); if (String.IsNullOrEmpty(passPhrase)) throw new CryptographicException("Private key could not be found"); if (String.IsNullOrEmpty(userid)) throw new CryptographicException("User ID not found"); UserID = userid; PublicKey = ReadPublicKey(publicKeyPath); SecretKey = ReadSecretKey(privateKeyPath); PrivateKey = ReadPrivateKey(passPhrase); } /// <summary> /// Reads the public encryption key from the key file /// </summary> /// <param name="publicKeyPath">public keypath</param> /// <returns></returns> private PgpPublicKey ReadPublicKey(string publicKeyPath) { Stream input = File.OpenRead(publicKeyPath); PgpPublicKeyRingBundle pgpPub = new PgpPublicKeyRingBundle( PgpUtilities.GetDecoderStream(input)); //we loop through the file and get the public key for encryption // the public master key file contains two sets of keys //one is the encryption public key (Elgmal) and the other is the signature // verification public key (DSA) foreach (PgpPublicKeyRing keyRing in pgpPub.GetKeyRings()) { foreach (PgpPublicKey key in keyRing.GetPublicKeys()) { if (key.IsEncryptionKey) { return key; } } } throw new CryptographicException("Public key could not be found"); } /// <summary> /// get the pgp secret signature key from which we will extract the private key /// </summary> /// <param name="privateKeyPath">get</param> /// <returns></returns> private PgpSecretKey ReadSecretKey(string privateKeyPath) { using (Stream keyIn = File.OpenRead(privateKeyPath)) using (Stream inputStream = PgpUtilities.GetDecoderStream(keyIn)) { //Get the secret key ring PgpSecretKeyRingBundle secretKeyRingBundle = new PgpSecretKeyRingBundle(inputStream); //Call the GetSecretKey function to get the secret key of the sender PgpSecretKey foundKey = GetSecretKey(secretKeyRingBundle); if (foundKey != null) return foundKey; } throw new CryptographicException("Secret key could not be found"); } /// <summary> /// gets the secret signature key that matches a specific user from the secret /// key ring bundle /// </summary> /// <param name="secretKeyRingBundle">Secret key ring bundle</param> /// <returns>pgp secret signature key</returns> private PgpSecretKey GetSecretKey(PgpSecretKeyRingBundle secretKeyRingBundle) { //get the key rings foreach (PgpSecretKeyRing kRing in secretKeyRingBundle.GetKeyRings()) { //get all the secret keys foreach (PgpSecretKey seckey in kRing.GetSecretKeys()) { //for each secret key check if it is a signing key if (seckey.IsSigningKey) { //check if the userid matches so that we get the correct secret key foreach (string userId in seckey.PublicKey.GetUserIds()) { if ( String.Compare(userId, UserID, true)==0) { //return the secret key return seckey; } } } } } return null; } /// <summary> /// Return the actual signature key by retrieving it from secret key using the /// passphrase /// </summary> /// <param name="passPhrase">secret passphrase</param> /// <returns></returns> private PgpPrivateKey ReadPrivateKey(string passPhrase) { PgpPrivateKey privateKey = SecretKey.ExtractPrivateKey(passPhrase.ToCharArray()); if (privateKey != null) return privateKey; throw new CryptographicException("No private key found in secret key."); } } }
Encrypt And Sign:
The PGPEncrpytion class uses the keys that it can retrieve from the KeyManagement class and performs the actual encryption. Again, the code is well commented.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using Org.BouncyCastle.Bcpg; using Org.BouncyCastle.Bcpg.OpenPgp; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities.Encoders; using System.Collections; namespace PGPSamples.PGP658 { /// <summary> /// hanldes pgp encryption /// </summary> public class PGPEncryption { /// <summary> /// Encrypts and signs a message token /// </summary> /// <param name="filename">this can be a empty string, this used by pgp to apply /// a more formal name for the file being encrypted, does not /// apply to our usage</param> /// <param name="encryptionKeys">The class that wraps encryption keys</param> /// <param name="outputStream">stream that holds the encrypted data</param> public void EncryptAndSignToken(string input, string embeddedFileName, KeyManagement encryptionKeys, Stream outputStream) { //64-bit encoding using (outputStream = new ArmoredOutputStream(outputStream)) { // Init encrypted data generator, data gets encrypted as it is being written // into the stream and output into the output stream PgpEncryptedDataGenerator encryptedDataGenerator = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, false, new SecureRandom()); encryptedDataGenerator.AddMethod(encryptionKeys.PublicKey); //BUFFER_SIZE = 1 << 16 should always be power of 2 using (Stream encryptedOut = encryptedDataGenerator.Open(outputStream, new byte[Constants.BUFFER_SIZE])) { // Init compression, data gets compressed as it is being written into // the stream and output into the encryptedout stream; // specify zip as the compression algorithm PgpCompressedDataGenerator compressedDataGenerator = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip); //open a stream for compression using (Stream compressedOut = compressedDataGenerator.Open(encryptedOut)) { // initialize the signature generator for version 3, since pgp 6.5.8 // is based on version 3 of the RFFC 4880 spec PgpV3SignatureGenerator signatureGenerator = new PgpV3SignatureGenerator( encryptionKeys.SecretKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1); //specify the type of signature signatureGenerator.InitSign(PgpSignature.BinaryDocument, encryptionKeys.PrivateKey); //write the signature header out to compressed output signatureGenerator.GenerateOnePassVersion(false).Encode( compressedOut); PgpLiteralDataGenerator literalDataGenerator = new PgpLiteralDataGenerator(); // initialize the literalpacket generator which is going to contain // the message in binary form using (Stream literalOut = literalDataGenerator.Open(compressedOut, PgpLiteralData.Binary, embeddedFileName, DateTime.Now, new byte[Constants.BUFFER_SIZE])) { //convert the input into a byte array byte[] buf = Encoding.ASCII.GetBytes(input); // data gets converted to binary which in turn gets compressed // and encrypted following the nested stream path literalOut.Write(buf, 0, input.Length); // same data gets converted to binary, added to the signature // body which in turn gets compressed and encrypted along with // the binary message literal data, following the nested // stream path signatureGenerator.Update(buf, 0, input.Length); } literalDataGenerator.Close(); //write out the signature body signatureGenerator.Generate().Encode(compressedOut); } } } } } }
How all of the above fits together for SSO:
We encrypt a token string containing member information with which they would be able to authenticate themselves on the third party website. We send the tokenized string in the web request when the user clicks on the SSO link.
Sources
- http://tools.ietf.org/html/rfc4880
- http://www.bouncycastle.org/csharp/
- http://blogs.microsoft.co.il/blogs/kim/archive/2009/01/23/pgp-zip-encrypted-files-with-c.aspx
- http://www.bouncycastle.org/docs/pgdocs1.6/index.html
- http://jopinblog.wordpress.com/2008/06/23/pgp-single-pass-sign-and-encrypt-with-bouncy-castle/
- http://www.bouncycastle.org/docs/pgdocs1.6/index.html
- http://www.pgpi.org/doc/pgpintro/
- http://www.csc.gatech.edu/~copeland/8843-04/pgp/pgp_Q_and_A.html/
- http://www.mccune.cc/PGPpage2.htm#Subkeys
- http://en.wikipedia.org/wiki/Binary-to-text_encoding
of |
