使用BouncyCastle进行OpenPGP加密

Jer*_*acs 9 c# bouncycastle openpgp public-key-encryption

我一直试图通过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);
    }
Run Code Online (Sandbox Code Playgroud)

我的测试方法如下所示:

    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();
    }
Run Code Online (Sandbox Code Playgroud)

我得到的结果如下:

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


Press any key to continue.
Run Code Online (Sandbox Code Playgroud)

有人能告诉我我做错了什么吗?

Jer*_*acs 19

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

  • 原始数据需要放入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);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的测试方法看起来像这样:

    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();
    }
Run Code Online (Sandbox Code Playgroud)

有人问,我的解密算法看起来像这样:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • @ b06我做了; 我在我的回答中添加了代码. (2认同)