PGP Cryptography With The Legion of the Bouncy Castle – Part 5

Posted on

We’re back! Back to PGP Cryptography tutorials!! Because when I want to learn something new I learn faster by writing a tutorial about it, sharing code and receiving feedback.

So in Part 4 I apologized that I did not have a lot of time and just showed how to integrate Bouncy Castle with Android by using SpongyCastle. Now I will go through how to generate and verify detached signatures. This has become important since Part 2 did teach how to sign and verify files, but the signature was embedded inside the file. Though this works, it did not work when trying to verify the file using a regular program like GPG / Kleopatra. Also not all PGP clients support ZLIB compression which could break compatibility. So I decided that the need to generate detached signatures was important.

Continuing with the previous examples we have the PGPTools file which I wrote to make cryptography easier with BC (Full source can be found here). Generating a detached signature file needs the following:

  • The file you want to sign
  • The name of signature file that will be generated
  • The PGP Key ring that contains your secret and public keys

Another interesting thing to know to go by is the naming convention of these files. Most programs look for it and makes it easier for the user to utilize and for programs to find. Supposed there is a file called “TheFile.txt”, below is how the signature file would be named:

  • ASCII Armored Signature: TheFile.txt.asc
  • Binary Signature: TheFile.txt.sig

This is not mandatory, but a nice convention to follow.

Now lets get to the code examples.

This method will sign a file and generate a detached signature

public static void signFileDetached(File fileToSign, File pgpKeyRingFile, File outputFile, char[] passphrase, boolean asciiArmor) throws IOException, PGPException, SignatureException {
	InputStream keyInputStream = new BufferedInputStream(new FileInputStream(pgpKeyRingFile));
	
	OutputStream outputStream = null;
	if (asciiArmor) {
		outputStream = new ArmoredOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile)));
	}
	else {
		outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
	}
		
	PGPSecretKey pgpSecretKey = readSecretKey(keyInputStream);
	PGPPrivateKey pgpPrivateKey = pgpSecretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(passphrase));
	PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
	signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, pgpPrivateKey);
		
	BCPGOutputStream bOut = new BCPGOutputStream(outputStream);
	InputStream fIn = new BufferedInputStream(new FileInputStream(fileToSign));

	int ch;
	while ((ch = fIn.read()) >= 0) {
		signatureGenerator.update((byte)ch);
	}

	fIn.close();

	signatureGenerator.generate().encode(bOut);
		
	outputStream.close();
	keyInputStream.close();
}

This is the code segment that will verify the signature file.

public static boolean verifyFileDetached(File fileToVerify, File signatureFile, File publicKeyFile) throws FileNotFoundException, IOException, PGPException, SignatureException {
	InputStream keyInputStream = new BufferedInputStream(new FileInputStream(publicKeyFile));
	InputStream sigInputStream = PGPUtil.getDecoderStream(new BufferedInputStream(new FileInputStream(signatureFile)));

	PGPObjectFactory pgpObjFactory = new PGPObjectFactory(sigInputStream);
	PGPSignatureList pgpSigList = null;

	Object obj = pgpObjFactory.nextObject();
	if (obj instanceof PGPCompressedData) {
		PGPCompressedData c1 = (PGPCompressedData)obj;
		pgpObjFactory = new PGPObjectFactory(c1.getDataStream());
		pgpSigList = (PGPSignatureList)pgpObjFactory.nextObject();
	}
	else {
		pgpSigList = (PGPSignatureList)obj;
	}

	PGPPublicKeyRingCollection pgpPubRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyInputStream));
	InputStream  fileInputStream = new BufferedInputStream(new FileInputStream(fileToVerify));
	PGPSignature sig = pgpSigList.get(0);
	PGPPublicKey pubKey = pgpPubRingCollection.getPublicKey(sig.getKeyID());
	sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), pubKey);

	int ch;
	while ((ch = fileInputStream.read()) >= 0) {
		sig.update((byte)ch);
	}

	fileInputStream.close();
	keyInputStream.close();
	sigInputStream.close();
		
	if (sig.verify()) {
		return true;
	}
	else {
		return false;
	}
}

Now putting it all together with an actual working version. Below is the PGPCryptoTools class that will perform the complex operations of signing and verifying.

/**
 * 
 * Copyright George El-Haddad</br>
 * <b>Time stamp:</b> Dec 10, 2013 - 16:07:43 PM<br/>
 * @author George El-Haddad
 * <br/>
 *
 */
public final class PGPCryptoTools {

	static {
		if(Security.getProvider("BC") == null) {
			Security.addProvider(new BouncyCastleProvider());
		}
	}

	private PGPCryptoTools() {

	}
		
	public static void signFileDetached(File fileToSign, File pgpKeyRingFile, File outputFile, char[] passphrase, boolean asciiArmor) throws IOException, PGPException, SignatureException {
		InputStream keyInputStream = new BufferedInputStream(new FileInputStream(pgpKeyRingFile));
		
		OutputStream outputStream = null;
		if (asciiArmor) {
			outputStream = new ArmoredOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile)));
		}
		else {
			outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
		}
		
		PGPSecretKey pgpSecretKey = readSecretKey(keyInputStream);
		PGPPrivateKey pgpPrivateKey = pgpSecretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(passphrase));
		PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
		signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, pgpPrivateKey);
		
		BCPGOutputStream bOut = new BCPGOutputStream(outputStream);
		InputStream fIn = new BufferedInputStream(new FileInputStream(fileToSign));

		int ch;
		while ((ch = fIn.read()) >= 0) {
			signatureGenerator.update((byte)ch);
		}

		fIn.close();

		signatureGenerator.generate().encode(bOut);
		
		outputStream.close();
		keyInputStream.close();
	}
	
	public static boolean verifyFileDetached(File fileToVerify, File signatureFile, File publicKeyFile) throws FileNotFoundException, IOException, PGPException, SignatureException {
		InputStream keyInputStream = new BufferedInputStream(new FileInputStream(publicKeyFile));
		InputStream sigInputStream = PGPUtil.getDecoderStream(new BufferedInputStream(new FileInputStream(signatureFile)));

		PGPObjectFactory pgpObjFactory = new PGPObjectFactory(sigInputStream);
		PGPSignatureList pgpSigList = null;

		Object obj = pgpObjFactory.nextObject();
		if (obj instanceof PGPCompressedData) {
			PGPCompressedData c1 = (PGPCompressedData)obj;
			pgpObjFactory = new PGPObjectFactory(c1.getDataStream());
			pgpSigList = (PGPSignatureList)pgpObjFactory.nextObject();
		}
		else {
			pgpSigList = (PGPSignatureList)obj;
		}

		PGPPublicKeyRingCollection pgpPubRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyInputStream));
		InputStream  fileInputStream = new BufferedInputStream(new FileInputStream(fileToVerify));
		PGPSignature sig = pgpSigList.get(0);
		PGPPublicKey pubKey = pgpPubRingCollection.getPublicKey(sig.getKeyID());
		sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), pubKey);

		int ch;
		while ((ch = fileInputStream.read()) >= 0) {
			sig.update((byte)ch);
		}

		fileInputStream.close();
		keyInputStream.close();
		sigInputStream.close();
		
		if (sig.verify()) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * <p>Return the first suitable key for signing in the key ring
	 * collection. For this case we only expect there to be one key
	 * available for signing.</p>
	 * 
	 * @param input - the input stream of the PGP key ring
	 * @return the first suitable PGP secret key found for signing
	 * @throws IOException on I/O related errors
	 * @throws PGPException on signing errors
	 */
	private static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException
	{
		PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input));
		PGPSecretKey secKey = null;

		@SuppressWarnings("unchecked")
		Iterator<PGPSecretKeyRing> iter = pgpSec.getKeyRings();
		while (iter.hasNext() && secKey == null) {
			PGPSecretKeyRing keyRing = iter.next();

			@SuppressWarnings("unchecked")
			Iterator<PGPSecretKey> keyIter = keyRing.getSecretKeys();
			while (keyIter.hasNext()) {
				PGPSecretKey key = keyIter.next();
				if (key.isSigningKey()) {
					secKey = key;
					break;
				}
			}
		}

		if(secKey != null) {
			return secKey;
		}
		else {
			throw new IllegalArgumentException("Can't find signing key in key ring.");
		}
	}

	/**
	 * <p>Return the first suitable key for encryption in the key ring
	 * collection. For this case we only expect there to be one key available
	 * for encryption.</p>
	 * 
	 * @param input - the input stream of the PGP key ring
	 * @return the first suitable PGP public key found for encryption
	 * @throws IOException on I/O related errors
	 * @throws PGPException on signing errors
	 */
	private static final PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException
	{
		PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input));
		PGPPublicKey pubKey = null;

		@SuppressWarnings("unchecked")
		Iterator<PGPPublicKeyRing> keyRingIter = pgpPub.getKeyRings();
		while (keyRingIter.hasNext() && pubKey == null) {
			PGPPublicKeyRing keyRing = keyRingIter.next();

			@SuppressWarnings("unchecked")
			Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
			while (keyIter.hasNext()) {
				PGPPublicKey key = keyIter.next();

				if (key.isEncryptionKey()) {
					pubKey = key;
					break;
				}
			}
		}

		if(pubKey != null) {
			return pubKey;
		}
		else {
			throw new IllegalArgumentException("Can't find encryption key in key ring.");
		}
	}
}

Below is the source code of the main application that we would run to sign and verify files.

/**
 * 
 * Copyright George El-Haddad</br>
 * <b>Time stamp:</b> Dec 10, 2013 - 16:09:43 PM<br/>
 * @author George El-Haddad
 * <br/>
 *
 */
public class PGPCryptoBC {

	public PGPCryptoBC() {
		
	}
	
	public void signFileDetached() {
		String keysDir = System.getProperty("user.dir")+File.separator+"src/george/crypto/pgp/keys";
		String filesDir = System.getProperty("user.dir")+File.separator+"src/george/crypto/pgp/files";
		
		File theFile = new File(filesDir+File.separator+"TheFile.txt");
		File keyRingFile = new File(keysDir+File.separator+"secret.asc");
		File signatureFile = new File(filesDir+File.separator+"TheFile.txt.sig");
		
		try {
			PGPCryptoTools.signFileDetached(theFile, keyRingFile, signatureFile, "TestPass12345!".toCharArray(), false);
			System.out.println("File to sign: "+theFile.getAbsolutePath());
			System.out.println("Signing key: "+keyRingFile.getAbsolutePath());
			System.out.println("Signed file: "+signatureFile.getAbsolutePath());
		}
		catch(Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public void verifyFileDetached() {
		String keysDir = System.getProperty("user.dir")+File.separator+"src/george/crypto/pgp/keys";
		String filesDir = System.getProperty("user.dir")+File.separator+"src/george/crypto/pgp/files";
		
		File publicKeyFile = new File(keysDir+File.separator+"public.asc");
		File signedFile = new File(filesDir+File.separator+"TheFile.txt");
		File signatureFile = new File(filesDir+File.separator+"TheFile.txt.sig");
		
		try {
			boolean verified = PGPCryptoTools.verifyFileDetached(signedFile, signatureFile, publicKeyFile);
			System.out.println("File: "+signedFile.getAbsolutePath());
			System.out.println("Verified: "+verified);
		}
		catch(Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public static void main(String ... args) {
		new PGPCryptoBC().signFileDetached();
		new PGPCryptoBC().verifyFileDetached();
	}
}

As you can see the methods are quite clean and friendly, but as you go deep into the code they get rather complex. A known problem with The Legion of the Bouncy Castle, but we try to make do with what we can. Hope you all enjoyed this part in my series.

And yes, I do plan to make more posts going deeper and trying out more complex things, such as generating a web-of-trust.

Advertisements

3 thoughts on “PGP Cryptography With The Legion of the Bouncy Castle – Part 5

    janhoydahl said:
    May 4, 2017 at 12:01 am

    Hi, great blog. I’m trying to improve https://github.com/justinludwig/jpgpj with capability to verify detached signatures, using your code as a starting point. However, since there is no license attached to this post I assume that it is intended to be GPLv3 as a previous post of yours. Your choice of license effectively stops the use with mentioned library (MIT licensed), so if you intend the greatest possible reach of your example code, please consider dual-licensing it also under a permissive license (MIT, BSD, Apache mm). What do you say?

      geodma responded:
      May 4, 2017 at 8:40 am

      Hi Jan, I just checked out your github project and I really like that you’re using my 5 part blog on bouncycastle! This was the intent in the first place to make an example that is easy to use and complete. Yes, you assumed right; in one of my earlier posts I only added a GPL-v3 license to one of the examples, I think it was on the main program or something and then I didn’t include any from that point on. I didn’t want to GPL-v3 the entire thing so rest assured. I actually put the code on github and added a BSD-3 license so there is no ambiguity. I hope this will make things better for you and other developers as well.

      https://github.com/george-haddad/bouncycastle

      Thank you for your comment and if there is any improvements you can see to be done please feel free to fork/pull-request it 🙂

        janhoydahl said:
        May 4, 2017 at 3:42 pm

        Thanks, will check out the github repo

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s