如何将 python AES 函数转换为 C# 代码

How to turn python AES function into C# code

我有一部分 python 代码想用在我的 C# 项目中,但我找不到正确的方法来实现它。

python代码:

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()

我尝试了下面的 C# 代码,但结果不同:

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

谢谢大家!

.NET 的 CFB 实现:

.NET 中的 CFB 存在问题。在 .NET Framework 中它受支持,在 .NET Core 中仅从 .NET 5.0 开始。

此外,.NET Framework 和.NET Core 允许不同的段大小,但都支持 8 位和 128 位,这对应于最常见的变体,即 CFB8 和 CFB128(或全块 CFB)。段大小是 CFB 中的一个附加参数,它对应于每个加密步骤加密的位,请参阅 CFB

另一个特点是在 .NET 中明文大小必须是段大小的整数倍。这很了不起(实际上已经是一个错误),因为 CFB 是一种不需要填充的流密码模式。
所以对于CFB,除了CFB8,一般都需要padding。在 CFB128 到完整块的情况下,允许应用默认填充 PKCS7。
因此,要得到与未填充的明文对应的密文,必须将密文截断为明文大小。

Python与C#代码的比较:

在发布的 Python 代码中,参数列表中的段大小默认为 128 位(PyCryptodome 默认为 8 位)。在C#代码中,没有指定段大小(这里表示为FeedbackSize),所以使用默认值128位。
因此,除非在 Python 代码中明确指定了 128 位以外的段大小,否则两个代码都应用相同的段大小。
此外,在 C# 代码中,填充 (PKCS7) 是根据 C# 实现的要求完成的。因此,将C#代码的密文截断为明文大小时,匹配Python代码的密文

以下示例使用您发布的代码未更改:

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

输出:

09f1e464983a7d25305d5b865386e477d97b34b9a6365372ef83b78e495692489c1848a124345eb808eb66d268c6d1ad
09f1e464983a7d25305d5b865386e477d97b34b9a6365372ef83b78e495692489c1848a124345eb808eb66

如您所见,缩短的密文对应于 Python 代码的输出。

请注意,如第 1 部分所述,CFB128 需要填充。将填充更改为 PaddingMode.None 将导致 CryptographicException:输入数据不是完整的块。但是,使用 CFB8 这将是可能的。


充气城堡:

.NET 内置实现的替代方法是 BouncyCastle,它将 CFB 实现为流密码模式,因此不需要填充。以下代码:

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

直接(即不截断)returnsPython代码的结果。