我尝试使用pycryptodome创建一个简单的加密/解密,但不断收到以下错误:
ValueError: Error 3 while encrypting in CBC mode
经过一些挖掘后,我发现如果没有足够的数据加密,你会收到这个错误,因为没有填充有效。问题是我添加了填充功能。调试后,似乎我的代码字面上完全跳过填充部分并导致此错误。我做错了什么?
import os, random
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
def encrypt(key, filename):
chunksize = 64*1024
outputfile = filename + "(encrypted)"
filesize = str(os.path.getsize(filename)).zfill(16)
IV =''
for i in range(16):
IV += chr(random.randint(0, 0xFF))
encryptor = AES.new(key, AES.MODE_CBC, IV.encode("latin-1"))
with open(filename, 'rb') as infile:
with open(outputfile, 'wb') as outfile:
outfile.write(filesize.encode("latin-1"))
outfile.write(IV.encode("latin-1"))
while True:
chunk = infile.read(chunksize)
print(len(chunk))
if len(chunk) == 0:
break
elif len(chunk) % 16 != 0:
chunk += ' ' * (16 - (len(chunk) % 16))
outfile.write(encryptor.encrypt(chunk))
def decrypt(key, filename):
chunksize = 64 *1024
outputfile = filename[:11]
with open(filename, 'rb') as infile:
filesize = int(infile.read(16))
IV = infile.read(16)
decryptor = AES.new(key, AES.MODE_CBC, IV.encode("latin-1"))
with open(outputfile, 'wb') as outfile:
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
outfile.write(decryptor.decrypt(chunk))
outfile.truncate(filesize)
def getkey (password):
hasher = SHA256.new(password.encode("latin-1"))
return hasher.digest()
def main():
choice = input ("do you want to [E]ncrypt of [D]ecrypt?")
if choice == 'E':
filename = input("File to encrypt >")
password = input("Password >")
encrypt(getkey(password), filename)
print("Encryption done!")
elif choice == 'D':
filename = input("File to Decrypt >")
password = input("Password >")
decrypt(getkey(password), filename)
print("Decryption done!")
else:
print("No option selected")
if __name__ == '__main__':
main()
*我正在使用python 3.6
编辑: 以下是运行代码时的完整控制台输出:
C:\Users\itayg\AppData\Local\Programs\Python\Python36\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.2\helpers\pydev\pydevd.py" --multiproc --qt-support --client 127.0.0.1 --port 21111 --file C:/Users/itayg/PycharmProjects/PyCrypto/encrypt.py
Connected to pydev debugger (build 171.4249.47)
pydev debugger: process 12876 is connecting
do you want to [E]ncrypt of [D]ecrypt?E
File to encrypt >grades.jpg
Password >123
65536
49373
Traceback (most recent call last):
File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.2\helpers\pydev\pydevd.py", line 1585, in <module>
globals = debugger.run(setup['file'], None, None, is_module)
File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.2\helpers\pydev\pydevd.py", line 1015, in run
pydev_imports.execfile(file, globals, locals) # execute the script
File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.2\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "C:/Users/itayg/PycharmProjects/PyCrypto/encrypt.py", line 66, in <module>
main()
File "C:/Users/itayg/PycharmProjects/PyCrypto/encrypt.py", line 55, in main
encrypt(getkey(password), filename)
File "C:/Users/itayg/PycharmProjects/PyCrypto/encrypt.py", line 29, in encrypt
outfile.write(encryptor.encrypt(chunk))
File "C:\Users\itayg\AppData\Local\Programs\Python\Python36\lib\site-packages\pycryptodome-3.4.6-py3.6-win-amd64.egg\Crypto\Cipher\_mode_cbc.py", line 167, in encrypt
raise ValueError("Error %d while encrypting in CBC mode" % result)
ValueError: Error 3 while encrypting in CBC mode
答案 0 :(得分:2)
好的,让我们解决一些代码错误的问题。首先是最明显的一个 - 你的填充会破坏Python 3.5+(并且你的用户&#39;菜单会在2.x上中断),因为infile.read()
会给你bytes
数组这样的尝试添加chunk += ' ' * (16 - (len(chunk) % 16))
形成的字符串会导致错误。您需要先将空白区域转换为bytes
数组:chunk += b' ' * (16 - (len(chunk) % 16))
但像这样的空白填充是一个坏主意 - 当你以后解密你的文件时,你怎么知道你添加了多少(如果有的话)填充?你需要将它存储在某个地方 - 你可以在标题中找到它。通过filesize
值告诉潜在的攻击者你的文件究竟有多大以及添加了多少填充,这使你能够进行填充oracle攻击(这可能与下面的代码有关,因此不要使用它来传递消息而不添加适当的MAC)。
你可以使用大量强大的填充方案 - 我个人更喜欢PKCS#7,它只是填充不均匀的块或添加一个n
个字节数的全新块,其值为{ {1}} - 这样,在解密之后,您可以从块中选择最后一个字节并确切地知道填充了多少字节,这样您就可以剥离它们。因此,请将您的加密部分替换为:
n
我还更改了您的def encrypt(key, filename):
outputfile = filename + "(encrypted)"
chunksize = 1024 * AES.block_size # use the cipher's defined block size as a multiplier
IV = bytes([random.randint(0, 0xFF) for _ in range(AES.block_size)]) # bytes immediately
encryptor = AES.new(key, AES.MODE_CBC, IV)
with open(filename, 'rb') as infile:
with open(outputfile, 'wb') as outfile:
outfile.write(IV) # write the IV
padded = False
while not padded: # loop until the last block is padded
chunk = infile.read(chunksize)
chunk_len = len(chunk)
# if no more data or the data is shorter than the required block size
if chunk_len == 0 or chunk_len % AES.block_size != 0:
padding = AES.block_size - (chunk_len % AES.block_size)
chunk += bytes([padding]) * padding
# on Python 2.x replace with: chunk += chr(padding_len) * padding_len
padded = True
outfile.write(encryptor.encrypt(chunk))
以匹配您正在使用的块大小(chunksize
的倍数) - 它恰好是64是16的倍数但您应该注意那些事情。
现在我们已经对加密进行了解决,解密就是这一切,但是反过来 - 解密所有块,读取最后一个块的最后一个字节,并从后面删除AES.block_size
个字节数,以匹配最后一个字节:
n