13

I already have a working program, but the only thing that doesn't work is the decrypt_file() function I have. I can still copy the encrypted text from the file and put it in my decrypt() function and have it work, but when I try to use my supposed-to-be handy decrypt_file() function it throws an error. Now I know 99.999% sure that my encrypt() and decrypt() functions are fine, but there is something with the bytes and strings conversion when I read and encode the text file that throws an error; I just can't find the hangup. Please help!

My Program:

from Crypto import Random
from Crypto.Cipher import AES

def encrypt(message, key=None, key_size=256):
    def pad(s):
        x = AES.block_size - len(s) % AES.block_size
        return s + ((bytes([x])) * x)

    padded_message = pad(message)

    if key is None:
        key = Random.new().read(key_size // 8)

    iv = Random.new().read(AES.block_size)
    cipher = AES.new(key, AES.MODE_CBC, iv)

    return iv + cipher.encrypt(padded_message)

def decrypt(ciphertext, key):
    unpad = lambda s: s[:-s[-1]]
    iv = ciphertext[:AES.block_size]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = unpad(cipher.decrypt(ciphertext))[AES.block_size:]

    return plaintext

def encrypt_file(file_name, key):
    f = open(file_name, 'r')
    plaintext = f.read()
    plaintext = plaintext.encode('utf-8')
    enc = encrypt(plaintext, key)
    f.close()
    f = open(file_name, 'w')
    f.write(str(enc))
    f.close()

def decrypt_file(file_name, key):
    def pad(s):
        x = AES.block_size - len(s) % AES.block_size
        return s + ((str(bytes([x]))) * x)

    f = open(file_name, 'r')
    plaintext = f.read()
    x = AES.block_size - len(plaintext) % AES.block_size
    plaintext += ((bytes([x]))) * x
    dec = decrypt(plaintext, key)
    f.close()
    f = open(file_name, 'w')
    f.write(str(dec))
    f.close()



key = b'\xbf\xc0\x85)\x10nc\x94\x02)j\xdf\xcb\xc4\x94\x9d(\x9e[EX\xc8\xd5\xbfI{\xa2$\x05(\xd5\x18'

encrypt_file('to_enc.txt', key)

The text file I encrypted:

b';c\xb0\xe6Wv5!\xa3\xdd\xf0\xb1\xfd2\x90B\x10\xdf\x00\x82\x83\x9d\xbc2\x91\xa7i M\x13\xdc\xa7'

My error when attempting decrypt_file:

    Traceback (most recent call last):
  File "C:\Python33\testing\test\crypto.py", line 56, in <module>
    decrypt_file('to_enc.txt', key)
  File "C:\Python33\testing\test\crypto.py", line 45, in decrypt_file
    plaintext += ((bytes([x]))) * x
TypeError: Can't convert 'bytes' object to str implicitly
[Finished in 1.5s]

When I replace line 45 with: plaintext += ((str(bytes([x])))) * x, this is the error I get:

Traceback (most recent call last):
  File "C:\Python33\testing\test\crypto.py", line 56, in <module>
    decrypt_file('to_enc.txt', key)
  File "C:\Python33\testing\test\crypto.py", line 46, in decrypt_file
    dec = decrypt(plaintext, key)
  File "C:\Python33\testing\test\crypto.py", line 23, in decrypt
    plaintext = unpad(cipher.decrypt(ciphertext))[AES.block_size:]
  File "C:\Python33\lib\site-packages\Crypto\Cipher\blockalgo.py", line 295, in decrypt
    return self._cipher.decrypt(ciphertext)
ValueError: Input strings must be a multiple of 16 in length
[Finished in 1.4s with exit code 1]
Trooper Z
  • 1,617
  • 14
  • 31
Zach King
  • 798
  • 1
  • 8
  • 21
  • it's the `bytes` object. That is a built-in type. You didn't define it in the program so it's using a the built-in object. I think you meant `plaintext`. – Keith Dec 31 '13 at 06:15
  • So are you saying the problem is in line 46? Sorry I'm kinda tired on this one and not really thinking clearly. – Zach King Dec 31 '13 at 06:22
  • It says right in the stacktrace the line and the code. – Keith Dec 31 '13 at 06:30
  • Sorry, lines 45 and 46 were some of my own debugging type things so I removed those and replaced the error with my actual error. – Zach King Dec 31 '13 at 06:32
  • Okay I also added another important error that I get when I try to fix the bytes thing, unless my 'fix' is incorrect. – Zach King Dec 31 '13 at 06:36

2 Answers2

31

I took a closer look at your code, and saw that there were several problems with it. First one is that the crypto functions with with bytes, not text. So it's better to just keep the data as a byte string. This is done simply by putting a 'b' character in the mode. This way you can get rid of all the encoding and bytes conversion you were trying to do.

I rewrote the whole code also using newer Python idioms. Here it is.

#!/usr/bin/python3

from Crypto import Random
from Crypto.Cipher import AES

def pad(s):
    return s + b"\0" * (AES.block_size - len(s) % AES.block_size)

def encrypt(message, key, key_size=256):
    message = pad(message)
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    return iv + cipher.encrypt(message)

def decrypt(ciphertext, key):
    iv = ciphertext[:AES.block_size]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext[AES.block_size:])
    return plaintext.rstrip(b"\0")

def encrypt_file(file_name, key):
    with open(file_name, 'rb') as fo:
        plaintext = fo.read()
    enc = encrypt(plaintext, key)
    with open(file_name + ".enc", 'wb') as fo:
        fo.write(enc)

def decrypt_file(file_name, key):
    with open(file_name, 'rb') as fo:
        ciphertext = fo.read()
    dec = decrypt(ciphertext, key)
    with open(file_name[:-4], 'wb') as fo:
        fo.write(dec)


key = b'\xbf\xc0\x85)\x10nc\x94\x02)j\xdf\xcb\xc4\x94\x9d(\x9e[EX\xc8\xd5\xbfI{\xa2$\x05(\xd5\x18'

encrypt_file('to_enc.txt', key)
#decrypt_file('to_enc.txt.enc', key)
Keith
  • 42,110
  • 11
  • 57
  • 76
  • Awesome! Thanks Keith. I knew that my code was pretty sloppy but I have been piecing together others' working parts and trying to make my own--to no avail. This works perfectly though! I'll go ahead and review the code fully and comment it up so that I won't forget how it all works. – Zach King Jan 02 '14 at 18:58
  • Have you tried with .docx or any kind except .txt files? – Quan Nov 21 '15 at 17:51
  • 2
    I'm skeptical about the `plaintext.rstrip(b"\0")` in the `decrypt` function. What if the plaintext ended with null bytes? If I encrypt a file with this code, isn't there a risk of the file being corrupt after decryption? – Aran-Fey Apr 03 '17 at 10:15
  • @keith Is there a way to make the decryption of files faster ? I have mp3 files to be encrypted and decrypted. – yajant b Feb 05 '19 at 09:56
  • @yajantb You could read the source in chunks, rather than suck in the whole thing as this does. this is for small text files. – Keith Feb 07 '19 at 07:21
  • @Keith Can you please elaborate this? A example with the code will be more useful. – yajant b Feb 07 '19 at 08:13
  • Thanks a lot @Keith..I had been looking for this kind of solution. – Ranjan Pal Jan 07 '21 at 08:51
3

In Python 3 (which you are clearly using) the default mode for files you open is text, not binary. When you read from the file, you get strings rather than byte arrays. That does not go along with encryption.

In your code, you should replace:

open(file_name, 'r')

with:

open(file_name, 'rb')

The same for when you open the file for writing. At that point, you can get rid of all the various occurrences where you convert from string to binary and vice versa.

For instance, this can go away:

plaintext = plaintext.encode('utf-8')
  • Okay I tried this; I replaced ALL of my open() modes with rb and wb respectively and now I get this issue: the encrypt_file function works but when I try to use decrypt_file() it runs fine but the file is empty when I open it up...I'm sorry could you please provide the edits in case I missed something, like Line x: , Line y: , etc. I'm really new to using bytes and encryption so it's all a "bit" confusing--and yes that was a pun...haha – Zach King Dec 31 '13 at 19:46