ritcoder
ritcoder

Reputation: 3304

PgP Encryption and Decryption using BouncyCastle c#

I've seen a number of posts, followed a number of tutorials but none seems to work. Sometimes, they make reference to some classes which are not found. Can I be pointed to a place where I can get a simple tutorial showing how to encrypt and decrypt a file.

I'm very new to Pgp and any assistance is welcomed.

Upvotes: 22

Views: 50008

Answers (4)

Alexander Stathis
Alexander Stathis

Reputation: 81

Now, in 2021, Nikhil's answer is probably best, since it abstracts out the need for working with BouncyCastle directly. Go give him an upvote.

If you want to work with BouncyCastle directly for some reason, I've got a modern implementation of Dan's answer, and the examples he's working from, that uses BouncyCastle directly in NET5. Take a look:

using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.IO;

Installed is Nuget Package Portable.BouncyCastle 1.8.10.

public class EncryptionService 
{
    public static void EncryptPGPFile(FileInfo inFile, FileInfo keyFile, FileInfo outFile, bool withIntegrityCheck = false, bool withArmor = false)
    {
        PgpPublicKeyRingBundle keyRing = null;
        using (var keyStream = keyFile.OpenRead())
        {
            keyRing = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(keyStream));
        }

        var publicKey = keyRing.GetKeyRings()
            .Cast<PgpPublicKeyRing>()
            .FirstOrDefault()
            ?.GetPublicKeys()
            .Cast<PgpPublicKey>()
            .FirstOrDefault(x => x.IsEncryptionKey);

        using var outFileStream = outFile.Open(FileMode.Create);
        using var armoredStream = new ArmoredOutputStream(outFileStream);
        Stream outStream = withArmor ? armoredStream : outFileStream;

        byte[] compressedBytes;

        var compressor = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);

        using (var byteStream = new MemoryStream())
        {
            // Annoyingly, this is necessary. The compressorStream needs to be closed before the byteStream is read from, otherwise
            // data will be left in the buffer and not written to the byteStream. It would be nice if compressorStream exposed a "Flush"
            // method. - AJS
            using (var compressorStream = compressor.Open(byteStream))
            {
                PgpUtilities.WriteFileToLiteralData(compressorStream, PgpLiteralData.Binary, inFile);
            }
            compressedBytes = byteStream.ToArray();
        };

        var encrypter = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());
        encrypter.AddMethod(publicKey);

        using var finalOutputStream = encrypter.Open(outStream, compressedBytes.Length);
        finalOutputStream.Write(compressedBytes, 0, compressedBytes.Length);
    }

    public static void DecryptPGPFile(FileInfo inFile, FileInfo keyFile, string password, FileInfo outFile)
    {
        using var inputFile = inFile.OpenRead();
        using var input = PgpUtilities.GetDecoderStream(inputFile);

        var pgpFactory = new PgpObjectFactory(input);

        var firstObject = pgpFactory.NextPgpObject();
        if (firstObject is not PgpEncryptedDataList)
        {
            firstObject = pgpFactory.NextPgpObject();
        }

        PgpPrivateKey keyToUse = null;
        PgpSecretKeyRingBundle keyRing = null;

        using (var keyStream = keyFile.OpenRead())
        {
            keyRing = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(keyStream));
        }

        var encryptedData = ((PgpEncryptedDataList)firstObject).GetEncryptedDataObjects()
            .Cast<PgpPublicKeyEncryptedData>()
            .FirstOrDefault(x =>
            {
                var key = keyRing.GetSecretKey(x.KeyId);
                if (key != null)
                {
                    keyToUse = key.ExtractPrivateKey(password.ToCharArray());
                    return true;
                }
                return false;
            });

        if (keyToUse == null)
        {
            throw new PgpException("Cannot find secret key for message.");
        }

        Stream clearText = encryptedData.GetDataStream(keyToUse);
        PgpObject message = new PgpObjectFactory(clearText).NextPgpObject();

        if (message is PgpCompressedData data)
        {
            message = new PgpObjectFactory(inputStream: data.GetDataStream()).NextPgpObject();
        }

        if (message is PgpLiteralData literalData)
        {
            using var outputFileStream = outFile.Open(FileMode.Create);
            Streams.PipeAll(literalData.GetInputStream(), outputFileStream);
        }
        else
        {
            throw new PgpException("message is not encoded correctly.");
        }

        if (encryptedData.IsIntegrityProtected() && !encryptedData.Verify())
        {
            throw new Exception("message failed integrity check!");
        }
    }
}

Upvotes: 3

Nikhil
Nikhil

Reputation: 243

I have used PgpCore package which is a wrapper around Portable.BouncyCastle.

It is very clean and simple to use. Multiple examples available here.

Upvotes: 13

Dan
Dan

Reputation: 3703

I know this question is years old but it is still #1 or #2 in Google for searches related to PGP Decryption using Bouncy Castle. Since it seems hard to find a complete, succinct example I wanted to share my working solution here for decrypting a PGP file. This is simply a modified version of the Bouncy Castle example included with their source files.

using System;
using System.IO;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Utilities.IO;

namespace PGPDecrypt
{
    class Program
    {
        static void Main(string[] args)
        {
            DecryptFile(
                @"path_to_encrypted_file.pgp",
                @"path_to_secret_key.asc",
                "your_password_here".ToCharArray(), 
                "output.txt"
            );
        }

        private static void DecryptFile(
            string inputFileName,
            string keyFileName,
            char[] passwd,
            string defaultFileName)
        {
            using (Stream input = File.OpenRead(inputFileName),
                   keyIn = File.OpenRead(keyFileName))
            {
                DecryptFile(input, keyIn, passwd, defaultFileName);
            }
        }

        private static void DecryptFile(
            Stream inputStream,
            Stream keyIn,
            char[] passwd,
            string defaultFileName)
        {
            inputStream = PgpUtilities.GetDecoderStream(inputStream);

            try
            {
                PgpObjectFactory pgpF = new PgpObjectFactory(inputStream);
                PgpEncryptedDataList enc;

                PgpObject o = pgpF.NextPgpObject();
                //
                // the first object might be a PGP marker packet.
                //
                if (o is PgpEncryptedDataList)
                {
                    enc = (PgpEncryptedDataList)o;
                }
                else
                {
                    enc = (PgpEncryptedDataList)pgpF.NextPgpObject();
                }

                //
                // find the secret key
                //
                PgpPrivateKey sKey = null;
                PgpPublicKeyEncryptedData pbe = null;
                PgpSecretKeyRingBundle pgpSec = new PgpSecretKeyRingBundle(
                    PgpUtilities.GetDecoderStream(keyIn));

                foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects())
                {
                    sKey = FindSecretKey(pgpSec, pked.KeyId, passwd);

                    if (sKey != null)
                    {
                        pbe = pked;
                        break;
                    }
                }

                if (sKey == null)
                {
                    throw new ArgumentException("secret key for message not found.");
                }

                Stream clear = pbe.GetDataStream(sKey);

                PgpObjectFactory plainFact = new PgpObjectFactory(clear);

                PgpObject message = plainFact.NextPgpObject();

                if (message is PgpCompressedData)
                {
                    PgpCompressedData cData = (PgpCompressedData)message;
                    PgpObjectFactory pgpFact = new PgpObjectFactory(cData.GetDataStream());

                    message = pgpFact.NextPgpObject();
                }

                if (message is PgpLiteralData)
                {
                    PgpLiteralData ld = (PgpLiteralData)message;

                    string outFileName = ld.FileName;
                    if (outFileName.Length == 0)
                    {
                        outFileName = defaultFileName;
                    }

                    Stream fOut = File.Create(outFileName);
                    Stream unc = ld.GetInputStream();
                    Streams.PipeAll(unc, fOut);
                    fOut.Close();
                }
                else if (message is PgpOnePassSignatureList)
                {
                    throw new PgpException("encrypted message contains a signed message - not literal data.");
                }
                else
                {
                    throw new PgpException("message is not a simple encrypted file - type unknown.");
                }

                if (pbe.IsIntegrityProtected())
                {
                    if (!pbe.Verify())
                    {
                        Console.Error.WriteLine("message failed integrity check");
                    }
                    else
                    {
                        Console.Error.WriteLine("message integrity check passed");
                    }
                }
                else
                {
                    Console.Error.WriteLine("no message integrity check");
                }
            }
            catch (PgpException e)
            {
                Console.Error.WriteLine(e);

                Exception underlyingException = e.InnerException;
                if (underlyingException != null)
                {
                    Console.Error.WriteLine(underlyingException.Message);
                    Console.Error.WriteLine(underlyingException.StackTrace);
                }
            }
        }

        private static PgpPrivateKey FindSecretKey(PgpSecretKeyRingBundle pgpSec, long keyID, char[] pass)
        {
            PgpSecretKey pgpSecKey = pgpSec.GetSecretKey(keyID);

            if (pgpSecKey == null)
            {
                return null;
            }

            return pgpSecKey.ExtractPrivateKey(pass);
        }
    }
}

Upvotes: 41

Chris B. Behrens
Chris B. Behrens

Reputation: 6297

How's this:

PartialInputStream during Bouncycastle PGP decryption

Also, the zip contains examples here:

http://www.bouncycastle.org/csharp/

Hope this helps. If you're still stuck, post some more detail about what classes the compiler is complaining about and the community will take a look.

Upvotes: 5

Related Questions