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

Posted on Updated on

In part 1 I went over PGP Key pair generation, DSA/El Gamal key pairs to be exact, and how we can generate them using the Legion of the Bouncy Castle cryptography API. These key pairs can be imported directly into PGP for use or used programmatically via the Bouncy Castle API.

OK, so now that we know how to generate our PGP key pair we now will learn how to digitally sign and verify files. Signing files allows our recipient to verify the authenticity of the origin of the file we send them. It also verifies the integrity of the file as well.

Signing a file

  • PGP Private Key of sender
  • Private Key Passphrase

Verifying a file

  • Public Key of sender

To make a developers life easier I’ve decided to create a static class called PGPCryptoTools which include the sign and verify methods. One just has to send the appropriate parameters (as shown in the bullet points above) to sign and verify files.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Security;
import java.util.Iterator;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;

/**
 * 
 * Copyright George El-Haddad</br>
 * <b>Time stamp:</b> Dec 6, 2012 - 11:41:43 AM<br/>
 * @author George El-Haddad
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
public final class PGPCryptoTools {

	static {
		Security.addProvider(new BouncyCastleProvider());
	}
	
	private PGPCryptoTools() {
		
	}
	
	/**
	 * 
	 * @param fileToSign - the file to sign
	 * @param pgpKeyRingFile - the PGP Key Ring file that will do the signing
	 * @param outputFile - the signed file to be outputted
	 * @param passphrase - the secret pass phrase of the PGP Private Key 
	 * @param asciiArmor - set to true to use ASCII armor mode
	 * @throws Exception
	 */
	public static final void signFile(File fileToSign, File pgpKeyRingFile, File outputFile, char[] passphrase, boolean asciiArmor) throws Exception {
		FileInputStream keyInputStream = new FileInputStream(pgpKeyRingFile);
		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);

		@SuppressWarnings("unchecked")
		Iterator<String> it = pgpSecretKey.getPublicKey().getUserIDs();
		if (it.hasNext()) {
			PGPSignatureSubpacketGenerator  spGen = new PGPSignatureSubpacketGenerator();
			spGen.setSignerUserID(false, it.next());
			signatureGenerator.setHashedSubpackets(spGen.generate());
		}
		
		OutputStream outputStream = null;
		if (asciiArmor) {
			outputStream = new ArmoredOutputStream(new FileOutputStream(outputFile));
		}
		else {
			outputStream = new FileOutputStream(outputFile);
		}
		
		PGPCompressedDataGenerator  compressDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
		BCPGOutputStream bcOutputStream = new BCPGOutputStream(compressDataGenerator.open(outputStream));
		signatureGenerator.generateOnePassVersion(false).encode(bcOutputStream);

		PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
		OutputStream literalDataGenOutputStream = literalDataGenerator.open(bcOutputStream, PGPLiteralData.BINARY, fileToSign);
		FileInputStream fis = new FileInputStream(fileToSign);
		
		int ch;
		while ((ch = fis.read()) >= 0) {
			literalDataGenOutputStream.write(ch);
			signatureGenerator.update((byte)ch);
		}

		literalDataGenerator.close();
		fis.close();

		signatureGenerator.generate().encode(bcOutputStream);
		compressDataGenerator.close();
		outputStream.close();
	}
	
	/**
	 *
	 * @param fileToVerify - the signed file to verify
	 * @param publicKeyFile - the public key to verify against
	 * @throws Exception
	 */
	public static final boolean verifyFile(File fileToVerify, File publicKeyFile) throws Exception {
		InputStream in = PGPUtil.getDecoderStream(new FileInputStream(fileToVerify));
		
		PGPObjectFactory pgpObjFactory = new PGPObjectFactory(in);
		PGPCompressedData compressedData = (PGPCompressedData)pgpObjFactory.nextObject();
		
		//Get the signature from the file
		 
		pgpObjFactory = new PGPObjectFactory(compressedData.getDataStream());
		PGPOnePassSignatureList onePassSignatureList = (PGPOnePassSignatureList)pgpObjFactory.nextObject();
		PGPOnePassSignature onePassSignature = onePassSignatureList.get(0);
		
		//Get the literal data from the file

		PGPLiteralData pgpLiteralData = (PGPLiteralData)pgpObjFactory.nextObject();
		InputStream literalDataStream = pgpLiteralData.getInputStream();
		
		InputStream keyIn = new FileInputStream(publicKeyFile);
		PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
		PGPPublicKey key = pgpRing.getPublicKey(onePassSignature.getKeyID());
		
		FileOutputStream literalDataOutputStream = new FileOutputStream(pgpLiteralData.getFileName());
		onePassSignature.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key);

		int ch;
		while ((ch = literalDataStream.read()) >= 0) {
			onePassSignature.update((byte)ch);
			literalDataOutputStream.write(ch);
		}

		literalDataOutputStream.close();
		
		//Get the signature from the written out file

		PGPSignatureList p3 = (PGPSignatureList)pgpObjFactory.nextObject();
		PGPSignature signature = p3.get(0);
		
		//Verify the two signatures

		if (onePassSignature.verify(signature)) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * <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 signing.</p>
	 * 
	 * @param input - the input stream of the key PGP Key Ring
	 * @return the first suitable PGP Secret Key found for signing
	 * @throws IOException
	 * @throws PGPException
	 */
	@SuppressWarnings("unchecked")
	private static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException
	{
		PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input));
		Iterator<PGPSecretKeyRing> iter = pgpSec.getKeyRings();
		PGPSecretKey secKey = null;
		
		while (iter.hasNext() && secKey == null) {
			PGPSecretKeyRing keyRing = iter.next();
			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.");
		}
	}
}

Now we can write code to sign and verify files. In the code example below we are assuming that the private and public keys (secret.asc and public.asc respectively) already exist. The file going to be signed is TheFile.txt and we want the signed file to be created as TheFile.pgp.

public void signFile() {
	String keysDir = System.getProperty("user.dir")+File.separator+"myKeys";
	String filesDir = System.getProperty("user.dir")+File.separator+"myFiles";
		
	File theFile = new File(filesDir+File.separator+"TheFile.txt");
	File keyRingFile = new File(keysDir+File.separator+"secret.asc");
	File signedFile = new File(filesDir+File.separator+"TheFile.pgp");
		
	try {
		PGPCryptoTools.signFile(theFile, keyRingFile, signedFile, "TestPass12345!".toCharArray(), false);
		System.out.println("File to sign: "+theFile.getAbsolutePath());
		System.out.println("Signing key: "+keyRingFile.getAbsolutePath());
		System.out.println("Signed file: "+signedFile.getAbsolutePath());
	}
	catch(Exception ex) {
		ex.printStackTrace();
	}
}

The recipient of the file would already have our public key (public.asc) and that is all they need to verify the file’s authenticity and integrity.

public void verifyFile() {
	String keysDir = System.getProperty("user.dir")+File.separator+"myKeys";
	String filesDir = System.getProperty("user.dir")+File.separator+"myFiles";
		
	File publicKeyFile = new File(keysDir+File.separator+"public.asc");
	File signedFile = new File(filesDir+File.separator+"TheFile.pgp");
		
	try {
		boolean verified = PGPCryptoTools.verifyFile(signedFile, publicKeyFile);
		System.out.println("File: "+signedFile.getAbsolutePath());
		System.out.println("Verified: "+verified);
	}
	catch(Exception ex) {
		ex.printStackTrace();
	}
}

Stay tuned for Part 3 Encryption

Advertisements

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

    w999da said:
    July 13, 2013 at 12:30 pm

    >”To make a developers life easier I’ve decided to “…
    …put the code under GPL so it can’t embedded together with BC’s Apache licensed library.

    hate

      w999da said:
      July 13, 2013 at 12:36 pm

      .. GPL-lovers are so selfish they putting GPL headers together with really free libraries, expecting they should also become GPLed.

    geodma responded:
    July 14, 2013 at 12:32 am

    What I put on my blog are mostly for example purposes, I know lots of people like to copy/paste code without regard. Which I am ok with; I have other posts where I just post code without any license so anyone can do whatever they want with it. But the BC examples I am giving was something I really had to spend time on doing.

    Most people just copy/pasted the test-case code that came out of the BC junit code and pretended that they had this all great working tutorial. Then when people asked some interesting questions they had no idea how to answer them. I took an effort not to present a half-assed tutorial to the world. It wasn’t easy, and if you go through the BC unit test cases and their lack of documentation you’ll understand what I had to go through.

    So with that, I decided to slap the GPL-v3 on my tutorial code. It’s not really meant for production use because it can be better, though it works, it can be much better.

    Though, I am interested in what you said about the GPL + Apache licence being not embedable. I manage an opensource project on sourceforge (Cardme http://sourceforge.net/projects/cardme/) using the BSD license. I like to use licenses like that because it helps other developers a lot. For this specific tutorial I chose to GPLv3 it.

    Sorry if you hate it, but I hope you at least found my tutorial useful enough to make your own version and use it to its full extent.

    I appreciate all comments, positive and negative 🙂 and if you have any suggestions for the future I will strongly consider them. Thanks.

    geodma responded:
    July 14, 2013 at 12:44 am

    And btw “>”To make a developers life easier I’ve decided to “…’ I was talking about making a static utility class, not talking about the license, but good one turning it around on me 😉

    […] 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 […]

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