Monday, January 2, 2012

Java: Implementing PGP Single Pass Sign and Encrypt using League of Bouncy Castle library

The League of Bouncy Castle Cryptography library is chock-full of goodies but it is hard to convert what is in there to more practical examples.
The example files are a solid basis but I seam to need to fiddle quite a bit until it something is usable for me. The PGP Single Pass Sign and Encrypt process is one of these things that took me for a long time to figure out. I owe much of the actual solution impementation to John Opincar who solved this puzzle for C#.
Here is my implementation for Java:




 /**

  * This is the primary function that will create encrypt a file and sign it

  * with a one pass signature. This leans on an C# example by John Opincar

  * @author Bilal Soylu

  * @param targetFileName

  *            -- file name on drive systems that will contain encrypted content

  * @param embeddedFileName

  *            -- the original file name before encryption

  * @param secretKeyRingInputStream

  *            -- Private Key Ring File

  * @param targetFileStream

  *            -- The stream for the encrypted target file

  * @param secretKeyPassphrase

  *            -- The private key password for the key retrieved from

  *            collection used for signing

  * @param signPublicKeyInputStream

  *            -- the public key of the target recipient to be used to

  *            encrypt the file

  * @throws Exception

  */

 public void fEncryptOnePassSignatureLocal(String targetFileName,

   String embeddedFileName, InputStream secretKeyRingInputStream,

    OutputStream targetFileStream, String secretKeyPassphrase,   

   InputStream signPublicKeyInputStream, InputStream contentStream) throws Exception {

  // ** INIT

  // read public Key from stream (file, if keyring we use the first working key)

  PGPPublicKey encKey = readPublicKey(signPublicKeyInputStream);

  // need to convert the password to a character array

  char[] password = secretKeyPassphrase.toCharArray();

  int BUFFER_SIZE = 1 << 16; // should always be power of 2(one shifted bitwise 16 places)

  //for now we will always do integrity checks and armor file

  boolean armor = true;

  boolean withIntegretyCheck = true;

  //set default provider, we will pass this along

  BouncyCastleProvider bcProvider = new BouncyCastleProvider();



  // armor stream if set

  if (armor)

   targetFileStream = new ArmoredOutputStream(targetFileStream);



  // Init encrypted data generator

  PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(

    SymmetricKeyAlgorithmTags.CAST5, withIntegretyCheck,

    new SecureRandom(), bcProvider);

  encryptedDataGenerator.addMethod(encKey);

  OutputStream encryptedOut = encryptedDataGenerator.open(targetFileStream,new byte[BUFFER_SIZE]);



  // start compression

  PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(

    CompressionAlgorithmTags.ZIP);

  OutputStream compressedOut = compressedDataGenerator.open(encryptedOut);



  //start signature

  //PGPSecretKeyRingCollection pgpSecBundle = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(secretKeyRingInputStream));

  //PGPSecretKey pgpSecKey = pgpSecBundle.getSecretKey(keyId);

  PGPSecretKey pgpSecKey = readSecretKey(secretKeyRingInputStream);

  if (pgpSecKey == null)

   throw new Exception("No secret key could be found in specified key ring collection.");

  PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(password,bcProvider);



  PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(

    pgpSecKey.getPublicKey().getAlgorithm(),

    HashAlgorithmTags.SHA1, bcProvider);

  

  signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);

  // iterate to find first signature to use

  for (@SuppressWarnings("rawtypes")

  Iterator i = pgpSecKey.getPublicKey().getUserIDs(); i.hasNext();) {

   String userId = (String) i.next();

   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();

  // get file handle

  File actualFile = new File(targetFileName);

  // create output stream

  OutputStream literalOut = literalDataGenerator.open(compressedOut,

    PGPLiteralData.BINARY, embeddedFileName,

    new Date(actualFile.lastModified()), new byte[BUFFER_SIZE]);

  

  

  // read input file and write to target file using a buffer

  byte[] buf = new byte[BUFFER_SIZE];

  int len;

  while ((len = contentStream.read(buf, 0, buf.length)) > 0) {

   literalOut.write(buf, 0, len);

   signatureGenerator.update(buf, 0, len);

  }

  // close everything down we are done

  literalOut.close();

  literalDataGenerator.close();

  signatureGenerator.generate().encode(compressedOut);

  compressedOut.close();

  compressedDataGenerator.close();

  encryptedOut.close();

  encryptedDataGenerator.close();

  



  if (armor) targetFileStream.close();



 }

 /**

  * Try to find a public key in the Key File or Key Ring File

  * We will use the first one for now.

  * @author Bilal Soylu

  * @param in -- File Stream to KeyRing or Key

  * @return first public key

  * @throws IOException

  * @throws PGPException

  */

 private static PGPPublicKey readPublicKey(InputStream in)

   throws IOException, PGPException {

  in = PGPUtil.getDecoderStream(in);



  PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in);



  //

  // we are only looking for the first key that matches

  //



  //

  // iterate through the key rings.

  //

  Iterator rIt = pgpPub.getKeyRings();



  while (rIt.hasNext()) {

   PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();

   Iterator kIt = kRing.getPublicKeys();



   while (kIt.hasNext()) {

    PGPPublicKey k = (PGPPublicKey) kIt.next();



    if (k.isEncryptionKey()) {

     return k;

    }

   }

  }



  throw new IllegalArgumentException(

    "Can't find encryption key in key ring.");

 }



 

 

 /**

  * Find first secret key in key ring or key file. 

  * A secret key contains a private key that can be accessed with a password.

  * @author Bilal Soylu

  * @param in -- input Key file or key ring file

  * @param passwd -- password for key

  * @return matching private key

  * @throws IOException

  * @throws PGPException

  * @throws NoSuchProviderException

  */

 private static PGPSecretKey readSecretKey(InputStream in)

   throws IOException, PGPException, NoSuchProviderException {

  

  PGPSecretKey               sKey = null;

  try {

   in = PGPUtil.getDecoderStream(in);

   PGPSecretKeyRingCollection pgpPriv = new PGPSecretKeyRingCollection(in);

 

   // we just loop through the collection till we find a key suitable for

   // decrypt

   Iterator  it = pgpPriv.getKeyRings();       

   PGPSecretKeyRing   pbr = null;

 

         while (sKey == null && it.hasNext())

         {

          Object readData = it.next();

          if (readData instanceof PGPSecretKeyRing) {           

           pbr = (PGPSecretKeyRing)readData;             

              sKey =  pbr.getSecretKey();

             }

         }

         

         if (sKey == null)

         {

             throw new IllegalArgumentException("secret key for message not found.");

         }

  }

        catch (PGPException e)

        {

            System.err.println(e);

            if (e.getUnderlyingException() != null)

            {

                e.getUnderlyingException().printStackTrace();

            }

        }

        return sKey; 

 } 


Cheers,
B.

1 comment:

Mauricio Sanaphre said...

Really useful!! This is what I was looking for, you just saved my day and code works very smooth, thanks!!