Reputation: 3972
I am using AES to encrypt and decrypt the image. I have inherited the code so please guide me if you guys see something wrong. I am trying to understand and fix the code.
chunk_size = 64*1024
output_file = filename+".enc"
file_size = bytes(os.path.getsize(filename))
IV = get_random_bytes(16)
encryptor = AES.new(key, AES.MODE_CBC, IV)
with open(filename, 'rb') as inputfile:
with open(output_file, 'wb') as outf:
outf.write(file_size)
outf.write(IV)
while True:
chunk = bytes(inputfile.read(chunk_size))
if len(chunk) == 0:
break
elif len(chunk) % 16 != 0:
chunk = chunk + bytes(16 - len(chunk)%16)
outf.write(encryptor.encrypt(chunk))
My image is 66 kb which comes around - 67584 bytes. Considering AES works with 16 bytes this code should error out but it generate the encrypted file. When I try to decrypt using
def decrypt(key, filename):
chunk_size = 64*1024
output_file = filename[:-4]
with open(filename, 'rb') as inf:
filesize = bytes(inf.read(16))
IV = inf.read(16)
decryptor = AES.new(key, AES.MODE_CBC, IV)
with open(output_file, 'wb') as outf:
while True:
chunk = inf.read(chunk_size)
print(len(chunk))
if len(chunk)==0:
break
outf.write(decryptor.decrypt(chunk))
outf.truncate(filesize)```
I get an error like below
TypeError: an integer is required (got type bytes)
and when I type chunk in bytes I get the following error
Input strings must be a multiple of 16 in length
I am confused how can I fix error "multiple of 16 in length" when my filesize on the console for source is shown as 65536.
Upvotes: 4
Views: 5059
Reputation: 1
You can take a look at aesfile : https://github.com/bibi21000/AesFile
To encrypt :
import aesfile
with open(source, 'rb') as fin, aesfile.open(destination, mode='wb', aes_key=key) as fout:
while True:
data = fin.read(7777)
if not data:
break
fout.write(data)
And decrypt :
import aesfile
with aesfile.open(source, mode='rb', aes_key=key) as fin, open(destination, 'wb') as fout :
while True:
data = fin.read(8888)
if not data:
break
fout.write(data)
Or you can compress (with zstd) and encrypt in one pass, simply replace
import aesfile
with
import aesfile.zstd
Look at https://github.com/bibi21000/CofferFile/blob/main/BENCHMARK.md for performance impact.
Upvotes: 0
Reputation: 111
Well I am not able to verify the given code since I dont know your exact need or usage or the idea of the implementation, but if you want to see a similar code I wrote about how to encrypt and decrypt the images using python by AES encryption, to get the crux idea(you may then be able to tweak and get your code working according to your needs, or use mine if you just want what it does)
You may consider a step by step blog walkthrough I wrote a while back it might help(Recommended before using the code for better understanding of the code) How to Encrypt and Decrypt Images using Python and pycryptodome
However if you just need the code and its dependencies you may as well fork it from its official github repo CrypImg
Note: You should get the neccessary modules installed before using, you may get them in the requirements.txt given along with the code in the github repo above.
I know I was not able to directly solve your problem, but just because I myself got it working after a lot of challenges I wanted to try to help if it matches what you need so you may get it working as well.
My code:
#Importing Stuff
from Crypto.Cipher import AES
import io
import PIL.Image
from tkinter import *
import os
#Private Stuff
key = b'Key of length 16' #Todo Enter a Key(Like a password only) Here of Length 16 (Both Key and ivb required keep both safely and securely)
iv = b'ivb of length 16' #Todo Enter a ivb (Like a password only) Here of Length 16 (Both Key and ivb required keep both safely and securely)
#Encrypting Image
def encrypt_image():
global key,iv,entry_for_folder
file_path=str(entry_for_folder.get())
if(file_path=="" or file_path[0]==" "):
file_path=os.getcwd()
files=[]
# r=root, d=directories, f = files
for r, d, f in os.walk(file_path):
for file in f:
if((('.JPG' in file) or ('.jpg' in file)) and ('.enc' not in file)):
files.append(os.path.join(r, file))
for file_name in files:
input_file = open(file_name,"rb")
input_data = input_file.read()
input_file.close()
cfb_cipher = AES.new(key, AES.MODE_CFB, iv)
enc_data = cfb_cipher.encrypt(input_data)
enc_file = open(file_name+".enc", "wb")
enc_file.write(enc_data)
enc_file.close()
#Decrypting Image
def decrypt_image():
global key,iv,entry_for_folder
file_path = str(entry_for_folder.get())
if (file_path == "" or file_path[0] == " "):
file_path = os.getcwd()
files = []
# r=root, d=directories, f = files
for r, d, f in os.walk(file_path):
for file in f:
if '.enc' in file:
files.append(os.path.join(r, file))
for file_name in files:
enc_file2 = open(file_name,"rb")
enc_data2 = enc_file2.read()
enc_file2.close()
cfb_decipher = AES.new(key, AES.MODE_CFB, iv)
plain_data = (cfb_decipher.decrypt(enc_data2))
imageStream = io.BytesIO(plain_data)
imageFile = PIL.Image.open(imageStream)
if('.jpg' in file_name):
imageFile.save((file_name[:-8])+".JPG")
elif('.JPG' in file_name):
imageFile.save((file_name[:-8])+".jpg")
#Tkinter Stuff
root=Tk()
root.title("Simple AES Encryption and Decryption of JPG Images")
folder_directory_label=Label(text="Enter the Folder Directory")
folder_directory_label.pack()
entry_for_folder=Entry(root)
entry_for_folder.pack()
encrypt=Button(text="Encrypt All",command=encrypt_image)
encrypt.pack()
label=Label(text="Leave Blank for Current Working Directory")
label.pack()
decrypt=Button(text="Decrypt All",command=decrypt_image)
decrypt.pack()
root.mainloop()
This above app code is Licensed under MIT LICENSE AGREEMENT
and After Encryption(leave the directory entry blank for the encrypting/decrypting all images recursively in the current working directory else enter a folder directory)
Hope it Helps a little.
------------------Update-------------------------
The Code is updated for more convenient usage, please check the github repo Cryptimg For the latest version of the code.
Upvotes: 0
Reputation: 49525
The file size is stored improperly. To store the file size in the first 16 bytes (as it's presumably intended with regard to the decrypt
method, although 16 bytes are actually too large) in big endian order, replace in the encryption:
file_size = bytes(os.path.getsize(filename))
with
file_size = os.path.getsize(filename).to_bytes(16, byteorder='big')
and in the decryption:
filesize = bytes(inf.read(16))
with
filesize = int.from_bytes(inf.read(16), byteorder='big')
With these changes, encryption and decryption work as intended.
Note: You use a Zero padding variant for padding and store the file size (probably only) to remove the padding after decryption. There is a more efficient method, PKCS7 padding. Here the information how many bytes to remove is already included in the padding itself. So the file size does not have to be stored (at least not to remove the padding). In addition, padding is also supported in PyCryptodome by the methods pad
and unpad
.
Upvotes: 4