使用BouncyCastle c#进行PgP加密和解密

21

我看了很多帖子,跟了很多教程,但似乎都没用。有时候,他们提到一些找不到的类。请问能否指向一个可以获得简单教程的地方,展示如何加密和解密文件。

我对Pgp非常陌生,欢迎任何帮助。

4个回答

38

我知道这个问题已经有几年了,但是在与Bouncy Castle相关的PGP解密搜索中,它仍然是谷歌排名第一或第二。由于似乎很难找到一个完整而简洁的示例,我想在这里分享我的工作解决方案,用于解密PGP文件。这只是Bouncy Castle源文件中包含的示例的修改版本。

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

2
不完全是我所需求的,但足够接近以满足我的需求。省去了我相当大量的工作。Bouncy Castle仍然是我在.NET中寻找PGP的最佳免费解决方案,并且还超过一些付费产品。 - Dave
1
你知道如何解决“加密消息包含签名消息 - 不是字面数据”错误吗? - Mokh Akh
@MokhAkh,如果你查看我的代码,你会发现当消息是PgpOnePassSignatureList而不是预期的PgpLiteralData时,就会引发此错误。我不知道你的确切情况,但我猜想如果你开始在SO或Google上搜索PgpOnePassSignatureList,你就会开始弄清楚了。 - Dan
请执行以下代码:plainFact.NextPgpObject();,这将解决我的问题。非常感谢! - Mokh Akh
一个加密解决方案,对于那些同时寻找解密和加密的人:https://dev59.com/iVbTa4cB1Zd3GeqP8jV2#5676629 - OzBob
显示剩余2条评论

11

我使用了PgpCore包,它是Portable.BouncyCastle的包装器。

它非常干净且易于使用。这里有多个示例可供参考


5
这个怎么样:

这个链接是有关Bouncycastle PGP解密过程中PartialInputStream的内容:

https://dev59.com/iVbTa4cB1Zd3GeqP8jV2

此外,里面还有一些示例文件:

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

希望这能帮到你。如果你还遇到问题,请提供更多关于编译器报错的类别等详细信息,社区会为您提供帮助。


嗨。我想写一个完整的解决方案,并发布我的代码以及我可能遇到的错误。我会查看解密代码。 - ritcoder

3

现在,2021年,Nikhil的回答可能是最好的,因为它抽象出了直接使用BouncyCastle的需要。去给他点赞吧。

如果你因为某些原因想直接使用BouncyCastle,我有一个现代化的Dan回答的实现,以及他所使用的示例,在NET5中直接使用BouncyCastle。看一下:

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

已安装 Nuget 包 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!");
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接