PgP Encryption and Decryption using BouncyCastle c#
Asked Answered
A

4

22

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.

Akira answered 8/8, 2011 at 19:49 Comment(0)
G
5

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.

Glynis answered 8/8, 2011 at 22:48 Comment(1)
Hi. I think I'll write a complete solution and post my code with the errors I may get. Will check out the decryption code.Akira
G
40

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);
        }
    }
}
Ginelle answered 27/8, 2015 at 19:21 Comment(8)
Not exactly what I needed, but close enough to tailor to my needs. Saved me a decent amount of work. Bouncy Castle is still the best free solution I've found for PGP in .NET, and beats some of the paid ones too.Onia
do you know how to solve "encrypted message contains a signed message - not literal data." error ?Immutable
@MokhAkh, if you look in my code, you'll see that this error is raised when the message is PgpOnePassSignatureList instead of the expected PgpLiteralData. I don't know your exact scenario but I'm guessing if you start searching SO or Google for PgpOnePassSignatureList, you'll start to figure it out.Ginelle
read next plainFact.NextPgpObject(); solve my issue, thank you very muchImmutable
@MokhAkh, do you know what is causing the first message to be of type PgpOnePassSignatureList instead of PgpLiteralData? Is it because of particular way data was encrypted? I only found one Ruby forum message talking about the same issue and suggesting the same solution. github.com/sgonyea/jruby-pgp/issues/2 But what is an internal cause?Barbarous
my client say that message decrypt with public key not private.Immutable
An Encrypt solution, for those also searching for both decrypt and encrypt: https://mcmap.net/q/456129/-partialinputstream-during-bouncycastle-pgp-decryptionRalaigh
Thanks. It worked. Can you also share the code for encryption?Bentonbentonite
N
12

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

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

Nosey answered 20/4, 2021 at 3:45 Comment(0)
G
5

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.

Glynis answered 8/8, 2011 at 22:48 Comment(1)
Hi. I think I'll write a complete solution and post my code with the errors I may get. Will check out the decryption code.Akira
N
3

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!");
        }
    }
}
Nanon answered 15/9, 2021 at 14:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.