I have a part of python code that I want to use in my C# project, but I can't find a right way to achieve it.
python code:
def getCiphertext(plaintext, key = key_, cfb_iv = iv_, size = 128):
message = plaintext.encode('utf-8')
cfb_cipher_encrypt = AES.new(key, AES.MODE_CFB, cfb_iv, segment_size = size)
mid = cfb_cipher_encrypt.encrypt(message)
return hexlify(mid).decode()
I have tried the C# code below, but the result is different:
using System.Security.Cryptography;
public static string AesEncrypt(string str, string key, string IVString)
{
Encoding encoder = Encoding.UTF8;
byte[] toEncryptArray = Encoding.UTF8.GetBytes(str);
RijndaelManaged rm = new RijndaelManaged
{
Key = encoder.GetBytes(key),
Mode = CipherMode.CFB,
BlockSize = 128,
Padding = PaddingMode.PKCS7,
IV = encoder.GetBytes(IVString),
};
ICryptoTransform cTransform = rm.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return ToBCDStringLower(resultArray);//result
}
public static string ToBCDStringLower(byte[] buffer)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < buffer.Length; i++)
{
sb.Append(buffer[i].ToString("x2"));
}
return sb.ToString();
}
Thanks guys all!
CFB in .NET is problematic. In .NET Framework it is supported, in .NET Core only from .NET 5.0.
In addition, .NET Framework and .NET Core allow different segment sizes, but both support 8-bit and 128-bit, which corresponds to the most common variants, namely CFB8 and CFB128 (or full block CFB). The segment size is an additional parameter in CFB which corresponds to the bits encrypted per encryption step, see CFB.
Another peculiarity is that in .NET the plaintext size must be an integer multiple of the segment size. This is remarkable (actually already a bug), since CFB is a stream cipher mode that does not require padding.
Therefore, for CFB, with the exception of CFB8, padding is generally required. In the case of CFB128 to the full block, allowing the default padding PKCS7 to be applied.
Thus, to obtain the ciphertext that corresponds to the unpadded plaintext, the ciphertext must be truncated to the plaintext size.
In the posted Python code, the segment size in the argument list defaults to 128 bits (the PyCryptodome default is 8 bits). In the C# code, the segment size (here denoted as FeedbackSize
) is not specified, so the default value of 128 bits is used.
Thus, unless a segment size other than 128 bits is explicitly specified in the Python code, both codes apply the same segment size.
Also, in the C# code, padding (PKCS7) is done as required by the C# implementation. Therefore, when the ciphertext of the C# code is truncated to the plaintext size, it matches the ciphertext of the Python code.
The following example uses the code you posted unchanged:
string plaintext = "The quick brown fox jumps over the lazy dog";
string key = "01234567890123456789012345678901";
string cfb_iv = "0123456789012345";
string ciphertext = AesEncrypt(plaintext, key, cfb_iv);
string ciphertextTrunc = ciphertext.Substring(0, plaintext.Length * 2); // *2 since AesEncryptOP returns the ciphertext hex encoded
Console.WriteLine(ciphertext);
Console.WriteLine(ciphertextTrunc);
Output:
09f1e464983a7d25305d5b865386e477d97b34b9a6365372ef83b78e495692489c1848a124345eb808eb66d268c6d1ad
09f1e464983a7d25305d5b865386e477d97b34b9a6365372ef83b78e495692489c1848a124345eb808eb66
As you can verify, the shortened ciphertext corresponds to the output of the Python code.
Note that as explained in the 1st section, padding is required for CFB128. Changing the padding to PaddingMode.None
will result in a CryptographicException: The input data is not a complete block. However, with CFB8 this would be possible.
An alternative to the .NET built-in implementation is BouncyCastle, which implements CFB as stream cipher mode so that no padding is needed. The following code:
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
...
public static string Encrypt(string str, string keyString, string IVString)
{
byte[] inputBytes = Encoding.UTF8.GetBytes(str);
byte[] IV = Encoding.UTF8.GetBytes(IVString);
byte[] key = Encoding.UTF8.GetBytes(keyString);
AesEngine engine = new AesEngine();
CfbBlockCipher blockCipher = new CfbBlockCipher(engine, 128);
BufferedBlockCipher cipher = new BufferedBlockCipher(blockCipher);
KeyParameter keyParam = new KeyParameter(key);
ParametersWithIV keyParamWithIv = new ParametersWithIV(keyParam, IV);
cipher.Init(true, keyParamWithIv);
byte[] outputBytes = new byte[cipher.GetOutputSize(inputBytes.Length)];
int length = cipher.ProcessBytes(inputBytes, outputBytes, 0);
cipher.DoFinal(outputBytes, length);
string encryptedInput = ToBCDStringLower(outputBytes);
return encryptedInput;
}
directly (i.e. without truncation) returns the result of the Python code.