Skipping elif statement?

我的梦境 提交于 2019-12-25 16:23:31

问题


Am trying to create a simple encryption/decryption using pycryptodome but keeping getting the following error:

ValueError: Error 3 while encrypting in CBC mode

after some digging I saw that you get this error if there is not enough data to encrypt, as in there is no padding in effect. The thing is that I've added a padding function. After debugging it seems as if my code literally skips the padding part completely and causes this error. What am I doing wrong?

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

*I am using python 3.6

EDIT: Here are the full console output when I run the code:

   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

回答1:


Ok, let's fix a few things that are wrong with your code. First the most obvious one - your padding would break on Python 3.5+ (and your user 'menu' would break on 2.x) because infile.read() would give you bytes array so trying to add a string formed by chunk += ' ' * (16 - (len(chunk) % 16)) would result in an error. You would need to convert your whitespace pad to bytes array first: chunk += b' ' * (16 - (len(chunk) % 16))

But whitespace padding like this is a bad idea - when you're later decrypting your file how will you know how much, if any, padding you've added? You need to store this somewhere - and you do in the 'header' via the filesize value, telling a potential attacker how exactly big is your file and how much padding was added opening you to a padding oracle attack (which is possible with the bellow code so do not use it for passing messages without adding a proper MAC to it).

There are plenty of robust padding schemes that you can use - I personally prefer PKCS#7 which is simply padding your uneven block or adding a whole new block with n number of bytes with the value of n - that way, after decryption, you can pick the last byte from your block and know exactly how many bytes were padded so you can strip them. So, replace your encryption portion with:

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

I've also changed your chunksize to match the block size you're using (multiples of AES.block_size) - it just happens that 64 is a multiple of 16 but you should pay attention to those things.

Now that we have the encryption sorted out, the decryption is all this but in reversal - decrypt all blocks, read the last byte of the last block and remove n amount of bytes from behind matching the value of the last byte:

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


来源:https://stackoverflow.com/questions/44123164/skipping-elif-statement

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!