10

我一直在尝试通过 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.

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

4

1 回答 1

21

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

  • 原始数据需要放入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;
}
于 2013-09-18T20:51:15.100 回答