Preface: this is required for a class, I know ECB should not be used.
I am trying to encrypt images using AES and then display the images
Steps needed:
Read the image,
Convert to byte object,
Pad the bytes,
Encrypt the bytes,
Convert back to image object,
Save as image file
This is my code right now:
from PIL import Image
from Crypto.Cipher import AES
from Crypto import Random
img = Image.open("photo.jpg")
img.tobytes()
key = '0123456789abcdef'
mode = AES.MODE_ECB
encryptor = AES.new(key, mode)
img.frombytes("RGB")
At this point I am stuck. I am getting a "not enough image data" error on the line "img.frombytes("RGB"), and am also stuck at the part to pad the bytes
So I needed a way to transfer files in the form of images (don't ask me why), if you just want to transfer text you can maybe create a txt file.
This is probably not exactly the best solution to the question as you probably want a way to hide data inside an existing image but I would like anyway to share the code in case it will help someone sometime somewhere.
Basically this will create an image with a size dependent on the file size and will put a sequence of 3 bytes in one pixel (RGB)
So I wrote a small folder2ImageEncoder.py
(it will encrypt all the data that is located in a folder named "files" by default)
from PIL import Image
from pathlib import Path
encryptedImagesFolder = 'encryptedImagesFolder'
Path(f"./{encryptedImagesFolder}").mkdir(parents=True, exist_ok=True)
newLine = b'\new\n\rL'
def encode_data_to_image(data: bytes, imagePath: str):
data += b'FINISH_OF_DATA'
data = str(
{
'path': imagePath,
'data': data
}
)
data = data.encode()
n = int((len(data)/3)**0.5) + 1
print(n, len(data))
img = Image.new('RGB', (n, n))
# data = img.getdata()
encryptedPixelsList = []
pixel = []
if len(data) % 3 != 0:
data += (3 - (len(data) % 3)) * b'\x00'
for i, Byte in enumerate(data):
if i % 3 == 2:
pixel.append(Byte)
encryptedPixelsList.append(tuple(pixel))
pixel = []
else:
pixel.append(Byte)
for _ in range(len(encryptedPixelsList), n**2):
encryptedPixelsList.append((0, 0, 0))
img.putdata(encryptedPixelsList)
imagePath = imagePath.replace('\\', '_')
img.save(f'./{encryptedImagesFolder}/{imagePath}.png')
# img.show()
def encode_folder(folder: Path):
for file in folder.iterdir():
if not file.is_dir():
with open(str(file), 'rb') as rb:
data = rb.readlines()
encode_data_to_image(
data=newLine.join(data),
imagePath=str(file))
else:
encode_folder(folder=file)
if __name__ == '__main__':
# ./files is the name of the folder you want to encrypt
encode_folder(folder=Path(r'./files'))
and a Image2FilesDecoder.py
this will iterate through the encrypted images folder and will retrieve its former form (run this in another folder with the encrypted images folder so it won't override the original files folder)
from PIL import Image
from pathlib import Path
newLine = b'\new\n\rL'
def decode_encrypted_images(folder: Path):
for pic in folder.iterdir():
img = Image.open(str(pic))
data = img.getdata()
totalData = []
for pixel in data:
totalData.extend(list(pixel))
decryptedData = bytes(totalData)
try:
decryptedData = eval(
decryptedData[:decryptedData.rfind(b'}')+1].decode())
except:
decryptedData.replace(b'\\', '')
decryptedData = eval(
decryptedData[:decryptedData.rfind(b'}')+1].decode())
decryptedData['data'] = decryptedData['data'][:-14]
filePathObj = Path(decryptedData['path'])
Path(filePathObj.parent).mkdir(
parents=True, exist_ok=True)
writeBytes = decryptedData['data'].split(newLine)
with open(str(filePathObj), 'wb') as wb:
wb.writelines(writeBytes)
if __name__ == '__main__':
decode_encrypted_images(folder=Path(
r".\encryptedImagesFolder"))
Related
I am converting the hexadecimal files to images. The input files are converted to byte string using binascii library. The problem arises when the byte string is written to form an image. The output of all the hexadecimal files is same. I will be grateful if someone provides me a solution.
Here is my code:
import binascii
import cv2
import os
from tkinter import *
from tkinter import filedialog
#Hide the root window that comes by default
root=Tk()
root.withdraw()
#Browse and select txt files
dir=[]
dir=filedialog.askopenfilenames(
initialdir="C:\Binaries\Hexadecimal_Text_Files",
title="Open Text file",
filetypes=(("Text Files", "*.txt"),)
)
#Reading data in txt files and decoding hexadecimal characters
for x in dir:
tf=open(x)#Open file
data=tf.read()#Read data in file
data=data.replace(' ','')#Remove whitespaces
data=data.replace('\n','')#Remove breaks in lines
data=binascii.a2b_hex(data)
tf.close()
#Extract txt filename without extension
pathname, extension = os.path.splitext(f"{x}")#Split path into filename and extenion
filename = pathname.split('/')#Get filename without txt extension
filepath=f"C:\Binaries\Images\{filename[-1]}.png"#Defining name of image file same as txt file
#Write data into image
with open(filepath, 'wb') as image_file:
img=image_file.write(data)
#Resizing Image
img=cv2.resize(img,(500,500))
cv2.imwrite(filepath,img)
Output:
I made my own version because I could not get yours to work, but if you want to make yours work, at least one problem with I found is with this line:
img=cv2.resize(img,(500,500))
by printing all the variables after the supposed "conversion", I found that your variable img in the previous line is not an image but the result of image_file.write(data) which returns the number of bytes written to the file and not the image itself, which is probably why it always prints the same image.
Here is my version
root=Tk()
root.withdraw()
file_path = filedialog.askopenfilename(
initialdir = "C:\Binaries\Images",
title = "Select Hexadecimal Text File",
filetypes = (("Text Files", "*.txt"),)
)
with open(file_path, "r") as hex_file:
hex_data = hex_file.read().replace("\n", "")
#replaces white spaces and new lines from file
binary_data = binascii.a2b_hex(hex_data)
#converts the hexadecimal data to binary
pathname, extension = os.path.splitext(file_path)
image_path = pathname + ".png"
#image path and format
with open(image_path, "wb") as image_file:
image_file.write(binary_data)
#writing the binary data to image file
img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
#if txt file is empty
if img is None:
print("Error: Image not loaded!")
else:
cv2.imshow("image", img)
#waits for key input and closes when pressing any key
cv2.waitKey(0)
cv2.destroyAllWindows()
I have converted the hexadecimal files into images by using numpy array and Pillow. Now I am getting different images.
import numpy as np
import binascii
import os
from PIL import Image as im
from tkinter import *
from tkinter import filedialog
# Hide the root window that comes by default
root = Tk()
root.withdraw()
# Browse and select txt files
dir = []
dir = filedialog.askopenfilenames(
initialdir="C:\Binaries\Folder_3",
title="Open Text file",
filetypes=(("Text Files", "*.txt"),)
)
# Reading data in txt files and decoding hexadecimal characters
for temp in dir:
tf = open(temp) # Open file
data = tf.read() # Read data in file
data= data.replace('\'','') #Remove label
data = data.replace(' ', '') # Remove whitespaces
data = data.replace('\n', '') # Remove breaks in lines
data = binascii.a2b_hex(data)
tf.close()
#Converting bytes array to numpy array
a = np.frombuffer(data, dtype='uint8')
#print(a) //Display array
#Finding optimal factor pair for size of image
x = len(a)
val1=0
val2=0
for i in range(1, int(pow(x, 1 / 2))+1):
if x % i == 0:
val1=i
val2=int(x / i)
#Converting 1-D to 2-D numpy array
a = np.reshape(a, (val1, val2))
#print(a) #Display 2-D array
#Writing array to image
data = im.fromarray(a)
# Split path into filename and extenion
pathname, extension = os.path.splitext(f"{temp}")
filename = pathname.split('/') # Get filename without txt extension
# Defining name of image file same as txt file
filepath = f"C:\Binaries\Images_3\{filename[-1]}.png"
#Resize image
data=data.resize((500,500))
#Saving image into path
data.save(filepath)
Context: My code fetches a set of coordinates from some png documents and later on performs some redaction in certain fields (it uses these coordinates for drawing rectangles in certain areas).
I want my final output to be a pdf with each redacted image as page. I can achieve this with fpdf package with no problem.
However, I intend to send this pdf file as email (base64 encoded) attachment. Is there any way to get the base64 string from fpdf output?
On top of that, can I use image binary string in fpdf image method?
See the redact_pdf method below (I placed some comments there to be more clear)
Code:
class Redaction:
def __init__(self,png_image_list,df_coordinates):
self.png_image_list = png_image_list
self.df_coordinates = df_coordinates
def _redact_images(self):
redacted_images_bin = []
for page_num,page_data in enumerate(self.png_image_list):
im_page = Image.open(io.BytesIO(page_data))
draw = ImageDraw.Draw(im_page)
df_filtered = self.df_coordinates[self.df_coordinates['page_number'] == page_num+1]
for index, row in df_filtered.iterrows():
x0 = row['x0'] * im_page.size[0]
y0 = row['y0'] * im_page.size[1]
x1 = row['x1'] * im_page.size[0]
y1 = row['y1'] * im_page.size[1]
x2 = row['x2'] * im_page.size[0]
y2 = row['y2'] * im_page.size[1]
x3 = row['x3'] * im_page.size[0]
y3 = row['y3'] * im_page.size[1]
coords = [x0,y0,x1,y1,x2,y2,x3,y3]
draw.polygon(coords,outline='blue',fill='yellow')
redacted_images_bin.append(im_page)
return redacted_images_bin
def redacted_pdf(self):
redacted_images = self._redact_images()
pdf = FPDF()
pdf.set_auto_page_break(0)
for index,img_redacted in enumerate(redacted_images):
img_redacted.save(f"image_{index}.png")
pdf.add_page()
pdf.image(f"image_{index}.png",w=210,h=297)
os.remove(f"image_{index}.png") # I would like to avoid file handling!
pdf.output("doc.pdf","F") # I would like to avoid file handling!
#return pdf #this is what I want, to return the pdf as base64 or binary
In documentation I found that you can get PDF as string using
pdf_string = pdf.output(dest='S')
so you can use standard module base64
import fpdf
import base64
pdf = fpdf.FPDF()
# ... add some elements ...
pdf_string = pdf.output(dest='S')
pdf_bytes = pdf_string.encode('utf-8')
base64_bytes = base64.b64encode(pdf_bytes)
base64_string = base64_bytes.decode('utf-8')
print(base64_string)
Result:
JVBERi0xLjMKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIKL1Jlc291cmNlcyAyIDAgUgovQ29udGVudHMgNCAwIFI+PgplbmRvYmoKNCAwIG9iago8PC9GaWx0ZXIgL0ZsYXRlRGVjb2RlIC9MZW5ndGggMTk+PgpzdHJlYW0KeMKcM1LDsMOiMsOQMzVXKMOnAgALw7wCEgplbmRzdHJlYW0KZW5kb2JqCjEgMCBvYmoKPDwvVHlwZSAvUGFnZXMKL0tpZHMgWzMgMCBSIF0KL0NvdW50IDEKL01lZGlhQm94IFswIDAgNTk1LjI4IDg0MS44OV0KPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1Byb2NTZXQgWy9QREYgL1RleHQgL0ltYWdlQiAvSW1hZ2VDIC9JbWFnZUldCi9Gb250IDw8Cj4+Ci9YT2JqZWN0IDw8Cj4+Cj4+CmVuZG9iago1IDAgb2JqCjw8Ci9Qcm9kdWNlciAoUHlGUERGIDEuNy4yIGh0dHA6Ly9weWZwZGYuZ29vZ2xlY29kZS5jb20vKQovQ3JlYXRpb25EYXRlIChEOjIwMjIwMjE3MjExMDE3KQo+PgplbmRvYmoKNiAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMSAwIFIKL09wZW5BY3Rpb24gWzMgMCBSIC9GaXRIIG51bGxdCi9QYWdlTGF5b3V0IC9PbmVDb2x1bW4KPj4KZW5kb2JqCnhyZWYKMCA3CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDE3NSAwMDAwMCBuIAowMDAwMDAwMjYyIDAwMDAwIG4gCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA4NyAwMDAwMCBuIAowMDAwMDAwMzU2IDAwMDAwIG4gCjAwMDAwMDA0NjUgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSA3Ci9Sb290IDYgMCBSCi9JbmZvIDUgMCBSCj4+CnN0YXJ0eHJlZgo1NjgKJSVFT0YK
As for image(): it needs filename (or url) and it can't work with string or io.BytesIO().
Eventually you may get source code and you can try to change it.
There is even request on GitHub: Support for StringIO objects as images
EDIT:
I found that there is fork fpdf2 which can use pillow.Image in image() - see fpdf2 Image
And in source code I found image() can also work with io.BytesIO()
Example code for fpdf2 (output() gives bytes instead of string)
import fpdf
import base64
from PIL import Image
import io
#print(fpdf.__version__)
pdf = fpdf.FPDF()
pdf.add_page()
pdf.image('lenna.png')
pdf.image('https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png')
f = open('lenna.png', 'rb')
pdf.image(f)
f = Image.open('lenna.png')
pdf.image(f)
f = open('lenna.png', 'rb')
b = io.BytesIO()
b.write(f.read())
pdf.image(b)
# save in file
pdf.output('output.pdf')
# get as bytes
pdf_bytes = pdf.output()
#print(pdf_bytes)
base64_bytes = base64.b64encode(pdf_bytes)
base64_string = base64_bytes.decode('utf-8')
print(base64_string)
Wikipedia: Lenna [image]
Test for writing in fpdf2
import fpdf
pdf = fpdf.FPDF()
pdf.add_page()
pdf.image('https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png')
# --- test 1 ---
pdf.output('output-test-1.pdf')
# --- test 2 ---
pdf_bytes = pdf.output()
with open('output-test-2.pdf', 'wb') as f: # it will close automatically
f.write(pdf_bytes)
# --- test 2 ---
pdf_bytes = pdf.output()
f = open('output-test-3.pdf', 'wb')
f.write(pdf_bytes)
f.close() # don't forget to close when you write
I have written a script to find image size and aspect ratio of all images in a directory along with their corresponding filepaths, I want to print dict values to csv file with following headers width,height,aspect-ratio and filepath
import os
import json
from PIL import Image
folder_images = "/home/user/Desktop/images"
size_images = dict()
def yocd(a,b):
if(b==0):
return a
else:
return yocd(b,a%b)
for dirpath, _, filenames in os.walk(folder_images):
for path_image in filenames:
if path_image.endswith(".png") or path_image.endswith('.jpg') or path_image.endswith('.JPG') or path_image.endswith('.jpeg'):
image = os.path.abspath(os.path.join(dirpath, path_image))
""" ImageFile.LOAD_TRUNCATED_IMAGES = True """
try:
with Image.open(image) as img:
img.LOAD_TRUNCATED_IMAGES = True
img.verify()
print('Valid image')
except Exception:
print('Invalid image')
img = False
if img is not False:
width, heigth = img.size
divisor = yocd(width, heigth)
w = str(int(width / divisor))
h = str(int(heigth / divisor))
aspectratio = w+':'+h
size_images[image] = {'width': width, 'heigth': heigth,'aspect-ratio':aspectratio,'filepath': image}
for k, v in size_images.items():
print(k, '-->', v)
with open('/home/user/Documents/imagesize.txt', 'w') as file:
file.write(json.dumps(size_images))```
You can add a (properly constructed) dict directly to a pandas.DataFrame. Then, DataFrames have a .to_csv() function.
Here are the docs:
Pandas: Create a DataFrame
Pandas: Write to CSV
Without dependencies (but you may have to tweak the formatting)
csv_sep = ';' # choose here wich field separatar you want
with open('your_csv', 'w') as f:
# header
f.write("width"+csv_sep"+height"+csv_sep"+aspect-ratio"+csv_sep+"filepath\n")
# data
for img in size_images:
fields = [img['width'], img['height'], img['aspect-ratio'], img['filepath']]
f.write(csv_sep.join(fields)+'\n')
I am trying to do a mass extraction of gps exif data, my code below:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
def get_exif_data(image):
exif_data = {}
info = image._getexif()
if info:
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
if decoded == "GPSInfo":
gps_data = {}
for t in value:
sub_decoded = GPSTAGS.get(t, t)
gps_data[sub_decoded] = value[t]
exif_data[decoded] = gps_data
else:
exif_data[decoded] = value
return exif_data
def _get_if_exist(data, key):
if key in data:
return data[key]
else:
pass
def get_lat_lon(exif_data):
gps_info = exif_data["GPSInfo"]
lat = None
lon = None
if "GPSInfo" in exif_data:
gps_info = exif_data["GPSInfo"]
gps_latitude = _get_if_exist(gps_info, "GPSLatitude")
gps_latitude_ref = _get_if_exist(gps_info, "GPSLatitudeRef")
gps_longitude = _get_if_exist(gps_info, "GPSLongitude")
gps_longitude_ref = _get_if_exist(gps_info, "GPSLongitudeRef")
if gps_latitude and gps_latitude_ref and gps_longitude and gps_longitude_ref:
lat = _convert_to_degrees(gps_latitude)
if gps_latitude_ref != "N":
lat = 0 - lat
lon = _convert_to_degrees(gps_longitude)
if gps_longitude_ref != "E":
lon = 0 - lon
return lat, lon
Code source
Which is run like:
if __name__ == "__main__":
image = Image.open("photo directory")
exif_data = get_exif_data(image)
print(get_lat_lon(exif_data)
This works fine for one photo, so I've used glob to iterate over all photos in a file:
import glob
file_names = []
for name in glob.glob(photo directory):
file_names.append(name)
for item in file_names:
if __name__ == "__main__":
image = Image.open(item)
exif_data = get_exif_data(image)
print(get_lat_lon(exif_data))
else:
pass
Which works fine, as long as every photo in the file is a) an image and b) has gps data. I have tried adding a pass in the _get_if_exist function as well as my file iteration, however, neither same to have had any impact and I'm still receiving KeyError: 'GPSInfo'
Any ideas on how I can ignore photos with no data or different file types?
A possible approach would be writing a small helper function that first checks, if the file is actually an image file and as a second step checks if the image contains EXIF data.
def is_metadata_image(filename):
try:
image = Image.open(filename)
return 'exif' in image.info
except OSError:
return False
I found that PIL does not work every time with .png files that do contain EXIF information when using _getexif(). So instead I check for the key exif in the info dictionary of an image.
I've tried this source code.
Simply you need to remove
gps_info = exif_data["GPSInfo"]
from the first line of get_lat_lon(exif_data) function, it works well for me.
I'm trying to check an image's dimension, before saving it. I don't need to change it, just make sure it fits my limits.
Right now, I can read the file, and save it to AWS without a problem.
output['pic file'] = request.POST['picture_file']
conn = myproject.S3.AWSAuthConnection(aws_key_id, aws_key)
filedata = request.FILES['picture'].read()
content_type = 'image/png'
conn.put(
bucket_name,
request.POST['picture_file'],
myproject.S3.S3Object(filedata),
{'x-amz-acl': 'public-read', 'Content-Type': content_type},
)
I need to put a step in the middle, that makes sure the file has the right size / width dimensions. My file isn't coming from a form that uses ImageField, and all the solutions I've seen use that.
Is there a way to do something like
img = Image.open(filedata)
image = Image.open(file)
#To get the image size, in pixels.
(width,height) = image.size()
#check for dimensions width and height and resize
image = image.resize((width_new,height_new))
I've done this before but I can't find my old snippet... so here we go off the top of my head
picture = request.FILES.get['picture']
img = Image.open(picture)
#check sizes .... probably using img.size and then resize
#resave if necessary
imgstr = StringIO()
img.save(imgstr, 'PNG')
imgstr.reset()
filedata = imgstr.read()
The code bellow creates the image from the request, as you want:
from PIL import ImageFile
def image_upload(request):
for f in request.FILES.values():
p = ImageFile.Parser()
while 1:
s = f.read(1024)
if not s:
break
p.feed(s)
im = p.close()
im.save("/tmp/" + f.name)