Bouncycastle encryption for PGP 2.6.x
Thanks to David Hook who pointed me to right direction and gave ideas to keep on pushing
The requirement: I had to integrate with external system by sending pgp encrypted xml messages to server X. Sounds easy. But the server X used pgp 2.6.3is for decryption.
I used Bouncycastle (BC) java implementation for encryption and did not find any very good examples. I think BC tries to keep things really flexible by having CompressedDataGenerator, LiteralDataGenerator, EncryptedDataGenerator etc. At first all this looks overwhelming. I was hoping to create and configure single instance and call encrypt method.
Because I had to be compatible with pgp 2.6.x I anticipated more issues. So how did I encrypt my xml. If your interested of the code then read on.
I saw various errors on console when I tried to decrypt my messages but unfortunately I am not able to bind them to my code. Meaning I’m probably not capable of saying that “Decompression error. Probable corrupted input.” means that you have to change X, Y and Z in your code. Final solution came from one BC example – KeyBasedFileProcessor
First the general ecryption process:
public File encryptXml(String xml, File result) throws IOException {
// Stream that writes result
System.out.println("Encrypting: " + xml.length() + " bytes");
//This is the final output
OutputStream out = new FileOutputStream(result);
//Intermediate output
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
//Create compressed generator
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedDataGenerator.ZIP);
// and direct that generator to write results to ByteArrayOutputStream
OutputStream compressedOut = comData.open(bOut);
Now in the BC example file shortens stuff and uses PGPUtil class here but I had to write stuff out.
true means that we require that data generator is compatible with old version (2.6.x)
PGPLiteralDataGenerator literalDataGen = new PGPLiteralDataGenerator(true);
// Actual stream that writes bytes to compressedOut.
OutputStream literalOut =
literalDataGen.open(compressedOut, PGPLiteralData.BINARY,
result.getName(), //Name of the "file" encrypted. As I understood its could be any name
xml.length(), //Total length of the data encrypted
new Date()); //Last modified time
// Copy input file bytes to literal stream with commons io utils
IOUtils.write(xml, literalOut);
literalOut.close();
compressedOut.close();
Some examples also demonstrate this done with some byte array as buffer (that has to be with size of power of 2).Those examples use partial packet streams to write data. But because old versions (like pgp 2.6.3) do not support partial packages we can’t use that. Instead we have to use length (fixed size package).
Now data compression is completed and we can start encrypting.
// object that encrypts the data
PGPEncryptedDataGenerator encDataGen =
new PGPEncryptedDataGenerator(PGPEncryptedDataGenerator.IDEA,//Algorithm (CAST5 should also work)
new SecureRandom(),
true, // oldFormat support is required
BOUNCYCASTLE_PROVIDER); //This is "BC"
//Add public key that is used for encryption
encDataGen.addMethod(publicKey);
//Get compressed bytes that are encrypted
byte[] bytes = bOut.toByteArray();
//Open encryption stream to final result outputstream and mark how many bytes will be written there
encryptedOut = encDataGen.open(out, bytes.length);
//Write bytes to stream that is going to encrypt them
encryptedOut.write(bytes);
//Close it up
encryptedOut.close();
out.close();
And we are done.Some key points:
- new PGPLiteralDataGenerator(true) – oldFormat
- new PGPEncryptedDataGenerator with oldFormat = true. Make sure you don’t set withIntegrityPacket=true. Look javaDoc for constructors
- Do not just stack up the streams
- Don’t use buffers, use fixed length packet format
For me it worked hope you won’t have to jump so many hoops for security. If you find any mistakes or see anything that could be improved please let me know.
RSS
Hi there,
Thanks a lot for that, very little documentation on how to circumvent the PGP2.6+ issue. Although, I’m still getting the “Unsupported packet format” error.
Below is teh code I use, which is pretty much identical to what you have –
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
boolean oldFormat = true;
OutputStream out = new FileOutputStream(encodedFile);
if(armor)
{
out = new ArmoredOutputStream(out);
}
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
OutputStream compressedStream = comData.open(bOut);
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(true);
OutputStream literalOut = lData.open(compressedStream, PGPLiteralData.BINARY, encodedFile.getName(), h1ldaOutput.length(), new Date());
IOUtils.write(h1ldaOutput, literalOut);
literalOut.close();
compressedStream.close();
PGPEncryptedDataGenerator encDataGen = new PGPEncryptedDataGenerator(PGPEncryptedData.IDEA, new SecureRandom(), oldFormat, “BC”);
encDataGen.addMethod(encKey);
byte[] bytes = bOut.toByteArray();
OutputStream encryptedOut = encDataGen.open(out, bytes.length);
encryptedOut.write(bytes);
encryptedOut.close();
out.close();
Any help/suggestions would be greatly appreciated
Is this h1ldaOutput a stream you wish to encrypt? Stream length() method might not return the total length of stream (I’m encrypting byte array). In addtion try removing armored option. when encrypted with armor you have to provide -a key when decrypting on command line if I remember it correctly.
Sry for late reply. Hope you get this thing working
Thanks for the reply, actually, you were a lot quicker than you think
h1ldaOutput is a String object. the armor variable at that point is false so it isn’t armored.
Thanks
I cannot see a reason why this shouldn’t work. Had few ideas but for me encrypting String with your code worked. Also you might still want to convert your string to byte[] before encryption. If String contains UTF8 characters then h1ldaOutput.length() != h1ldaOutput.getBytes().length
For example ‘char ä’ length()=6 getBytes().length=7 and after decrypting last character is missing.