Python 转 C# AES CBC PKCS7

9

我正在尝试将这段C#代码转换成Python(2.5,GAE)。问题是,每次运行加密(在相同的字符串上)时,Python脚本生成的加密字符串都是不同的。

string Encrypt(string textToEncrypt, string passphrase)
 {
    RijndaelManaged rijndaelCipher = new RijndaelManaged();
    rijndaelCipher.Mode = CipherMode.CBC;
    rijndaelCipher.Padding = PaddingMode.PKCS7;

    rijndaelCipher.KeySize = 128;
    rijndaelCipher.BlockSize = 128;
    byte[] pwdBytes = Encoding.UTF8.GetBytes(passphrase);
    byte[] keyBytes = new byte[16];
    int len = pwdBytes.Length;
    if (len > keyBytes.Length)
    {
        len = keyBytes.Length;
    }
    Array.Copy(pwdBytes, keyBytes, len);
    rijndaelCipher.Key = keyBytes;
    rijndaelCipher.IV = new byte[16];
    ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
    byte[] plainText = Encoding.UTF8.GetBytes(textToEncrypt);
    return Convert.ToBase64String(transform.TransformFinalBlock(plainText, 0, plainText.Length));
}

Python代码:(PKCS7编码器:http://japrogbits.blogspot.com/2011/02/using-encrypted-data-between-python-and.html

from Crypto.Cipher import AES
from pkcs7 import PKCS7Encoder
#declared outside of all functions
key = '####'
mode = AES.MODE_CBC
iv = '\x00' * 16
encryptor = AES.new(key, mode, iv)
encoder = PKCS7Encoder()

def function(self):
 text = self.request.get('passwordTextBox')
 pad_text = encoder.encode(text)
 cipher = encryptor.encrypt(pad_text)
 enc_cipher = base64.b64encode(cipher)

这段代码是C#继承而来的。Python代码必须以相同的方式进行加密和解密,以便C#代码可以正确地解码值。

注意:我对Python不熟悉 :)

编辑:抱歉,应该明确指出有一个函数被调用。

谢谢!


2
你是否了解IV/CBC模式的工作原理?如果每次运行该存根,它应该针对相同的输入产生相同的输出。然而,如果您多次调用encryptor.encrypt并使用相同的输入而不重新初始化encryptor(将其重置为相同的初始状态),则会每次产生不同的输出。 - Foon
我的修改是否改变了你的评论?如果没有,最好的解决方法是什么? - Eonasdan
通过将encryptor = AES.new(key, mode, iv)移入函数来修复。感谢@Foon。 - Eonasdan
4个回答

3

将Python代码修改为:

from Crypto.Cipher import AES
from pkcs7 import PKCS7Encoder
#declared outside of all functions
key = '####'
mode = AES.MODE_CBC
iv = '\x00' * 16
encoder = PKCS7Encoder()

def function(self):
 encryptor = AES.new(key, mode, iv)**
 text = self.request.get('passwordTextBox')
 pad_text = encoder.encode(text)
 cipher = encryptor.encrypt(pad_text)
 enc_cipher = base64.b64encode(cipher)

如果有人通过 Google 找到这个页面


3

你的C#代码无效。

Encrypt函数以string passphrase作为密码,但在这一行中尝试引用它:byte[] pwdBytes = Encoding.UTF8.GetBytes(key);

key更改为passphrase

现在这两个函数对我来说产生了相同的结果:

Python

secret_text = 'The rooster crows at midnight!'
key = 'A16ByteKey......'
mode = AES.MODE_CBC
iv = '\x00' * 16

encoder = PKCS7Encoder()
padded_text = encoder.encode(secret_text)

e = AES.new(key, mode, iv)
cipher_text = e.encrypt(padded_text)

print(base64.b64encode(cipher_text))

# e = AES.new(key, mode, iv)
# cipher_text = e.encrypt(padded_text)
# print(base64.b64encode(cipher_text))

C#(修正了上面提到的拼写错误)

Console.WriteLine(Encrypt("The rooster crows at midnight!", "A16ByteKey......"));

Python结果

XAW5KXVbItrc3WF0xW175UJoiAfonuf+s54w2iEs+7A=

C#结果

XAW5KXVbItrc3WF0xW175UJoiAfonuf+s54w2iEs+7A=

我怀疑你在Python代码中多次重复使用“e”。如果取消Python脚本的最后两行的注释,你将看到输出现在已经不同。但是如果取消最后三行的注释,你将看到输出是相同的。正如Foon所说,这是由于CBC工作方式

CBC(密码块链接)在加密一系列字节块时起作用。第一个块通过将IV与明文的第一个字节结合起来加密(“The rooster…”)。第二个块使用第一个操作的结果而不是IV。

当你第二次调用e.encrypt()(例如取消Python脚本的最后两行的注释)时,它会从上一次停止的地方开始。它在加密第一个块时不再使用IV,而是使用上一个加密块的输出。这就是为什么结果看起来不同的原因。通过取消Python脚本的最后三行的注释,你初始化了一个新的加密器,它将在第一个块中使用IV,导致你获得相同的结果。


你是正确的。我的 C# 代码中应该这样写:byte[] pwdBytes = Encoding.UTF8.GetBytes(passphrase); 已在原文中修正。 - Eonasdan
很好的解释文章,但我已经编写了代码,使用你上面提供的相同代码,仍然得到不同的加密文本。有什么想法吗?我正在运行Visual C# 2010 Express和Python 2.7.2,使用pycrypto 2.3.1。在Linux和Windows(ActiveState Python)上都运行了Python端,结果相同 - 就是无法与C#匹配。 - spidie
在我的情况下,如果secret_key的字符数小于16,则两者都返回相同结果(24个字符)。但是,如果它大于等于16,C#将返回具有44个字符的结果(例如:JC3ysTmIE7p8i9i5Ue4ySRWq45aJrOhp9VTfnrIx87Y=),而Python将返回具有24个字符的结果(例如:JC3ysTmIE7p8i9i5Ue4ySQ==)。这些是相同的字符,但Python缺少了一部分。为什么?(我使用的是Python3) - phileoseda

1
这个奇特的PKCS7编码器不仅仅是一个使用静态长度填充的函数。因此,我用非常少的代码实现了它。
#!/usr/bin/env python

from Crypto.Cipher import AES
import base64

# the block size for the cipher object; must be 16, 24, or 32 for AES
BLOCK_SIZE = 16

# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length.  This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE

# PKCS7 method
PADDING = '\x06'
mode = AES.MODE_CBC
iv = '\x08' * 16 # static vector: dangerous for security. This could be changed periodically
# 

# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING

# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)



def CryptIt(password, secret):
    cipher = AES.new(secret, mode, iv)
    encoded = EncodeAES(cipher, password)
    return encoded

def DeCryptIt(encoded, secret):
    cipher = AES.new(secret, mode, iv)
    decoded = DecodeAES(cipher, encoded)
    return decoded

我希望这能有所帮助。 干杯。

我在2011年7月提出了这个问题(并接受了一个答案),已经将近2年了!你的回答可能会帮助未来的其他人,但我已经从这个项目中离开了。 - Eonasdan

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接