0

This is a continuation of this question. Please do not mark this question as a duplicate for it has a different error I need to fix.

TL;DR for the link:

So I was asking about a Unicode error in python for my encryption program and someone told me to just encode the password in utf-8 and it worked.


Now I have a second problem, where it says that the IV isn't 16 bytes, so I checked this by putting print(len(IV)) after the IV and after running 3 tests it only returns 16 once, in the middle of the printed characters, and the end character is greater than 16 (eg: 37, 35, 28, etc.).

How could I fix this so that the IV always returns 16 bytes?

Full error:

Traceback (most recent call last):
  File "/home/pi/Desktop/Projects/FyleCript/Dev Files/encryption.py", line 77, in <module>
    encrypt(SHA256.new(password.encode('utf-8')).digest(), str(Tfiles))
  File "/home/pi/Desktop/Projects/FyleCript/Dev Files/encryption.py", line 17, in encrypt
    encryptor = AES.new(key, AES.MODE_CBC, IV)
  File "/usr/lib/python3/dist-packages/Crypto/Cipher/AES.py", line 94, in new
    return AESCipher(key, *args, **kwargs)
  File "/usr/lib/python3/dist-packages/Crypto/Cipher/AES.py", line 59, in __init__
    blockalgo.BlockAlgo.__init__(self, _AES, key, *args, **kwargs)
  File "/usr/lib/python3/dist-packages/Crypto/Cipher/blockalgo.py", line 141, in __init__
    self._cipher = factory.new(key, *args, **kwargs)
ValueError: IV must be 16 bytes long

Code:

def encrypt(key, filename):
        chunksize = 64 * 1024
        outFile = os.path.join(os.path.dirname(filename), "(encrypted)"+os.path.basename(filename))
        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)

        with open(filename, "rb") as infile:
                with open(outFile, "wb") as outfile:
                        outfile.write(filesize)
                        outfile.write(IV)
                        while True:
                                chunk = infile.read(chunksize)

                                if len(chunk) == 0:
                                        break

                                elif len(chunk) % 16 !=0:
                                        chunk += ' ' *  (16 - (len(chunk) % 16))

                                outfile.write(encryptor.encrypt(chunk))


def decrypt(key, filename):
        outFile = os.path.join(os.path.dirname(filename), os.path.basename(filename[11:]))
        chunksize = 64 * 1024
        with open(filename, "rb") as infile:
                filesize = infile.read(16)
                IV = infile.read(16)

                decryptor = AES.new(key, AES.MODE_CBC, IV)

                with open(outFile, "wb") as outfile:
                        while True:
                                chunk = infile.read(chunksize)
                                if len(chunk) == 0:
                                        break

                                outfile.write(decryptor.decrypt(chunk))

                        outfile.truncate(int(filesize))

Any help would be appreciated.

Trooper Z
  • 1,617
  • 14
  • 31
  • 1
    The first thing I noticed: `if choice == "E" or 'e'` will always be true. Instead do `if choice in ( "E", 'e')` – Patrick Haugh Jul 16 '18 at 13:06
  • We don't need to see your entire program, just the minimal amount required to reproduce the issue. There's a *lot* that can be trimmed out. – glibdud Jul 16 '18 at 13:20
  • Do not use `os.random()` when doing crypto, use [`Crypto.Random()`](https://www.dlitz.net/software/pycrypto/api/current/Crypto.Random-module.html) instead. Check [`this answer`](https://stackoverflow.com/a/44212550/7553525) to see how to do it properly, it also shows differences between Python 2.x and 3.x. – zwer Jul 16 '18 at 13:29

2 Answers2

3

Well, let's take a look at what IV may consist of:

IV = ''

for i in range(16):
    IV += chr(random.randint(0, 0xFF))

Let's see how many bytes a character from range(0, 0xff) consumes:

>>> [len(chr(i).encode()) for i in range(0, 0xff)]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

And this is the source of the problem: you're assuming each character is one byte long, but that's not the case.

You can generate a random IV of N bytes with the following code:

import os

N = 16
IV = os.urandom(N)

Another issue in your code is that you're opening all your files in 'rb' mode, which stands for "read binary", but attempting to write to it instances of str, like your IV. That won't work because in this mode you're only allowed to read and write bytes, not str. In my solution for calculating the IV this issue completely disappears.

ForceBru
  • 43,482
  • 10
  • 63
  • 98
  • So how do I solve the next error: `chunk += ' ' * (16 - (len(chunk) % 16)) TypeError: can't concat bytes to str`? If I put a `b` in front of it, the encrypted file says it has bytes, but when i open it in the text editor, the file is blank. – Trooper Z Jul 16 '18 at 13:49
  • @TrooperZ, you should read about the usage of `bytes` in Python 3 and how this is different from `str`. If a _text_ editor shows you something that looks empty, don't trust it and open your file in a hex editor. – ForceBru Jul 16 '18 at 13:51
  • But in python 2, it shows odd characters (which means it's encrypted) – Trooper Z Jul 16 '18 at 13:53
  • What's the size of the newly generated encrypted file? If it's zero bytes, then it is empty indeed. – ForceBru Jul 16 '18 at 13:56
  • it's MUCH bigger (a 3.4 kb to 6.8) – Trooper Z Jul 16 '18 at 17:19
  • So, if the size of the new file is not zero, that file is, obviously, not empty. A text editor may show garbage or may not show anything at all (because not all characters are printable). To see the actual bytes of the encrypted file, just do `print(open(your_file, 'rb').read())` or open the file in a hex editor. – ForceBru Jul 16 '18 at 17:22
  • but theres also another issue: when i try to decrypt, it says its already decrypted (sorry for grammar issues im rushing) – Trooper Z Jul 19 '18 at 16:45
  • If you have other issues with your code, feel free to ask another question. Please try to fix this yourself first and don't forget to provide a [mcve]. – ForceBru Jul 19 '18 at 16:46
1

You have not converted your IV string to a byte string. In Python 3 str is not a byte string, but a character string. str is abstracted away from the concept of how characters are represented as bytes.

You'll need to convert your IV variable (and perhaps others, I haven't checked) to be instances of bytes. It's also a bit easier to make your bytestring in Python 3.

random_byte_list = [random.randrange(256) for _ in range(16)]
IV = bytes(random_byte_list)
Dunes
  • 37,291
  • 7
  • 81
  • 97