Python PyCrypto使用AES加密/解密文本文件

13
我已经拥有一个可工作的程序,但唯一不工作的是我的decrypt_file()函数。我仍然可以从文件中复制加密文本并将其放入我的decrypt()函数中使其工作,但当我尝试使用我的应该很方便的decrypt_file()函数时,它会抛出一个错误。 现在我99.999%确定我的encrypt()decrypt()函数没有问题,但是当我读取和编码文本文件时,字节和字符串转换存在问题,导致出现错误; 我只是找不到错误所在。请帮忙!
我的程序:
from Crypto import Random
from Crypto.Cipher import AES

def encrypt(message, key=None, key_size=256):
    def pad(s):
        x = AES.block_size - len(s) % AES.block_size
        return s + ((bytes([x])) * x)

    padded_message = pad(message)

    if key is None:
        key = Random.new().read(key_size // 8)

    iv = Random.new().read(AES.block_size)
    cipher = AES.new(key, AES.MODE_CBC, iv)

    return iv + cipher.encrypt(padded_message)

def decrypt(ciphertext, key):
    unpad = lambda s: s[:-s[-1]]
    iv = ciphertext[:AES.block_size]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = unpad(cipher.decrypt(ciphertext))[AES.block_size:]

    return plaintext

def encrypt_file(file_name, key):
    f = open(file_name, 'r')
    plaintext = f.read()
    plaintext = plaintext.encode('utf-8')
    enc = encrypt(plaintext, key)
    f.close()
    f = open(file_name, 'w')
    f.write(str(enc))
    f.close()

def decrypt_file(file_name, key):
    def pad(s):
        x = AES.block_size - len(s) % AES.block_size
        return s + ((str(bytes([x]))) * x)

    f = open(file_name, 'r')
    plaintext = f.read()
    x = AES.block_size - len(plaintext) % AES.block_size
    plaintext += ((bytes([x]))) * x
    dec = decrypt(plaintext, key)
    f.close()
    f = open(file_name, 'w')
    f.write(str(dec))
    f.close()



key = b'\xbf\xc0\x85)\x10nc\x94\x02)j\xdf\xcb\xc4\x94\x9d(\x9e[EX\xc8\xd5\xbfI{\xa2$\x05(\xd5\x18'

encrypt_file('to_enc.txt', key)

我加密的文本文件:

b';c\xb0\xe6Wv5!\xa3\xdd\xf0\xb1\xfd2\x90B\x10\xdf\x00\x82\x83\x9d\xbc2\x91\xa7i M\x13\xdc\xa7'

我在尝试使用decrypt_file时出现了错误:

    Traceback (most recent call last):
  File "C:\Python33\testing\test\crypto.py", line 56, in <module>
    decrypt_file('to_enc.txt', key)
  File "C:\Python33\testing\test\crypto.py", line 45, in decrypt_file
    plaintext += ((bytes([x]))) * x
TypeError: Can't convert 'bytes' object to str implicitly
[Finished in 1.5s]

当我将第45行替换为:plaintext += ((str(bytes([x])))) * x,我收到以下错误信息:
Traceback (most recent call last):
  File "C:\Python33\testing\test\crypto.py", line 56, in <module>
    decrypt_file('to_enc.txt', key)
  File "C:\Python33\testing\test\crypto.py", line 46, in decrypt_file
    dec = decrypt(plaintext, key)
  File "C:\Python33\testing\test\crypto.py", line 23, in decrypt
    plaintext = unpad(cipher.decrypt(ciphertext))[AES.block_size:]
  File "C:\Python33\lib\site-packages\Crypto\Cipher\blockalgo.py", line 295, in decrypt
    return self._cipher.decrypt(ciphertext)
ValueError: Input strings must be a multiple of 16 in length
[Finished in 1.4s with exit code 1]

它是bytes对象。这是一个内置类型。程序中没有定义它,所以使用了内置对象。我认为你指的是plaintext - Keith
那么你是说问题在第46行吗?抱歉,我有点累了,思维不太清晰。 - Zach King
堆栈跟踪中明确指出了代码所在的行数和代码内容。 - Keith
抱歉,第45和46行是我自己调试时添加的内容,所以我将其删除并用实际错误替换了该错误。 - Zach King
好的,我还添加了另一个重要的错误,当我尝试修复字节时会出现,除非我的“修复”是不正确的。 - Zach King
2个回答

30

我仔细查看了你的代码,发现存在几个问题。首先,加密功能只能用于字节而非文本。因此最好将数据保留为字节字符串。这可以通过在模式中放置“b”字符来实现。这样就可以摆脱你试图进行的所有编码和字节转换。

我重新编写了整个代码,并使用了更新的 Python 成语。以下是它。

#!/usr/bin/python3

from Crypto import Random
from Crypto.Cipher import AES

def pad(s):
    return s + b"\0" * (AES.block_size - len(s) % AES.block_size)

def encrypt(message, key, key_size=256):
    message = pad(message)
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    return iv + cipher.encrypt(message)

def decrypt(ciphertext, key):
    iv = ciphertext[:AES.block_size]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext[AES.block_size:])
    return plaintext.rstrip(b"\0")

def encrypt_file(file_name, key):
    with open(file_name, 'rb') as fo:
        plaintext = fo.read()
    enc = encrypt(plaintext, key)
    with open(file_name + ".enc", 'wb') as fo:
        fo.write(enc)

def decrypt_file(file_name, key):
    with open(file_name, 'rb') as fo:
        ciphertext = fo.read()
    dec = decrypt(ciphertext, key)
    with open(file_name[:-4], 'wb') as fo:
        fo.write(dec)


key = b'\xbf\xc0\x85)\x10nc\x94\x02)j\xdf\xcb\xc4\x94\x9d(\x9e[EX\xc8\xd5\xbfI{\xa2$\x05(\xd5\x18'

encrypt_file('to_enc.txt', key)
#decrypt_file('to_enc.txt.enc', key)

太棒了!谢谢Keith。我知道我的代码相当混乱,但我一直在拼凑其他人的工作部分并尝试制作自己的--但都没有成功。不过这个完美地运行了!我会仔细检查代码并加上注释,这样我就不会忘记它是如何工作的。 - Zach King
你尝试过使用.docx或任何除.txt文件以外的文件吗? - Quan
2
我对decrypt函数中的plaintext.rstrip(b"\0")持怀疑态度。如果明文以空字节结尾会怎样?如果我使用这段代码加密一个文件,解密后文件不会出现损坏的风险吗? - Aran-Fey
@keith 有没有办法让文件的解密更快?我需要加密和解密MP3文件。 - yajant b
@Keith,您能否详细说明一下?附上代码示例会更有帮助。 - yajant b
显示剩余2条评论

3
在Python 3中(显然您正在使用),您打开的文件的默认模式是文本而不是二进制模式。当您从文件中读取时,得到的是字符串而不是字节数组。这与加密不符。
在您的代码中,您应该替换:
open(file_name, 'r')

使用:

open(file_name, 'rb')

当您打开文件进行写入时,同样需要进行相应的操作。这时,您可以消除所有将字符串转换为二进制或反之的情况。
例如,以下内容可以被删除:
plaintext = plaintext.encode('utf-8')

好的,我尝试了一下;我分别用rb和wb替换了所有的open()模式,现在出现了这个问题:encrypt_file函数可以正常工作,但是当我尝试使用decrypt_file()时,它运行得很好,但是当我打开文件时,文件是空的...对不起,您能否提供编辑内容,以防我错过了什么,比如第x行:,第y行:等。我真的很新手,对于使用字节和加密来说,这一切都有点令人困惑——是的,那是一个双关语...哈哈 - Zach King

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