0

我正在尝试使用 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
4

1 回答 1

2

好的,让我们修复一些您的代码有问题的地方。首先是最明显的一个 - 您的填充将在 Python 3.5+ 上中断(并且您的用户“菜单”将在 2.x 上中断)因为infile.read()会给您提供bytes数组,因此尝试添加由 形成的字符串chunk += ' ' * (16 - (len(chunk) % 16))会导致错误。您需要bytes先将空白填充转换为数组: chunk += b' ' * (16 - (len(chunk) % 16))

但是像这样的空白填充是一个坏主意——当你稍后解密你的文件时,你怎么知道你添加了多少(如果有的话)填充?您需要将其存储在某处-您通过filesize值在“标题”中执行此操作,告诉潜在的攻击者您的文件到底有多大以及添加了多少填充,从而使您面临填充 oracle 攻击(可以使用以下代码所以不要使用它来传递消息而不添加适当的 MAC)。

您可以使用许多强大的填充方案 - 我个人更喜欢PKCS#7n ,它只是填充您的不均匀块或添加一个字节数为的全新块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以匹配您正在使用的块大小(的倍数AES.block_size) - 恰好 64 是 16 的倍数,但您应该注意这些事情。

现在我们已经解决了加密问题,解密就是这样,但相反 - 解密所有块,读取最后一个块的最后一个字节,并n从后面删除与最后一个字节的值匹配的字节数:

def decrypt(key, filename):
    outputfile = filename[:-11] + "(decrypted)"
    chunksize = 1024 * AES.block_size  # use the cipher's defined block size as a multiplier
    with open(filename, 'rb') as infile:
        IV = infile.read(AES.block_size)
        decryptor = AES.new(key, AES.MODE_CBC, IV)
        with open(outputfile, 'wb') as outfile:
            old_chunk = b''  # stores last chunk, needed for reading data with a delay
            stripped = False
            while not stripped:  # delayed loop until the last block is stripped
                chunk = decryptor.decrypt(infile.read(chunksize))  # decrypt as we read
                if len(chunk) == 0:  # no more data
                    padding = old_chunk[-1]  # pick the padding value from the last byte
                    if old_chunk[-padding:] != bytes([padding]) * padding:
                        raise ValueError("Invalid padding...")
                    old_chunk = old_chunk[:-padding]  # strip the padding
                    stripped = True
                outfile.write(old_chunk)  # write down the 'last' chunk
                old_chunk = chunk  # set the new chunk for checking in the next loop
于 2017-05-23T04:55:52.693 回答