【问题标题】:OpenPGP encryption with BouncyCastle使用 BouncyCastle 进行 OpenPGP 加密
【发布时间】:2013-09-22 07:26:27
【问题描述】:

我一直在尝试通过 Bouncy Castle 使用 OpenPGP 构建内存中的公钥加密基础设施。我们的一个供应商使用 OpenPGP 公钥加密来加密他们的所有提要,并要求我们做同样的事情,所以我坚持使用技术和实现。所以现在我正在编写一个 OpenPGP 加密/解密工具包来自动化这些提要。

bouncycastle.org 上的示例莫名其妙地默认将加密数据写入文件系统并从文件系统中收集密钥;这不是我想做的,所以我一直在尝试让一切都基于流。

我已经到了可以真正让我的代码编译和运行的地步,但是我的加密有效负载是空的。我觉得我错过了一些愚蠢的东西,但是经过几天的尝试,我已经失去了客观地检查这个的能力。

我的实用程序类包含这些方法:

    public static PgpPublicKey ImportPublicKey(
        this Stream publicIn)
    {
        var pubRings =
            new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(publicIn)).GetKeyRings().OfType<PgpPublicKeyRing>();
        var pubKeys = pubRings.SelectMany(x => x.GetPublicKeys().OfType<PgpPublicKey>());
        var pubKey = pubKeys.FirstOrDefault();
        return pubKey;
    }

    public static Stream Streamify(this string theString, Encoding encoding = null)
    {
        encoding = encoding ?? Encoding.UTF8;
        var stream = new MemoryStream(encoding.GetBytes(theString));
        return stream;
    }

    public static string Stringify(this Stream theStream,
                                   Encoding encoding = null)
    {
        encoding = encoding ?? Encoding.UTF8;
        using (var reader = new StreamReader(theStream, encoding))
        {
            return reader.ReadToEnd();
        }
    }

    public static byte[] ReadFully(this Stream stream)
    {
        if (!stream.CanRead) throw new ArgumentException("This is not a readable stream.");
        var buffer = new byte[32768];
        using (var ms = new MemoryStream())
        {
            while (true)
            {
                var read = stream.Read(buffer, 0, buffer.Length);
                if (read <= 0)
                    return ms.ToArray();
                ms.Write(buffer, 0, read);
            }
        }
    }

    public static void PgpEncrypt(
        this Stream toEncrypt,
        Stream outStream,
        PgpPublicKey encryptionKey,
        bool armor = true,
        bool verify = true,
        CompressionAlgorithmTag compressionAlgorithm = CompressionAlgorithmTag.Zip)
    {
        if (armor) outStream = new ArmoredOutputStream(outStream);
        var compressor = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
        outStream = compressor.Open(outStream);
        var data = toEncrypt.ReadFully();
        var encryptor = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, verify, new SecureRandom());
        encryptor.AddMethod(encryptionKey);
        outStream = encryptor.Open(outStream, data.Length);
        outStream.Write(data, 0, data.Length);
    }

我的测试方法如下:

    private static void EncryptMessage()
    {
        var pubKey = @"<public key text>";

        var clearText = "This is an encrypted message.  There are many like it but this one is cryptic.";
        using (var stream = pubKey.Streamify())
        {
            var key = stream.ImportPublicKey();
            using (var clearStream = clearText.Streamify())
            using (var cryptoStream = new MemoryStream())
            {
                clearStream.PgpEncrypt(cryptoStream,key);
                cryptoStream.Position = 0;
                Console.WriteLine(cryptoStream.Stringify());
                Console.WriteLine("Press any key to continue.");
            }
        }
        Console.ReadKey();
    }

我得到的结果是这样的:

-----BEGIN PGP MESSAGE-----
Version: BCPG C# v1.7.4114.6378


Press any key to continue.

谁能告诉我我做错了什么?

【问题讨论】:

    标签: c# bouncycastle public-key-encryption openpgp


    【解决方案1】:

    好的,我设法让这个工作。这个实现有几个问题。一个问题是某些事情必须按顺序完成。以下是似乎需要发生的事情:

    • 原始数据需要放入PgpLiteralData对象中
    • 文字数据需要加密。
    • 加密数据需要压缩。
    • 需要对压缩数据(可选)进行保护。
    • 需要按使用顺序关闭底层流。

    应该有一种更优雅的方法来做到这一点,但是 BouncyCastle 库使用的流都是单向的,令人沮丧,而且在某些情况下,我需要将流转换为字节数组以使另一部分工作.我包括我使用和独立验证的代码;如果有人有更好的方法来做到这一点,我会很感兴趣。

    public static class OpenPgpUtility
    {
        public static void ExportKeyPair(
            Stream secretOut,
            Stream publicOut,
            AsymmetricKeyParameter publicKey,
            AsymmetricKeyParameter privateKey,
            string identity,
            char[] passPhrase,
            bool armor)
        {
            if (armor)
            {
                secretOut = new ArmoredOutputStream(secretOut);
            }
    
            var secretKey = new PgpSecretKey(
                PgpSignature.DefaultCertification,
                PublicKeyAlgorithmTag.RsaGeneral,
                publicKey,
                privateKey,
                DateTime.UtcNow,
                identity,
                SymmetricKeyAlgorithmTag.Cast5,
                passPhrase,
                null,
                null,
                new SecureRandom()
                );
    
            secretKey.Encode(secretOut);
    
            if (armor)
            {
                secretOut.Close();
                publicOut = new ArmoredOutputStream(publicOut);
            }
    
            var key = secretKey.PublicKey;
    
            key.Encode(publicOut);
    
            if (armor)
            {
                publicOut.Close();
            }
        }
    
        public static PgpPublicKey ImportPublicKey(
            this Stream publicIn)
        {
            var pubRings =
                new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(publicIn)).GetKeyRings().OfType<PgpPublicKeyRing>();
            var pubKeys = pubRings.SelectMany(x => x.GetPublicKeys().OfType<PgpPublicKey>());
            var pubKey = pubKeys.FirstOrDefault();
            return pubKey;
        }
    
        public static PgpSecretKey ImportSecretKey(
            this Stream secretIn)
        {
            var secRings =
                new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(secretIn)).GetKeyRings().OfType<PgpSecretKeyRing>();
            var secKeys = secRings.SelectMany(x => x.GetSecretKeys().OfType<PgpSecretKey>());
            var secKey = secKeys.FirstOrDefault();
            return secKey;
        }
    
        public static Stream Streamify(this string theString, Encoding encoding = null)
        {
            encoding = encoding ?? Encoding.UTF8;
            var stream = new MemoryStream(encoding.GetBytes(theString));
            return stream;
        }
    
        public static string Stringify(this Stream theStream,
                                       Encoding encoding = null)
        {
            encoding = encoding ?? Encoding.UTF8;
            using (var reader = new StreamReader(theStream, encoding))
            {
                return reader.ReadToEnd();
            }
        }
    
        public static byte[] ReadFully(this Stream stream, int position = 0)
        {
            if (!stream.CanRead) throw new ArgumentException("This is not a readable stream.");
            if (stream.CanSeek) stream.Position = 0;
            var buffer = new byte[32768];
            using (var ms = new MemoryStream())
            {
                while (true)
                {
                    var read = stream.Read(buffer, 0, buffer.Length);
                    if (read <= 0)
                        return ms.ToArray();
                    ms.Write(buffer, 0, read);
                }
            }
        }
    
        public static void PgpEncrypt(
            this Stream toEncrypt,
            Stream outStream,
            PgpPublicKey encryptionKey,
            bool armor = true,
            bool verify = false,
            CompressionAlgorithmTag compressionAlgorithm = CompressionAlgorithmTag.Zip)
        {
            var encryptor = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, verify, new SecureRandom());
            var literalizer = new PgpLiteralDataGenerator();
            var compressor = new PgpCompressedDataGenerator(compressionAlgorithm);
            encryptor.AddMethod(encryptionKey);
    
            //it would be nice if these streams were read/write, and supported seeking.  Since they are not,
            //we need to shunt the data to a read/write stream so that we can control the flow of data as
            //we go.
            using (var stream = new MemoryStream()) // this is the read/write stream
            using (var armoredStream = armor ? new ArmoredOutputStream(stream) : stream as Stream)
            using (var compressedStream = compressor.Open(armoredStream))
            {
                //data is encrypted first, then compressed, but because of the one-way nature of these streams,
                //other "interim" streams are required.  The raw data is encapsulated in a "Literal" PGP object.
                var rawData = toEncrypt.ReadFully();
                var buffer = new byte[1024];
                using (var literalOut = new MemoryStream())
                using (var literalStream = literalizer.Open(literalOut, 'b', "STREAM", DateTime.UtcNow, buffer))
                {
                    literalStream.Write(rawData, 0, rawData.Length);
                    literalStream.Close();
                    var literalData = literalOut.ReadFully();
    
                    //The literal data object is then encrypted, which flows into the compressing stream and
                    //(optionally) into the ASCII armoring stream.
                    using (var encryptedStream = encryptor.Open(compressedStream, literalData.Length))
                    {
                        encryptedStream.Write(literalData, 0, literalData.Length);
                        encryptedStream.Close();
                        compressedStream.Close();
                        armoredStream.Close();
    
                        //the stream processes are now complete, and our read/write stream is now populated with 
                        //encrypted data.  Convert the stream to a byte array and write to the out stream.
                        stream.Position = 0;
                        var data = stream.ReadFully();
                        outStream.Write(data, 0, data.Length);
                    }
                }
            }
        }
    }
    

    我的测试方法如下所示:

        private static void EncryptMessage()
        {
            var pubKey = @"<public key text here>";
    
            var clearText = @"<message text here>";
            using (var stream = pubKey.Streamify())
            {
                var key = stream.ImportPublicKey();
                using (var clearStream = clearText.Streamify())
                using (var cryptoStream = new MemoryStream())
                {
                    clearStream.PgpEncrypt(cryptoStream, key);
                    cryptoStream.Position = 0;
                    var cryptoString = cryptoStream.Stringify();
                    Console.WriteLine(cryptoString);
                    Console.WriteLine("Press any key to continue.");
                }
            }
            Console.ReadKey();
        }
    

    由于有人问,我的解密算法是这样的:

    public static Stream PgpDecrypt(
        this Stream encryptedData,
        string armoredPrivateKey,
        string privateKeyPassword,
        Encoding armorEncoding = null)
    {
        armorEncoding = armorEncoding ?? Encoding.UTF8;
        var stream = PgpUtilities.GetDecoderStream(encryptedData);
        var layeredStreams = new List<Stream> { stream }; //this is to clean up/ dispose of any layered streams.
        var dataObjectFactory = new PgpObjectFactory(stream);
        var dataObject = dataObjectFactory.NextPgpObject();
        Dictionary<long, PgpSecretKey> secretKeys;
    
        using (var privateKeyStream = armoredPrivateKey.Streamify(armorEncoding))
        {
            var secRings =
                new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(privateKeyStream)).GetKeyRings()
                                                                                           .OfType<PgpSecretKeyRing>();
            var pgpSecretKeyRings = secRings as PgpSecretKeyRing[] ?? secRings.ToArray();
            if (!pgpSecretKeyRings.Any()) throw new ArgumentException("No secret keys found.");
            secretKeys = pgpSecretKeyRings.SelectMany(x => x.GetSecretKeys().OfType<PgpSecretKey>())
                                          .ToDictionary(key => key.KeyId, value => value);
        }
    
        while (!(dataObject is PgpLiteralData) && dataObject != null)
        {
            try
            {
                var compressedData = dataObject as PgpCompressedData;
                var listedData = dataObject as PgpEncryptedDataList;
    
                //strip away the compression stream
                if (compressedData != null)
                {
                    stream = compressedData.GetDataStream();
                    layeredStreams.Add(stream);
                    dataObjectFactory = new PgpObjectFactory(stream);
                }
    
                //strip the PgpEncryptedDataList
                if (listedData != null)
                {
                    var encryptedDataList = listedData.GetEncryptedDataObjects()
                                                      .OfType<PgpPublicKeyEncryptedData>().First();
                    var decryptionKey = secretKeys[encryptedDataList.KeyId]
                        .ExtractPrivateKey(privateKeyPassword.ToCharArray());
                    stream = encryptedDataList.GetDataStream(decryptionKey);
                    layeredStreams.Add(stream);
                    dataObjectFactory = new PgpObjectFactory(stream);
                }
    
                dataObject = dataObjectFactory.NextPgpObject();
            }
            catch (Exception ex)
            {
                //Log exception here.
                throw new PgpException("Failed to strip encapsulating streams.", ex);
            }
        }
    
        foreach (var layeredStream in layeredStreams)
        {
            layeredStream.Close();
            layeredStream.Dispose();
        }
    
        if (dataObject == null) return null;
    
        var literalData = (PgpLiteralData)dataObject;
        var ms = new MemoryStream();
        using (var clearData = literalData.GetInputStream())
        {
            Streams.PipeAll(clearData, ms);
        }
        ms.Position = 0;
        return ms;
    }
    

    【讨论】:

    • 我在看 Bouncy castle 时偶然发现了这篇文章,我的一个问题是有没有办法从加密字符串中删除 PGP 标记?
    • @katie77 我相信如果你正确打开它(使用分层流),标记会被正确剥离。
    • 谢谢,这是我发现的唯一真正适用于 PGP 加密/解密的代码。
    • @ajeh 哈哈,你问我 4 年前写的东西,所以我不再 100% 确定,但我记得我有同样的感觉并尝试压缩,然后加密,没有成功。有了这个库的绝对残暴的文档,我主要关心的是让这个工作......而且这个工作。 YMMV,请随时进行实验并发布结果。我并不是说这是最好的方法,只是对我有用的唯一方法。
    • 古老的帖子,但是....正如其他人指出的那样,在加密后进行压缩是没有意义的。这只会导致输出文件增长而不是缩小。至于一个有效的样本,有这个(它确实压缩然后加密):stackoverflow.com/questions/10209291/pgp-encrypt-and-decrypt 请注意,它仍然使用 MemoryStream,这对可以加密的文件大小施加了实际限制。不幸的是,Bouncy 库似乎不支持在不使用临时文件的情况下避免 MemoryStream 的压缩/加密解决方案。
    猜你喜欢
    • 2016-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-22
    • 1970-01-01
    • 2021-03-22
    相关资源
    最近更新 更多