【问题标题】:Python AES encryption furnishing different result from originating C# codePython AES 加密提供与原始 C# 代码不同的结果
【发布时间】:2021-04-04 13:36:19
【问题描述】:

C# 代码使用 AES 加密字节数组。

我使用 PyCryptodome 编写了一个 Python 程序来做同样的事情,但是当我使用 C# 代码时,加密的字节总是与结果不同,我确保:

  • 将两者的 IV 设置为相同的值(仅用于测试目的)
  • 确保两者中的密钥相同
  • 确保原始数据相同

我正在加密的内容:一个字节数组。这些字节主要表示 TLD 格式的数据。

Python 程序将成为实用程序的一部分,该实用程序将动态生成流,并由用 C# 编写的 Web 应用程序处理。

使用http://aes.online-domain-tools.com,我实际上可以解密 C# 代码生成的字节,并验证它使用的是 AES,并且原始数据是正确的。

问题是:还有什么可能是鉴别因素?

Python sn-ps:

      from Crypto.Cipher import AES
      from Crypto import Random
      from Crypto.Util.Padding import pad

**** Correction ***
        aes_cipher = AES.new(bytes(key, 'UTF-8'), AES.MODE_CBC)
#
# Correct, the above call would use a random IV value.
# For debugging & learning purpose, I halted this in the 
# debugger and manually set 
# aes_cipher.IV = <a given value>
# and used the same IV in the C# code to try and keep all known inputs identical.
# 
        aes_cipher.block_size = 128
        aes_cipher.key_size = 128   # bits
        encrypted_pack = aes_cipher.encrypt(pad(pack, 16))

        # Tack on to the beginning the 16 bytes of the "IV"

        # FYI - the C# decryption function strips off the first 16 IV bytes
        encrypted_pack = aes_cipher.IV + encrypted_pack

        return encrypted_pack

C# sn-ps

            AesCipher = new RijndaelManaged();
            AesCipher.KeySize = 128;  // 192, 256

            // BlockSize: 128-bit == 16 bytes. 
            // 128-bit is the default for RijndaelManaged
            AesCipher.BlockSize = 128;

            AesCipher.Mode = CipherMode.CBC;
            AesCipher.Padding = PaddingMode.Zeros;

...
...

# 
# Yes, GenerateIV() generates a random IV.
# As mentioned above, I overrode this by setting 
# AesCipher.IV = <the same value as above>
# 

                AesCipher.GenerateIV();                
                setKey(key);   // converts a string of decimal digits to string of hex digits  

                ICryptoTransform transform = AesCipher.CreateEncryptor();
                byte[] encrypted = transform.TransformFinalBlock(buf, 0, buf.Length);
                byte[] result = new byte[encrypted.Length + 16];

                Buffer.BlockCopy(AesCipher.IV, 0, result, 0, 16);
                Buffer.BlockCopy(encrypted, 0, result, 16, encrypted.Length);

                return result;


***
Update
***
There was another problem that I just discovered and fixed.
The key was being saved as a 32-byte rather than 16-byte bytearray, which would explain the gigantic discrepancy from online tool results.

Solved easiy with 
```byte_key = binascii.unhexlify(key)
Once I did that, the returned by both pieces of code matched, and they matched what was in the online tool, too.
Sneaky because in the debugger, it's easy to miss because the values look the same.

【问题讨论】:

  • AesCipher.GenerateIV() 正在生成一个随机 IV,如果我没记错的话。这不同于 在两者中将 IV 设置为相同的值(仅用于测试目的)
  • 我不太确定aes_cipher.encrypt(pad(pack, 16)) 是什么,但听起来好像是在填充数据然后对其进行加密。我不是 Python 程序员,所以我可能是错的,但听起来就是这样。另一方面,您的 C# 代码肯定会加密数据,然后将其复制到结果数组中的偏移位置。
  • 与问题中的描述相反,Python 代码中也使用了随机 IV,因为在实例化 AES 对象 s 时没有在第三个参数中传递 IV。 documentation.
  • @John - 我知道如果我不填充输入,那么我会得到一个异常“数据必须在 CBC 模式下填充到 128 字节边界”
  • 您的密钥在 sn-ps 中的解码方式也可能不同,但您没有在此处显示该代码。再次检查。此外,如果您明确传递密钥,则不应设置密钥大小。将使用该密钥。如果您设置了密钥大小,那么库很可能会出现异常行为并为您生成新密钥。

标签: python c# encryption aes pycryptodome


【解决方案1】:

AesCipher.GenerateIV() 正在生成一个随机 IV,如果我没记错的话。这不同于

将两者的 IV 设置为相同的值(仅用于测试目的)

Crypto.Util.Padding.pad的默认填充是

style (string) – 填充算法。它可以是‘pkcs7’(默认)、‘iso7816’或‘x923’。

不同于:

AesCipher.Padding = PaddingMode.Zeros;

完整的 C# 和 Python 示例:

public static byte[] SimpleEncryptAesVariableLengthCbcZeros(string key, byte[] iv, byte[] plain)
{
    byte[] key2 = Encoding.UTF8.GetBytes(key);

    if (key.Length == 0 || key.Length > 32)
    {
        throw new ApplicationException("Illegal length for key");
    }

    int keySize = key2.Length <= 16 ? 128 : key2.Length <= 24 ? 192 : 256;

    using (var aesCipher = new RijndaelManaged())
    {
        aesCipher.KeySize = keySize;

        // BlockSize: 128-bit == 16 bytes. 
        // 128-bit is the default for RijndaelManaged
        aesCipher.BlockSize = 128;

        aesCipher.Mode = CipherMode.CBC;
        aesCipher.Padding = PaddingMode.Zeros;

        if (iv == null)
        {
            // IV as calculated by http://aes.online-domain-tools.com/
            // SHA1(key) truncated to 16 bytes
            iv = SHA1.HashData(key2);
            Array.Resize(ref iv, aesCipher.BlockSize / 8);
        }
        else if (iv.Length != aesCipher.BlockSize / 8)
        {
            throw new ApplicationException("Illegal length for IV");
        }

        aesCipher.IV = iv;

        // Key is padded with bytes set to 0
        Array.Resize(ref key2, aesCipher.KeySize / 8);
        aesCipher.Key = key2;

        using (var encryptor = aesCipher.CreateEncryptor())
        {
            var encrypted = encryptor.TransformFinalBlock(plain, 0, plain.Length);

            var iv_encrypted = new byte[iv.Length + encrypted.Length];
            Array.Copy(iv, 0, iv_encrypted, 0, iv.Length);
            Array.Copy(encrypted, 0, iv_encrypted, iv.Length, encrypted.Length);
            return iv_encrypted;
        }
    }
}

// https://stackoverflow.com/a/311179/613130
public static byte[] StringToByteArray(string hex)
{
    hex = hex.Replace(" ", string.Empty);

    byte[] bytes = new byte[hex.Length / 2];

    for (int i = 0; i < hex.Length; i += 2)
    {
        bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
    }

    return bytes;
}

public static string ByteArrayToString(byte[] bytes, string join)
{
    string res = string.Join(join, Array.ConvertAll(bytes, x => x.ToString("x2")));
    return res;
}


static void Main(string[] args)
{
    string key = "abcdefghabcdefghabcdefghabcdefgh";
    byte[] plain = StringToByteArray("0000000000000000000000000000000001");
    var res = SimpleEncryptAesVariableLengthCbcZeros(key, null, plain);
    var res2 = ByteArrayToString(res, " ");
    Console.WriteLine(res2);
}

并且(请注意,这可能是我一生中第二次或第三次编写 Python,所以我不太确定它的质量,而且肯定没有优化):

from Crypto.Cipher import AES
from Crypto import Random
import hashlib 
#from Crypto.Util.Padding import pad

#key must be string
#iv must be bytes or None
#plain must be bytes
def SimpleEncryptAesVariableLengthCbcZeros(key, iv, plain):
    key2 = bytes(key, 'UTF-8')
    
    if len(key2) == 0 or len(key2) > 32:
        raise Exception('Illegal length for key')

    keySize = 128 if len(key2) <= 16 else 192 if len(key2) <= 24 else 256

    if iv == None:
        #IV as calculated by http://aes.online-domain-tools.com/
        #SHA1(key) truncated to 16 bytes
        h = hashlib.sha1()
        h.update(key2)

        iv = h.digest()
        iv = iv[0:16]
    elif len(iv) != 128 // 8:
        raise Exception('Illegal length for iv')

    #Key is padded with bytes set to 0
    key2 = key2 + b'\0' * (keySize // 8 - len(key2))

    aes_cipher = AES.new(key2, AES.MODE_CBC, iv)

    aes_cipher.key_size = keySize
    aes_cipher.block_size = 128

    padded = plain
    
    #zero padding
    if len(padded) % 16 != 0:
        padded = padded + b'\0' * (16 - len(padded) % 16)

    encrypted = aes_cipher.encrypt(bytes(padded))

    iv_encrypted = iv + encrypted

    return iv_encrypted

key = 'abcdefghabcdefghabcdefghabcdefgh'
plain = bytearray.fromhex('0000000000000000000000000000000001')
iv_encrypted = SimpleEncryptAesVariableLengthCbcZeros(key, None, plain)
print(' '.join(["{:02x}".format(x) for x in iv_encrypted]))

【讨论】:

  • 你是正确的,生成随机 IV。
  • 我将在问题中添加一个说明,即即使我单步执行代码并使用特定值覆盖 IV(与 C# 代码中使用的相同),最终结果也会有所不同。
  • @Guy 你修复了填充吗?要检查它是否是填充,请尝试加密至少 32 字节的块。你在加密什么?文本?如果是,加密文本是否以相同的方式编码? (UTF8 或 Unicode)
  • @xanatos - 你能解释一下你的建议是什么意思吗?
  • @Guy AesCipher.Padding = PaddingMode.PKCS7
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多