PGP Single Pass Sign and Encrypt with Bouncy Castle

Bouncy Castle is a great open source resource. However, the off-the-shelf PGP functionality is severely lacking in real-world-usabaility. Most of what you need is easy enough to code up yourself (and I would love to contribute what I’ve done if I could). One thing that you really need that doesn’t come built-in and is actually quite hard to do right is PGP Single Pass Sign and Encrypt. Here’s a class in the style of csharp\crypto\test\src\openpgp\examples\KeyBasedFileProcessor.cs that does exactly that.

using System;
using System.IO;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Security;

namespace PgpCrypto
{
	public class PgpProcessor
	{
		public void SignAndEncryptFile(string actualFileName, string embeddedFileName,
			Stream keyIn, long keyId, Stream outputStream,
			char[] password, bool armor, bool withIntegrityCheck, PgpPublicKey encKey)
		{
			const int BUFFER_SIZE = 1 << 16; // should always be power of 2

			if (armor)
				outputStream = new ArmoredOutputStream(outputStream);

			// Init encrypted data generator
			PgpEncryptedDataGenerator encryptedDataGenerator =
				new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());
			encryptedDataGenerator.AddMethod(encKey);
			Stream encryptedOut = encryptedDataGenerator.Open(outputStream, new byte&#91;BUFFER_SIZE&#93;);

			// Init compression
			PgpCompressedDataGenerator compressedDataGenerator = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
			Stream compressedOut = compressedDataGenerator.Open(encryptedOut);

			// Init signature
			PgpSecretKeyRingBundle pgpSecBundle = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
			PgpSecretKey pgpSecKey = pgpSecBundle.GetSecretKey(keyId);
			if (pgpSecKey == null)
				throw new ArgumentException(keyId.ToString("X") + " could not be found in specified key ring bundle.", "keyId");
			PgpPrivateKey pgpPrivKey = pgpSecKey.ExtractPrivateKey(password);
			PgpSignatureGenerator signatureGenerator = new PgpSignatureGenerator(pgpSecKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
			signatureGenerator.InitSign(PgpSignature.BinaryDocument, pgpPrivKey);
			foreach (string userId in pgpSecKey.PublicKey.GetUserIds())
			{
				PgpSignatureSubpacketGenerator spGen = new PgpSignatureSubpacketGenerator();
				spGen.SetSignerUserId(false, userId);
				signatureGenerator.SetHashedSubpackets(spGen.Generate());
				// Just the first one!
				break;
			}
			signatureGenerator.GenerateOnePassVersion(false).Encode(compressedOut);

			// Create the Literal Data generator output stream
			PgpLiteralDataGenerator literalDataGenerator = new PgpLiteralDataGenerator();
			FileInfo embeddedFile = new FileInfo(embeddedFileName);
			FileInfo actualFile = new FileInfo(actualFileName);
			// TODO: Use lastwritetime from source file
			Stream literalOut = literalDataGenerator.Open(compressedOut, PgpLiteralData.Binary,
				embeddedFile.Name, actualFile.LastWriteTime, new byte&#91;BUFFER_SIZE&#93;);

			// Open the input file
			FileStream inputStream = actualFile.OpenRead();

			byte&#91;&#93; buf = new byte&#91;BUFFER_SIZE&#93;;
			int len;
			while ((len = inputStream.Read(buf, 0, buf.Length)) > 0)
			{
				literalOut.Write(buf, 0, len);
				signatureGenerator.Update(buf, 0, len);
			}

			literalOut.Close();
			literalDataGenerator.Close();
			signatureGenerator.Generate().Encode(compressedOut);
			compressedOut.Close();
			compressedDataGenerator.Close();
			encryptedOut.Close();
			encryptedDataGenerator.Close();
			inputStream.Close();

			if (armor)
				outputStream.Close();
		}
	}
}

24 Responses to “PGP Single Pass Sign and Encrypt with Bouncy Castle”

  1. Compare Files Method in C# Unit Testing « John Opincar’s Blue Corner Says:

    […] Files Method in C# Unit Testing May 6, 2008 — jopincar I’ve been working on a class that wraps the low-level PGP crypto capabilities of Bouncy Castle and presents a higher-level, […]

  2. Bradguru Says:

    This example doesn’t work.

    Signer SHA1WITHELGAMAL not recognised.

  3. Bradguru Says:

    Nevermind. I got the keys mixed up. It should have been a DSA key sorry.

  4. Bookmarks about Pgp Says:

    […] – bookmarked by 6 members originally found by ndb3dgj on 2008-11-06 PGP Single Pass Sign and Encrypt with Bouncy Castle https://jopinblog.wordpress.com/2008/06/23/pgp-single-pass-sign-and-encrypt-with-bouncy-castle/ – […]

  5. PGP Zip Encrypted Files With C# - .NET Geek Says:

    […] We found a few samples online, but nothing I felt comfortable to use in our codebase. Credits to John Opincar who published a post on single pass encryption and signing. We used the blog post of his, the […]

  6. Girish Says:

    Thanks for sharing your code, Do you have a single pass decryption for a signed and encrypted file.
    Thanks,

  7. Aaron Says:

    Thank you. I ported this for a Java project I was working on. It solved my problem.

  8. PGP Single Pass Sign and Encrypt .NET Stream « Shamsul Amry's Brain Dump Says:

    […] to figure out how to do things. I was lazy at that time, and managed to find this blog entry: PGP Single Pass Sign and Encrypt with Bouncy Castle that provided me with the code to encrypt file contents using […]

  9. BARBOSS Says:

    question: How we can get KeyId?

  10. DodleUp Says:

    Why do you use compression after Decryption?

    • DodleUp Says:

      * sorry i mean “after encryption”

    • bugstomper Says:

      Yes, I replying here to highlight this. It never makes sense to compress after encryption. If you compress before encryption you get the benefit of having fewer bytes to encrypt, and compression is faster than encryption. If you compress after encryption it is a complete waste of time because the encrypted data is statistically identical to random and a compression algorithm does not result in any compression when applied to random data, in fact it produces larger size output.

      • bugstomper Says:

        Looking at the code again, I think DodleUp and I made the same mistake. When you nest output streams the inner-most one, which you make first, gets the data last. This is making a signature_stream(compression_stream(encrypt_stream(output_stream))) which means when you send your input to it the data is first processed by the signature, then compressed, then encrypted, then sent to the output stream.

        The streams are created in reverse order of how they are processed, which is a standard confusing aspect of nested output streams, but this code is correct.

      • The Bear In Boulder Says:

        Cryptanalysis of compressed data is also slightly harder to cryptanalyze although you can get a false sense of security. The problem is that the same N bytes of a file still lead to the same N’ bytes of compressed data (e.g., “….” or the same for a .docx file). This has broken a lot of cryptosystems. Legacy zip file encryption is a good contemporary example.

        This is brought to you as a PSA of yet another reason why you shouldn’t roll your own crypto and should be careful when using trusted libraries.

      • bugstomper Says:

        Bear, your reply to my reply arrived in my mailbox at the same time as an announcement of a talk on a recently discovered vulnerability in https that is relevant to this discussion. The new vulnerability is called TIME, and is a variation of one discovered last year called CRIME. They both make use of situations in which a web server compresses information that is then encrypted. Here is how compression can add a vulnerability to an encryption system:

        Let’s say you have set up a system that will send encrypted secret information to someone who has the key to decrypt it, and it allows the sending of the secret message to be initiated by someone who doesn’t know the secret and doesn’t have the key. For example, Eva can go to Bob’s site and tell it “send a copy of your secret to Alice encrypted with a key that she knows and tell her Eva wants her to have this secret from Bob”. Bob’s site prefixes his secret message with the message from Eva, signs it, compresses it, encrypts it, and sends it to Alice. Eva sniffs the network for the encrypted message as it goes out.

        Let’s say that the whole setup is automated and Eva can make it send thousands of messages to Alice before Alice knows anything (or can arrange for Alice to not receive the messages after they are intercepted). Eva puts different random strings in the part of the message that she writes. If she gets a partial match with the secret string, the result will compress a little bit better than when there is less redundancy, as that is how compression works. This allows her to figure out the characters in the encrypted secret byte by byte until she has the whole thing, without ever having to find the decryption key.

        Thus the compression before encryption, which intuitively you would think makes cryptanalysis “slightly harder”, actually creates another potential channel for information leakage. There are of course ways around it – For example you could be careful that if an adversary can control some of the plaintext of a message that contains secrets you want to keep out of the hands of the adversary that you use some sort of randomization to isolate the two such as double encryption or adding random amounts of padding. But this certainly reinforces your point about not trying to roll your own crypto.

  11. Tom Says:

    Thank You for your contribution, it’s appreciated!

  12. twoby4 Says:

    Great job!

    Below is a working java version (you will find the “readSecretKey” method in the BC PGPExampleUtil class in source package org.bouncycastle.openpgp.examples):

    public static void signAndEncryptFile(String actualFileName, String embeddedFileName,
    InputStream keyIn, OutputStream outputStream,
    char[] password, boolean armor, boolean withIntegrityCheck, PGPPublicKey encKey) throws Exception {

    final int BUFFER_SIZE = 1 < 0) {
    literalOut.write(buf, 0, len);
    signatureGenerator.update(buf, 0, len);
    }
    literalOut.close();
    literalDataGenerator.close();
    signatureGenerator.generate().encode(compressedOut);
    compressedOut.close();
    compressedDataGenerator.close();
    encryptedOut.close();
    encryptedDataGenerator.close();
    inputStream.close();
    if(armor) {
    outputStream.close();
    }
    }

    • Petter Says:

      twoby4: do you have the code complete somewhere? Parts seems to be missing, I would appreciate it very much! :)

  13. Sumit Says:

    Hi,
    What is this “embeddedFileName” in the signAndEncryptFile method.Is this the file name after encryption or something else.Please clarify.

    Thanks
    Sumit

  14. selvam Says:

    unable to private key any help please?

  15. selvam Says:

    sorry unable to add private key every thing work perfect but after encryption done, while derypting it throws error message, you public key does not contain private key


Leave a comment