Sunday, 7 June 2009

PGP Decryption with C#

Yesterday I posted on how I was able to encrypt some data and compress it using C#. Been thinking about how useful it would be to be able to decrypt it as well, there is no pressing need for me to do this in the situation I was working in really - once my data is encrypted and archived it would be sent off to someone else, so I dont really need to worry about decrypting it.



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

15 comments:

  1. This is a great post. Is there a way to decrypt a file encrypted with a DSA key, using BouncyCastle?
    I will appreciate any directions on how to go about it.

    I have come across this question on several forums while trying to figure it and hope my this can help some.

    ReplyDelete
  2. Glad you liked it :). As far as I know, DSA is only used for signing and not for encryption.

    I remember when I was working on this, I had to make the distinction between signing and encrypting data.

    But, in saying that, if you have encrypted with DSA, then they encryption should be the same as the example above (if using keys). Note that the example to decrypt doesnt speciy an encryption algorythm.

    ReplyDelete
  3. Thanks for the reply. I expected the same but am stuck and will appreciate if you can share your opinion on the following observation:

    The private key constructor accepts an asymmetric key in the PrivateKey implementation for C#.

    Constructor:
    public PgpPrivateKey (AsymmetricKeyParameter privateKey, long keyId);

    My private key is Dsa and is symmetric – {Org.BouncyCastle.Crypto.Parameters.DsaPrivateKeyParameters}

    and upon the execution of the statement - Stream clear = pbe.GetDataStream(privKey); the run time exception is -{"Unable to cast object of type 'Org.BouncyCastle.Crypto.Parameters.DsaPrivateKeyParameters' to type 'Org.BouncyCastle.Crypto.Parameters.ElGamalKeyParameters'."}

    Should it default to ElGamal? Is there a way to specify it to use DsaKeyParameters?

    Thanks again,
    Saurabh

    ReplyDelete
  4. I am trying to compile your code but it is giving error as it couldn't find the class "PGPKeys". Am I missing something?

    ReplyDelete
  5. @Saurabh:

    I am not sure right now, but it could be that you have a mismatch with your keys.

    DSA and El Gamal are two distinct things, one is an encryption method and the other is a signing method. This could be the problem you are facing, as I mentioned previously.

    It is an intruiging problem from my point of view, but probably not from yours!

    ReplyDelete
  6. Raphael,

    You are absolutely correct (and I missed the fact – it is not evident in PGP documentation -) PGP uses El Gamal for encryption and DSA for signing. I instantiated the keys for encrypting and signing the file and was trying to use the same (keys) object for decrypting the file, which led to the exception.

    Again, cryptography can be quite perplexing and your blog is a great help to those who use this once in a blue moon.

    Thanks again

    ReplyDelete
  7. In your example call you list a public and secret key file. I can see how to export the public key however what is secring.gpg and how can i export this. i'm using gnupg.

    Thanks
    Sean

    ReplyDelete
  8. Sean Rock, if you use the following it should export the secret key for you:

    gpg --export-secret-key -a > secret.gpg

    Should do the trick for you.

    ReplyDelete
  9. Thanks Thanks Thanks THanks THanks a ton for your great help !!!!

    ReplyDelete
  10. No worries, I am glad that people find my work helpful :)

    ReplyDelete
  11. Thanks for this article helped me finalize my gpg project... do add a part to handle uncompressed gpg files as it seems to fail when it goes through that part

    ReplyDelete
  12. This was a great post. Thanks. I have taken the liberty (as a poster above asked for it) and modified the decrypt method so that if message is not compressed or zipped then it will still decrypt. Again thanks for this post.

    public void decrypt(Stream input, string outputpath){
    input = PgpUtilities.GetDecoderStream(input);
    try {
    PgpObjectFactory pgpObjFactory = new PgpObjectFactory(input);
    PgpEncryptedDataList pgpEncryptedDataList;
    PgpObject pgpObj = pgpObjFactory.NextPgpObject();
    if (pgpObj is PgpEncryptedDataList){
    pgpEncryptedDataList = (PgpEncryptedDataList)pgpObj;
    }else {
    pgpEncryptedDataList = (PgpEncryptedDataList)pgpObjFactory.NextPgpObject();
    }
    PgpPrivateKey privKey = pgpKeys.PGPPrivateKey;
    PgpPublicKeyEncryptedData pgpPublicKeyEncryptedData = null;
    foreach (PgpPublicKeyEncryptedData pked in pgpEncryptedDataList.GetEncryptedDataObjects()){
    if (privKey != null){
    pgpPublicKeyEncryptedData = pked;
    break;
    }
    }

    Stream clear = pgpPublicKeyEncryptedData.GetDataStream(privKey);
    PgpObjectFactory plainFact = new PgpObjectFactory(clear);
    PgpObject message = plainFact.NextPgpObject();
    Stream encDataInputStream = null;

    if (message is PgpCompressedData){
    PgpCompressedData compData = (PgpCompressedData)message;
    encDataInputStream = compData.GetDataStream();
    PgpObjectFactory pgpObjFactory2 = new PgpObjectFactory(encDataInputStream);
    message = pgpObjFactory2.NextPgpObject();
    if (message is PgpOnePassSignatureList){
    message = pgpObjFactory2.NextPgpObject();
    }
    }else{
    message = plainFact.NextPgpObject();
    }

    PgpLiteralData pgpLiteralData = null;
    pgpLiteralData = (PgpLiteralData)message;
    Stream output = File.Create(outputpath + "\\" + pgpLiteralData.FileName);
    Stream unc = pgpLiteralData.GetInputStream();
    Streams.PipeAll(unc, output);
    output.Close();


    }catch (Exception e) {
    throw new Exception(e.Message);
    }
    }

    ReplyDelete
  13. This is great post,but i want to know Bouncy Castle is freeware library or any charges.Please Help me

    ReplyDelete
  14. Excellent post, Raphael. I had been looking out for this all over the web. Each and every link i stumbled upon took me to FileEncryption using BouncyCastle. The only link which talked about FileDecryption was yours. You are a saviour.

    Thanks a lot.

    ReplyDelete