【问题标题】:Extra null characters when decrypting AES-CBC-PKCS7 with BouncyCastle使用 BouncyCastle 解密 AES-CBC-PKCS7 时出现额外的空字符
【发布时间】:2019-10-01 13:06:56
【问题描述】:

我需要在 2 个不同的项目中实现 AES 加密,但一个必须使用 .NET 标准加密库,另一个必须使用 BouncyCastle。两者都是 C# 代码。相关方法如下:

.NET:

internal class NETAesCryptor : IAesCryptor
{
    public Tuple<byte[], byte[]> Encrypt(string plaintext, byte[] key)
    {
        byte[] ciphertext, iv;
        using (var aes_provider = new AesCryptoServiceProvider())
        {
            aes_provider.Padding = PaddingMode.PKCS7;
            aes_provider.GenerateIV();
            iv = aes_provider.IV;
            var encryptor = aes_provider.CreateEncryptor(key, iv);
            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    using (var sw = new StreamWriter(cs))
                    {
                        sw.Write(plaintext);
                    }
                    ciphertext = ms.ToArray();
                }
            }
        }
        var result = new Tuple<byte[], byte[](ciphertext, iv);
        return result;
    }

    public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
    {
        string plaintext;
        using (var aes_provider = new AesCryptoServiceProvider())
        {
            aes_provider.Padding = PaddingMode.PKCS7;
            aes_provider.IV = iv;
            var decryptor = aes_provider.CreateDecryptor(key, iv);
            using (var ms = new MemoryStream(ciphertext))
            {
                using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (var sr = new StreamReader(cs))
                    {
                        plaintext = sr.ReadToEnd();
                    }
                }
            }
        }
        return plaintext;
    }
}

充气城堡:

internal class BCAesCryptor : IAesCryptor
{
    private SecureRandom _r;

    public BCAesCryptor()
    {
        _r = new SecureRandom();
    }

    public Tuple<byte[], byte[]> Encrypt(string plaintext, byte[] key)
    {
        var plaintext_bytes = Encoding.UTF8.GetBytes(plaintext);
        var iv = GenerateRandomBytes(16);

        var engine = new AesEngine();
        var cbc_cipher = new CbcBlockCipher(engine);
        var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
        var key_param = new KeyParameter(key);
        var key_param_with_iv = new ParametersWithIV(key_param, iv);

        cipher.Init(true, key_param_with_iv);
        var ciphertext = new byte[cipher.GetOutputSize(plaintext_bytes.Length)];
        var length = cipher.ProcessBytes(plaintext_bytes, ciphertext, 0);
        cipher.DoFinal(ciphertext, length);

        var result = new Tuple<byte[], byte[]>(ciphertext, iv);
        return result;
    }

    public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
    {
        var engine = new AesEngine();
        var cbc_cipher = new CbcBlockCipher(engine);
        var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
        var key_param = new KeyParameter(key);
        var key_param_with_iv = new ParametersWithIV(key_param, iv);

        cipher.Init(false, key_param_with_iv);
        var plaintext = new byte[cipher.GetOutputSize(ciphertext.Length)];
        var length = cipher.ProcessBytes(ciphertext, plaintext, 0);
        cipher.DoFinal(plaintext, length);

        var result = Encoding.UTF8.GetString(plaintext);
        return result;
    }

    private byte[] GenerateRandomBytes(int length = 16)
    {
        var result = new byte[length];
        _r.NextBytes(result);
        return result;
    }
}

.NET 方法之间的加密/解密工作正常,Bouncycastle 加密/.NET 解密也工作正常。但是由于某种原因,Bouncycastle 解密在明文末尾添加了可变数量的\0 字符,我不知道为什么会出现这种情况。

我正在使用的测试代码:

[TestClass]
public class AesCryptorTests
{
    private byte[] _key;
    private string _plaintext;

    public AesCryptorTests()
    {
        _key = GenerateRandomBytes();
        _plaintext = "Lorem ipsum dolor sit amet";
    }

    [TestMethod]
    public void TestMethod2()
    {
        var bc = new BCAesCryptor();
        var net = new NETAesCryptor();
        var result = net.Encrypt(_plaintext, _key);
        var new_plaintext = bc.Decrypt(result.Ciphertext, result.IV, _key);
        Assert.AreEqual(_plaintext, new_plaintext);
    }

    private byte[] GenerateRandomBytes(int cantidad = 16)
    {
        var result = new byte[cantidad];
        using (var r = new RNGCryptoServiceProvider())
        {
            r.GetBytes(result);
        }
        return result;
    }
}

在之前的测试中,解密返回Lorem ipsum dolor sit amet\0\0\0\0\0\0而不是明文。

任何建议/评论将不胜感激。

【问题讨论】:

    标签: c# encryption aes bouncycastle


    【解决方案1】:

    Bouncy Castle 只能在调用GetOutputSize 的过程中提前猜测明文消息的输出大小。它无法知道使用了多少填充字节,因为只有在解密后才可用。所以他们必须对密文进行部分解密才能知道填充量,这太过分了。因此,您只得到一个偏高的估计值,以便最大字节数仍然可以容纳在新创建的缓冲区中。

    您需要 ProcessBytesDoFinal 的返回值来查看在方法被调用。 DoFinal 解密最后一个块,然后从最后一个块中删除填充,因此只有在那个时候才知道(剩余)明文的大小。

    您当前看到的零值字节只是缓冲区中未使用的字节,因为明文大小小于GetOutputSize 返回的值。


    当然,这一切都隐藏在 .NET 示例的流式代码中,其中需要 ReadToEnd 进行一些高级缓冲(可能在内部使用 MemoryStream)。

    【讨论】:

    • 按照你的解释,它工作得很好。将为遇到相同问题的任何人发布另一个答案的完整代码。此外,您对 .NET 和 Bouncycastle AES 处理之间差异的解释非常有用,并且在我问之前解决了我脑海中的几个问题。谢谢。
    • 现在你知道它为什么叫BufferedBlockCipher了。请注意,Bouncy 的猜测似乎仍然高一个字节。始终应用填充(绝对是在 Bouncy Castle 中),因此它应该返回一个 x 倍于块大小的值 - 1,因为总是至少有一个填充字节 - 经典的一对一:)
    【解决方案2】:

    遵循 Maarten Bodewes 的instructions,最终的工作代码如下:

    public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
    {
        var engine = new AesEngine();
        var cbc_cipher = new CbcBlockCipher(engine);
        var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
        var key_param = new KeyParameter(key);
        var key_param_with_iv = new ParametersWithIV(key_param, iv);
    
        cipher.Init(false, key_param_with_iv);
        var decryption_buffer = new byte[cipher.GetOutputSize(ciphertext.Length)];
        var initial_length = cipher.ProcessBytes(ciphertext, decryption_buffer, 0);
        var last_bytes = cipher.DoFinal(decryption_buffer, initial_length);
        var total_bytes = initial_length + last_bytes;
    
        var plaintext = new byte[total_bytes];
        Array.Copy(decryption_buffer, plaintext, total_bytes);
        var result = Encoding.UTF8.GetString(plaintext);
        return result;
    }
    

    请注意,明文的长度现在是使用解密方法的整数输出来计算的,简单的数组副本能够创建没有额外字符的明文。

    【讨论】:

      猜你喜欢
      • 2015-08-22
      • 2017-10-11
      • 2015-06-24
      • 1970-01-01
      • 2013-08-11
      • 2021-01-04
      • 2021-10-23
      • 2014-08-15
      • 2015-08-04
      相关资源
      最近更新 更多