当前位置: 首页 > 知识库问答 >
问题:

使用弹跳城堡进行 OpenPGP 加密

鲍飞星
2023-03-14

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

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

共有1个答案

华坚成
2023-03-14

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

  • 需要将原始数据放入PgplitalData对象
  • 文字数据需要加密。
  • 加密数据需要压缩。
  • 压缩数据(可选)需要装甲。
  • 底层流需要按使用顺序关闭。

应该有一种更优雅的方式来做到这一点,但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;
}
 类似资料:
  • 在FIPS模式下,PKCS#12格式在加密文件时必须使用兼容的加密和哈希算法。 当我阅读BC-FIPS文档时,它说在批准模式下,它们不支持PKCS#12,因此这意味着我们无法在BC-FIPS批准模式下读取PKCS12密钥库;我们使用BC-FIPS作为加密提供者,还使用PKCS12和JKS格式存储密钥和证书。 BC-FIPS声称-“PKCS12密钥存储支持以下变体: PKCS12-3DES-3DES

  • 使用PEM证书,如 我的问题是,AFAIK,pemparser中没有密码的位置。 有人能给我一个如何将代码迁移到PEMParser版本的例子吗?

  • 一个多星期以来,我一直在使用它与我的python服务器实现RSA安全通信。 然而,我不能为我的生活找出适当的进口所有的罐子。 我已经包括了在bouncy castles网站上找到的所有罐子,但仍然没有骰子! 我读到他们到处搬家。如果这段代码很旧或已损坏,那么还有什么其他带有pkcs1填充的RSA实现? 编辑: pub key位于名为key的文件中。酒吧如何读入该文件以用作密钥 编辑2:根据答案添加

  • Ladar Levison编写了ecies_encrypt和ecies_decrypt C函数。这些功能与ECIES弹力城堡兼容吗?如果不是,有没有人知道要修复什么或者ecies的另一个C实现?

  • 我的程序生成AES密钥,用rsa算法加密并发送到服务器。但是,当我从服务器获取它并解密时,一些字节被破坏并替换为序列,根据这个问题UTF-8编码和解码问题,我意识到问题是服务器(也解密密钥)将其存储为UTF-8字符串。有没有办法创建“UTF-8友好”键?现在如何生成密钥:

  • 我知道曲线名称()以及EC公钥的和坐标。 如何用它们创建? 我读过https://stackoverflow.com/a/29355749/5453873,但是那里的代码使用而不是,ECPublicKey是中的接口,而不是一个可实例化的类。