Thing is, I just wondered how it would work. I already know that Bouncy Castle works heavily with streams, so I guess the logic would be:
- Open a file as a stream
- Get the encryption keys needed to decrypt the file
- Decrypt the file to a stream
- Write the file(s)
I already have the code that will manage the keys for me and I already have an understanding on how Bouncy Castle's libs work with streams. I had a quick google to see if there was anything going in the way of examples on how to do this, but only managed to find one (someone had pasted the example in the Bouncy Castle source to a forum). Thing is, none of the offered examples seemed to fit my code, which was a shame. However, these things can be overcome once you understand a little more about how Bouncy Castle implements OpenPGP. The code I created to decrypt my archive isnt going to go into production (probably), its just something I wanted to try out, so it's all one big method. Note, these libs work on streams - to decrypt my archive and get to the literal data, I had to manipulate the streams to get there, I think this could put a lot of people off using this. First off, we need to get the file we wish to decrypt into a stream and determine the decoder from it:
input = PgpUtilities.GetDecoderStream(input);
The next thing we want to do is create a PGPObjectFactory. From here we can get a PGPEncryptedDataList and start creating objects from our input stream:
PgpObjectFactory pgpObjF = new PgpObjectFactory(input);
PgpEncryptedDataList enc;
PgpObject obj = pgpObjF.NextPgpObject();
if (obj is PgpEncryptedDataList)
{
enc = (PgpEncryptedDataList)obj;
}
else
{
enc = (PgpEncryptedDataList)pgpObjF.NextPgpObject();
}
Now we are able to get the first encrypted object from our stream so lets decrypt the stream using the private key:
PgpPrivateKey privKey = pgpKeys.PGPPrivateKey;
PgpPublicKeyEncryptedData pbe = null;
foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects())
{
if (privKey != null)
{
pbe = pked;
break;
}
}
Stream clear = pbe.GetDataStream(privKey);
PgpObjectFactory plainFact = new PgpObjectFactory(clear);
PgpObject message = plainFact.NextPgpObject();
With this done, we are now able to start inspecting the decrypted objects we are creating. We know the file we made earlier was an encrypted zip file, so at this point it is fair to assume message is compressed, which it is:
if (message is PgpCompressedData)
{
PgpCompressedData cData = (PgpCompressedData)message;
Stream compDataIn = cData.GetDataStream();
I knew that message was of the type PGPCompressedData from inspecting the object, what we need to do now is find out when message becomes literal data. To do this, we need to go a bit further, I need to create a new PGPObjectFactory:
PgpObjectFactory o = new PgpObjectFactory(compDataIn);
message = o.NextPgpObject();
if (message is PgpOnePassSignatureList)
{
message = o.NextPgpObject();
PgpLiteralData Ld = null;
Ld = (PgpLiteralData)message;
Stream output = File.Create(outputpath + "\\" + Ld.FileName);
Stream unc = Ld.GetInputStream();
Streams.PipeAll(unc, output);
}
I had thought that the literal data would come next, but it didnt - dont forget when encrypting the data previously I signed it - so I got the signature first. So, I just needed to move on to the next object which was the literal data. As you can see above, all I need to do now is write the file out to the disk and I am done. I did think when I had completed this, "What about decompressing directory structures?". Well, in my humble opinion, there is no need to do this. From what I have seen with encryption over the last few months, files get encrypted, not directories. I wrote a lib the other day that compresses into a zip, I will post it here over the next week, it also decompresses (including the directory structure). So the way I see it, if you wanted to encrypt a set of files and directories, you would create the zip first and then create an encrypted archive from that file. I know it would be two seperate operations, but this sort of suits my situation at the moment: I dont need to decrypt archives programatically, but I can if I need to (now, anyway). So with the stuff I have now, I can create a zip for internal deployment and then create a seperate, encrypted zip from that file for external deployment.
The complete code for decryption is as follows:
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.IO;
using Org.BouncyCastle.Bcpg;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Tools.PGP.Crypto;
namespace Tools.PGP.Crypto
{
public class PGPDecrypt
{
public string _encryptedFilePath;
public string _privKeyPath;
public char[] _password;
public string _outputPath;
public PGPKeys pgpKeys;
public PGPDecrypt(string encryptedFilePath, string privKeyPath, string password, string outputPath, string pubKeyPath, long keyID)
{
_encryptedFilePath = encryptedFilePath;
_outputPath = outputPath;
_password = password.ToCharArray();
_privKeyPath = privKeyPath;
pgpKeys = new PGPKeys(pubKeyPath, privKeyPath, password, keyID);
}
public void decrypt(Stream input, string outputpath)
{
input = PgpUtilities.GetDecoderStream(input);
try
{
PgpObjectFactory pgpObjF = new PgpObjectFactory(input);
PgpEncryptedDataList enc;
PgpObject obj = pgpObjF.NextPgpObject();
if (obj is PgpEncryptedDataList)
{
enc = (PgpEncryptedDataList)obj;
}
else
{
enc = (PgpEncryptedDataList)pgpObjF.NextPgpObject();
}
PgpPrivateKey privKey = pgpKeys.PGPPrivateKey;
PgpPublicKeyEncryptedData pbe = null;
foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects())
{
if (privKey != null)
{
pbe = pked;
break;
}
}
Stream clear = pbe.GetDataStream(privKey);
PgpObjectFactory plainFact = new PgpObjectFactory(clear);
PgpObject message = plainFact.NextPgpObject();
if (message is PgpCompressedData)
{
PgpCompressedData cData = (PgpCompressedData)message;
Stream compDataIn = cData.GetDataStream();
PgpObjectFactory o = new PgpObjectFactory(compDataIn);
message = o.NextPgpObject();
if (message is PgpOnePassSignatureList)
{
message = o.NextPgpObject();
PgpLiteralData Ld = null;
Ld = (PgpLiteralData)message;
Stream output = File.Create(outputpath + "\\" + Ld.FileName);
Stream unc = Ld.GetInputStream();
Streams.PipeAll(unc, output);
}
else
{
PgpLiteralData Ld = null;
Ld = (PgpLiteralData)message;
Stream output = File.Create(outputpath + "\\" + Ld.FileName);
Stream unc = Ld.GetInputStream();
Streams.PipeAll(unc, output);
}
}
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
}
}
To decrypt a file, I can just call the method like so:
PGPDecrypt test = new PGPDecrypt(@"C:\test\somefile.zip",
@"C:\GnuPG\secring.gpg",
"password",
@"C:\test\test",
@"C:\GnuPG\pubring.gpg",
3699527550217851901);
FileStream fs = File.Open(@"C:\test\somefile.zip", FileMode.Open);
test.decrypt(fs,@"C:\test\test");