dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.96k stars 4.65k forks source link

AES CFB 128 gives wrong results #85205

Open beta-tester opened 1 year ago

beta-tester commented 1 year ago

Description

hi, there is an issue with encrypting / decrypting AES CFB 128. (System.Security.Cryptography.Aes, CipherMode.CFB, KeySize = 128) on an ESP32 micro controller i am using the MBEDTLS library to encrypt / decrypt data in AES CFB 128. i can encrypt / decrypt the same data on a PC by using python3. but when i try to encrypt / decrypt the data on a PC by using .NET i get corrupt data.

i am using VisualStudio 2022 Community .NET 6.0 / .NET 7.0

in the test below, i get the following hex dump for encryption:

Reproduction Steps

i am using the following code snips to reproduce the issue:

Expected behavior

the expected behavior is having same result under C# / Python / MBEDTLS:

Actual behavior

actual behavior in C# is having different result compared to Python and MBEDTLS results:

Regression?

No response

Known Workarounds

No response

Configuration

Other information

No response

ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones See info in area-owners.md if you want to be subscribed.

Issue Details
### Description hi, there is an issue with encrypting / decrypting AES CFB 128 cyper. on an ESP32 micro controller i am using the `MBEDTLS` library to encrypt / decrypt data in AES CFB 128. i can encrypt / decrypt the same data on a PC by using python3. but when i try to encrypt / decrypt the data on a PC by using .NET i get corrupt data. i am using VisualStudio 2022 Community .NET 6.0 / .NET 7.0 in the test below, i get the folowing hex dump for encryption: - hex dump: `0x66, 0x16, 0xF9, 0x2E, 0x42, 0xA8,`, C#. - hex dump: `0x66, 0xE9, 0x4B, 0xD4, 0xEF, 0x8A,`, Python. - hex dump: `0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a,`, MBEDTLS (ESP32 / Arduino). ### Reproduction Steps - MBEDTLS (ESP32 / Arduino): hex dump: `0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a,` ```C #include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "mbedtls/aes.h" void test() { int error = 0; uint8_t key[16]; uint8_t iv[16]; uint8_t text[6]; memset(key, 0, sizeof(key)); memset(iv, 0, sizeof(iv)); memset(text, 0, sizeof(text)); uint8_t* encrypted = malloc(sizeof(text)); mbedtls_aes_context ctx; mbedtls_aes_init(&ctx); if ((error = mbedtls_aes_setkey_enc(&ctx, key, 128)) != 0) goto cleanup; size_t iv_off = 0; if ((error = mbedtls_aes_crypt_cfb128(&ctx, MBEDTLS_AES_ENCRYPT, sizeof(text), &iv_off, iv, text, encrypted)) != 0) goto cleanup; // dump for (int i = 0; i < sizeof(text); i++) printf("0x%02x, ", *(((const char *)encrypted) + i)); printf("\n"); cleanup: mbedtls_aes_free(&ctx); free(encrypted); ``` - Python3.11 (PC / Linux, Windows) hex dump: `0x66, 0xE9, 0x4B, 0xD4, 0xEF, 0x8A,` ```Python from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend def test(): key = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' iv = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' text = '\x00\x00\x00\x00\x00\x00' print(f"in: {text.encode('ascii')}") cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend()) encryptor = cipher.encryptor() encrypted = encryptor.update(text.encode('ASCII')) + encryptor.finalize() # dump hex_dump = "" for i, b in enumerate(encrypted): hex_dump += f"0x{b:02X}, " print(hex_dump) ``` - C#: hex dump: `0x66, 0x16, 0xF9, 0x2E, 0x42, 0xA8,` ```C# using System.Security.Cryptography; using System.IO.Compression; using System.IO; using System.Text; void test() { var key = new byte[16]; var iv = new byte[16]; var text = new char[6]; byte[] encrypted; using (var crypto = Aes.Create()) { crypto.Mode = CipherMode.CFB; crypto.Padding = PaddingMode.None; crypto.Key = key; crypto.IV = iv; using (var ct = crypto.CreateEncryptor()) { encrypted = ct.TransformFinalBlock(Encoding.ASCII.GetBytes(text), 0, text.Length); } } for (int i = 0; i < encrypted.Length; i++) Console.Write($"0x{encrypted[i]:X02}, "); Console.WriteLine(); } ``` ### Expected behavior the expected behavior is having same result under C# / Python / MBEDTLS: - hex dump: `0x66, 0xE9, 0x4B, 0xD4, 0xEF, 0x8A,`, C#. - hex dump: `0x66, 0xE9, 0x4B, 0xD4, 0xEF, 0x8A,`, Python. - hex dump: `0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a,`, MBEDTLS (ESP32 / Arduino). ### Actual behavior actual behavior in C# is having different result compared to Python and MBEDTLS results: - hex dump: `0x66, 0x16, 0xF9, 0x2E, 0x42, 0xA8,`, C#. - hex dump: `0x66, 0xE9, 0x4B, 0xD4, 0xEF, 0x8A,`, Python. - hex dump: `0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a,`, MBEDTLS (ESP32 / Arduino). ### Regression? _No response_ ### Known Workarounds _No response_ ### Configuration - .NET 6 / .NET 7 Core Console App - Windows 10 - VisualStudio 2022 Community - PC x64 ### Other information _No response_
Author: beta-tester
Assignees: -
Labels: `area-System.Security`
Milestone: -
vcsjones commented 1 year ago

.NET defaults to a feedback size of 8, not 128, for AES. .NET also requires you to pad to the feedback size, but you can work around that with padding and just add it and remove as needed. Here is an example:

using System.Security.Cryptography;

test();

void test()
{
    var key = new byte[16];
    var iv = new byte[16];
    var text = new char[6];
    byte[] encrypted, decrypted;
    using (var crypto = Aes.Create())
    {
        crypto.Key = key;
        encrypted = crypto.EncryptCfbTruncated(iv, System.Text.Encoding.ASCII.GetBytes(text));
        decrypted = crypto.DecryptCfbTruncated(iv, encrypted);
    }

    // dump
    for (int i = 0; i < encrypted.Length; i++)
        Console.Write($"0x{encrypted[i]:X02}, ");

    Console.WriteLine();

    for (int i = 0; i < decrypted.Length; i++)
        Console.Write($"0x{decrypted[i]:X02}, ");
    Console.WriteLine();
}

static class Extensions
{
    public static byte[] EncryptCfbTruncated(this Aes aes, byte[] iv, byte[] data)
    {
        byte[] result = aes.EncryptCfb(data, iv, PaddingMode.Zeros, feedbackSizeInBits: 128);
        return result[0..data.Length];
    }

    public static byte[] DecryptCfbTruncated(this Aes aes, byte[] iv, byte[] data)
    {
        int size = aes.GetCiphertextLengthCfb(data.Length, PaddingMode.Zeros, feedbackSizeInBits: 128);
        byte[] tempBuffer = new byte[size];
        data.AsSpan().CopyTo(tempBuffer);
        byte[] decrypted = aes.DecryptCfb(tempBuffer, iv, PaddingMode.Zeros, feedbackSizeInBits: 128);
        return decrypted[0..data.Length];
    }
}

That roundtrips the data and outputs what the other platforms output.

beta-tester commented 1 year ago

thank you for the quick workaround.

i never expected that CFB needs padding and truncating to get the same behavior as the other language implementations gives. looks odd to me.

beta-tester commented 1 year ago

i think i have an issue with the workaround. as far as i understand the methods EncryptCfb() and DecryptCfb() they can only process the complete data in one pass. is there a possibility to Read/Write small chunks of data and decrypt/encrypt them and continue/update the decryption/encryption of further chunks like the update in the other language implementations (like a big data stream)?

vcsjones commented 1 year ago

they can only process the complete data in one pass. is there a possibility to Read/Write small chunks of data and decrypt/encrypt them and continue/update the decryption/encryption of further chunks like the update in the other language implementations (like a big data stream)?

Indeed, those are the "one shots". For incremental work, your initial example of using CreateTransfom + TransformBlock + TransformFinalBlock would do the trick.

beta-tester commented 1 year ago

For incremental work, your initial example of using CreateTransfom + TransformBlock + TransformFinalBlock would do the trick.

Uhh, that job is way more complicated than expected in C#. specially for me who is just using the Aes library and has no deep background what is going on under the hood.

the MCU encrypts data in an stacked two pass aes_cfb_128 method, where in between the two passes the encrypted data is XOR'ed depending on small chunks of previous encrypted data of the first pass. (i think it is done to obscure the weaker aes_cfb_128 method - but i have to use it as is to be compatible with other units of the same kind)

now i have to keep track of the padding / truncating to be able to decrypt the two stacked encryption passes with the embedded picking the small chunks of XOR dependencies to undo the XOR'ing in between. the small chunks are not aligned to the padding needed by the C# implementation. the data stream also isn't aligned/padded.

it is so much easier in MBEDTLS and Python to do so without the need of keep track of padding or truncating the encrypted data.

is there a plan to get rid of the need of manually padding/truncating for the user of the Aes CFB library? or are there other libraries available for C# .NET >=6.0 those are more like the other language libraries?