AES encryptions/decryptions differs between Python and C#?

Question:

i am using AES encryption/decryption in Python3 using the pip pycryptodome package on Linux (Debian, ubuntu).
now i needed to implement the same program for Windows by using C#.
but the encryption results differs between the two platforms (slightly).

  • AES encryptions from Python (Linux) cannot decrypted on C# (Windows),
    i get an exception because of wrong padding
  • AES encryptions from C# (Windows) cannot always decrypted on Python (Linux).
  • but it is always possible to decrypt encryptions from the same programming language when it was encrypted with the same language.

EDIT: i installed python 3.11 and pip pycryptodome on the windows system and there the test.py parogram behaves the same as on the linux system. but using python there is no option. i have to stick on C# on the windows system.

AES encryptions/decryptions on python:
t:'TEST+++TEST+++TEST' -> e:'dMTh7CVOf/1PkhTEWRsnv68j' -> d:'TEST+++TEST+++TEST'

AES encryptions/decryptions on C#:
t:'TEST+++TEST+++TEST' -> e:'dMTh7CVOf/1PkhTEWRsnv68jdQ==' -> d:'TEST+++TEST+++TEST'

what i am doing wrong here?

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#

import base64

# pip install pycryptodome
from Crypto.Cipher import AES

KEY   = b'x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41'
IV    = b'x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41'
MODE  = AES.MODE_CFB
BLOCK = 128

def en(plain_text):
    crypto = AES.new(key=KEY, mode=MODE, iv=IV)
    crypto.block_size = BLOCK
    b_plain = plain_text.encode('utf-8')
    b_encrypted = crypto.encrypt(b_plain)
    base64_encrypted_text = base64.b64encode(b_encrypted).decode('utf-8')
    return base64_encrypted_text

def de(base64_encrypted_text):
    crypto = AES.new(key=KEY, mode=MODE, iv=IV)
    crypto.block_size = BLOCK
    b_encrypted = base64.b64decode(base64_encrypted_text)
    b_plain = crypto.decrypt(b_encrypted)
    plain_text = b_plain.decode('utf-8')
    return plain_text

t = 'TEST+++TEST+++TEST'
e = en(t)
d = de(e)
print(f"t:'{t}' -> e:'{e}' -> d:'{d}'")
# t:'TEST+++TEST+++TEST' -> e:'dMTh7CVOf/1PkhTEWRsnv68j' -> d:'TEST+++TEST+++TEST'
// C# .NET 6.0 Console
//

using System.Security.Cryptography;

byte[] KEY = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41 };
byte[] IV = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41 };
CipherMode MODE = CipherMode.CFB;
int BLOCK = 128;

string en(string plain_text)
{
    string base64_encrypted_text;
    using (var crypto = Aes.Create())
    {
        crypto.Mode = MODE;
        crypto.BlockSize = BLOCK;
        crypto.Key = KEY;
        crypto.IV = IV;
        using (var s_encrypted = new MemoryStream())
        {
            using (var s_crypto = new CryptoStream(s_encrypted, crypto.CreateEncryptor(KEY, IV), CryptoStreamMode.Write))
            {
                using (var s_plain = new StreamWriter(s_crypto))
                {
                    s_plain.Write(plain_text);
                }
            }
            base64_encrypted_text = Convert.ToBase64String(s_encrypted.ToArray());
        }
    }
    return base64_encrypted_text;
}

string de(string base64_encrypted_text)
{
    string plain_text;
    using (var crypto = Aes.Create())
    {
        crypto.Mode = MODE;
        crypto.BlockSize = BLOCK;
        crypto.Key = KEY;
        crypto.IV = IV;
        var b_encrypted = Convert.FromBase64String(base64_encrypted_text);
        using (var s_encrypted = new MemoryStream(b_encrypted))
        {
            using (var s_crypto = new CryptoStream(s_encrypted, crypto.CreateDecryptor(KEY, IV), CryptoStreamMode.Read))
            {
                using (var s_plain = new StreamReader(s_crypto))
                {
                    plain_text = s_plain.ReadToEnd();
                }
            }
        }
    }
    return plain_text;
}

var t = "TEST+++TEST+++TEST";
var e = en(t);
var d = de(e);
Console.WriteLine($"t:'{t}' -> e:'{e}' -> d:'{d}'");
// t:'TEST+++TEST+++TEST' -> e:'dMTh7CVOf/1PkhTEWRsnv68jdQ==' -> d:'TEST+++TEST+++TEST'
Asked By: beta-tester

||

Answers:

PyCryptoDome correctly implements the CFB(Cipher feedback) mode, where the size of a cipher text is the same as the size of the corresponding plain text and padding is not required. But the System.Security.Cryptography of .NET framework prior to 6.0 does not support encrypting without padding in the CFB mode.

If you are using .NET 6.0 or later, you can disable padding like this(with attribution to ‘beta-tester’).

string en(string plain_text)
{
    string base64_encrypted_text;
    using (var crypto = Aes.Create())
    {
        crypto.Mode = MODE;
        crypto.Padding = PaddingMode.None;
        byte[] ptBytes = Encoding.UTF8.GetBytes(plane_text);
        using (var s_encrypted = new MemoryStream())
        {
            using (var s_crypto = new CryptoStream(s_encrypted, crypto.CreateEncryptor(KEY, IV), CryptoStreamMode.Write))
            {
                s_crypto.Write(ptBytes);
            }
        }
        Byte[] ctBytes = s_encrypted.ToArray();
        base64_encrypted_text = Convert.ToBase64String(ctBytes);
    }
    return base64_encrypted_text;
}

And if you are using .NET prior to 6.0(including Mono), you can truncate the cipher text like this.

using System.Text;
...
string en(string plain_text)
{
    string base64_encrypted_text;
    using (var crypto = Aes.Create())
    {
        crypto.Mode = MODE;
        byte[] ptBytes = Encoding.UTF8.GetBytes(plane_text);
        using (var s_encrypted = new MemoryStream())
        {
            using (var s_crypto = new CryptoStream(s_encrypted, crypto.CreateEncryptor(KEY, IV), CryptoStreamMode.Write))
            {
                s_crypto.Write(ptBytes);
            }
        }
        Byte[] ctBytes = new Byte[ptBytes.Length];
        Buffer.BlockCopy(s_encrypted.ToArray(), 0, ctBytes, 0, ptBytes.Length);
        base64_encrypted_text = Convert.ToBase64String(ctBytes);
    }
    return base64_encrypted_text;
}

And as a side note, you don’t need to set the BlockSize property of a cipher. (For example, the block size of AES algorithm is 128 bit by definition.)

Answered By: relent95

thank you @relent95 for the hint.

now it works with setting the crypto.Padding = PaddingMode.None;.
(it was set to PaddingMode.PKCS7 by default)

BTW.: the order of settings are very important.
e.g.: setting the crypto.Mode will alter other settings.

NOTE: this is only working in .NET 6.0.

in .NET 4.7 or .NET 4.8 it is still not working!!!

// C# .NET 6.0 Console
// C# .NET 6.0 WPF App
//

using System.Security.Cryptography;

byte[] KEY = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41 };
byte[] IV = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41 };
CipherMode MODE = CipherMode.CFB;
PaddingMode PADDING = PaddingMode.None;

string en(string plain_text)
{
    string base64_encrypted_text;
    using (var crypto = Aes.Create())
    {
        crypto.Mode = MODE;
        crypto.Padding = PADDING;
        crypto.Key = KEY;
        crypto.IV = IV;
        using (var s_encrypted = new MemoryStream())
        {
            using (var s_crypto = new CryptoStream(s_encrypted, crypto.CreateEncryptor(KEY, IV), CryptoStreamMode.Write))
            {
                using (var s_plain = new StreamWriter(s_crypto))
                {
                    s_plain.Write(plain_text);
                }
            }
            base64_encrypted_text = Convert.ToBase64String(s_encrypted.ToArray());
        }
    }
    return base64_encrypted_text;
}

string de(string base64_encrypted_text)
{
    string plain_text;
    using (var crypto = Aes.Create())
    {
        crypto.Mode = MODE;
        crypto.Padding = PADDING;
        crypto.Key = KEY;
        crypto.IV = IV;
        var b_encrypted = Convert.FromBase64String(base64_encrypted_text);
        using (var s_encrypted = new MemoryStream(b_encrypted))
        {
            using (var s_crypto = new CryptoStream(s_encrypted, crypto.CreateDecryptor(KEY, IV), CryptoStreamMode.Read))
            {
                using (var s_plain = new StreamReader(s_crypto))
                {
                    plain_text = s_plain.ReadToEnd();
                }
            }
        }
    }
    return plain_text;
}

var t = "TEST+++TEST+++TEST";
var e = en(t);
var d = de(e);
Console.WriteLine($"t:'{t}' -> e:'{e}' -> d:'{d}'");
// t:'TEST+++TEST+++TEST' -> e:'dMTh7CVOf/1PkhTEWRsnv68jdQ==' -> d:'TEST+++TEST+++TEST'
Answered By: beta-tester