Дополнительные нулевые символы при расшифровке AES-CBC-PKCS7 с BouncyCastle - PullRequest
3 голосов
/ 14 мая 2019

Мне нужно реализовать шифрование AES в 2 разных проектах, но один должен использовать стандартные криптографические библиотеки .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;
    }
}

BouncyCastle:

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 вместо открытого текста.

Любой совет / комментарий будет принят с благодарностью.

Ответы [ 2 ]

3 голосов
/ 14 мая 2019

Надувной замок может только угадать размер выходного сообщения в виде открытого текста заранее во время вызова на GetOutputSize. Он не может знать, сколько байтов заполнения используется, потому что они доступны только после расшифровки . Таким образом, они должны были бы частично расшифровать зашифрованный текст, чтобы узнать количество заполнения, и это зашло слишком далеко. Таким образом, вы получите только приблизительную оценку, так что максимальное количество байтов все еще может поместиться в ваш только что созданный буфер.

Вам понадобится возвращаемое значение ProcessBytes и DoFinal, чтобы увидеть фактическое количество байтов, которые расшифровываются из зашифрованного текста (во входном буфере и внутренний буфер), когда методы называются. DoFinal расшифровывает последний блок (ы), а затем удаляет заполнение из последнего блока, поэтому только в это время известен размер (оставшегося) открытого текста.

То, что вы видите в настоящее время как байты с нулевым значением, это просто неиспользуемые байты буфера, так как размер открытого текста меньше значения, возвращаемого GetOutputSize.


Конечно, все это скрыто в потоковом коде примера .NET, где ReadToEnd требуется для выполнения некоторой расширенной буферизации (возможно, с использованием MemoryStream внутри себя).

2 голосов
/ 14 мая 2019

Следуя инструкциям от Мартена Бодевеса, окончательный рабочий код выглядит следующим образом:

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;
}

Обратите внимание, что длина открытого текста теперь рассчитывается с помощью целочисленных выводов методов дешифрования, и простая копия массива может создавать открытый текст без лишних символов.

...