跳过 elif 声明?

Skipping elif statement?

我正在尝试使用 pycryptodome 创建一个简单的 encryption/decryption,但不断收到以下错误:

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()

*我用的是python3.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

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

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

您可以使用许多强大的填充方案 - 我个人更喜欢 PKCS#7,它只是填充您的不均匀块或添加一个具有 n 字节数和值的全新块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