使用 Python 和 Pycryptodome 无法从 Input() 加密和解密字符串

问题描述 投票:0回答:1

我正在尝试创建一个应用程序,该应用程序将根据用户使用的密码生成的密钥来加密和解密数据。我正在使用 Pycryptodome 并收到此错误:

ValueError: Data must be padded to 16 byte boundary in CBC mode

下面是代码

from Crypto.Protocol.KDF import PBKDF2
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad


class SingleEncryption:

    def __init__(self, password):
        self.password = password
        # self.salt = random data
        self.key = PBKDF2(self.password, self.salt, dkLen=32)

    def saveKey(self):

        if input(f"Would you like to save this key ({self.key}) to a file? Y/N ") == "Y":
            file_out = open(input("Where should I save it? "), "wb")
            file_out.write(self.key)
            file_out.close()
        else:
            print(f"Key not saved, the key is {self.key}")

    def encrypt(self, data):
        cipher = AES.new(self.key, AES.MODE_CBC)
        ciphered_data = cipher.encrypt(pad(data, AES.block_size))
        return ciphered_data

    def decrypt(self, cipher_data):
        cipher2 = AES.new(self.key, AES.MODE_CBC,)
        original_data = unpad(cipher2.decrypt(cipher_data), AES.block_size)
        return original_data


if __name__ == "__main__":
    encrypt = SingleEncryption(input("Encryption password? "))
    while True:
        q = input("Encrypt, Decrypt, or Quit? ")
        if q == "Encrypt":
            print(encrypt.encrypt(input("What to encrypt? ").encode()))
        elif q == "Decrypt":
            print(encrypt.decrypt(input("Encrypted data? ").encode()))
        elif q == "Quit":
            break
        else:
            print("Incorrect input, it is case sensitive.")

python encryption pycryptodome
1个回答
0
投票

错误消息Data必须在CBC模式下填充到16字节边界不是由缺失填充引起的(当前代码填充明文!),而是由于I/O问题导致密文损坏(并且不是)在加密期间抛出,但在解密期间抛出)。

密文由应用程序作为字节字符串输出,并用

b''
(或
b""
)标记,除了 ASCII 字符之外,通常还包含
\xNM
形式的转义序列。

如果在解密期间输入此字节字符串,则

\x
不会被解释为转义序列的标记,而是作为输入文本的一部分(就像
b''
一样,如果也应该输入的话)。这会破坏密文,不仅更改其内容,还更改其长度,使其不再对应于 16 字节的倍数,这才是错误消息的实际原因。

问题的一个简单解决方案是二进制到文本的编码,例如十六进制或 Base64,例如:

...
if q == "Encrypt":
    print(encrypt.encrypt(input("What to encrypt? ").encode()).hex())         # hex encode ciphertext
elif q == "Decrypt":
    print(encrypt.decrypt(bytes.fromhex(input("Encrypted data? "))).decode()) # hex decode ciphertext before decryption
...

进行此更改后,不再触发错误消息,而是通常显示 Padding is invalid 错误消息。

第二个问题是由于加密和解密使用不同的IV造成的。请记住,使用的 CBC 模式需要 IV。由于加密时没有指定IV,PyCryptodome在加密和解密时会生成随机IV,从而导致错误。

因此,通常的解决方案是持久存储 IV,而不是暂时存储。为此,IV 和密文通常在加密期间连接并在解密期间分开。 IV 的暴露不是安全问题,因为它不是秘密:

...
def encrypt(self, data):
    cipher = AES.new(self.key, AES.MODE_CBC)
    ciphered_data = cipher.encrypt(pad(data, AES.block_size))
    return cipher.iv + ciphered_data # concatenate IV and ciphertext
        
def decrypt(self, cipher_data):
    iv = cipher_data[:16]         # separate IV...
    ciphertext = cipher_data[16:] # ...and ciphertext
    cipher = AES.new(self.key, AES.MODE_CBC, iv) # specify the IV from encryption
    original_data = unpad(cipher.decrypt(ciphertext), AES.block_size)
    return original_data

第三个问题可能是由随机盐的处理引起的。当使用随机盐(出于安全原因需要)时,每个

SingleEncryption
都会生成不同的密钥(因为密钥派生发生在
__init__()
中),因此只有使用 same 执行解密才能成功
SingleEncryption
实例作为加密。如果这足以满足要求,那么就可以(尽管使用相同盐的多重加密是一个漏洞)。

但是,如果也可以使用不同

SingleEncryption
实例进行加密和解密,则有必要将随机盐的生成和密钥派生从
__init__()
移动到
encrypt()
,其中盐还必须是与 IV 和密文连接(盐,像 IV 一样,不是秘密的)。
decrypt()
中,盐、IV和密文被分离,然后推导出密钥,最后解密密文。

© www.soinside.com 2019 - 2024. All rights reserved.