Reputation: 31
I have some code that uses LSB steganography to hide encrypted data in the frames of a video. The main issue I am having is I don't know what I can do to make the video more resistant against youtube compression/compression in general. I also can't find much better libraries for steganography that would help.
In terms of what I tried, I thought about using ECC but the library I use "stegano" doesn't really have anything apart from "red" and "lsb". I also thought about editing the video that gets generated to be one big frame of a random color to maybe make the compression not as effective but It still doesn't work.
In terms of what I mean by "Youtube compression", The video would ultimately be uploaded to youtube unlisted and then I can download it and reveal the hidden encrypted data and go from there.
The following is the code I currently use that works without compression.
This code works just fine and will do exactly what I want. The issue is when I upload the video to youtube and download the video from youtube that it breaks.
import cv2
import numpy as np
import secrets
import string
import os
import beaupy #Teminal User Interface | TUI. aka nice menu
from pystyle import Colors, Colorate
import math
from tqdm import tqdm
from stegano import lsb #what I use to hide and reveal data.
import shutil
import magic
import gcm
import base64
from pytube import YouTube #downloading video from youtube
from subprocess import call,STDOUT
def clear():
os.system('clear||cls')
def get_file_type(bytes_data):
mime = magic.Magic(mime=True)
file_type = mime.from_buffer(bytes_data)
return file_type.split('/')[1]
def generate_filename():
alphabet = string.ascii_letters + string.digits
filename = ''.join(secrets.choice(alphabet) for i in range(12)) + ".mp4"
return filename
def generate_video():
output_filename = generate_filename()
duration=7
width=640
height=480
fps=30
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_filename, fourcc, fps, (width, height))
# Generate "random" background color | BGR from left to right
background_color = tuple(secrets.randbelow(256) for i in range(3)) #(0, 0, 255) = red
# Create frames with "random" background color
for i in tqdm(range(int(fps * duration)), desc="Creating video..."):
frame = np.zeros((height, width, 3), dtype=np.uint8)
frame[:, :, :] = background_color
out.write(frame)
out.release()
cv2.destroyAllWindows()
clear()
return output_filename
def clean_tmp(path=".tmp"):
if os.path.exists(path):
shutil.rmtree(path)
print("[INFO] tmp files have been cleaned up.\n")
def split_string(s_str,count=25): #up to how many frames the data is embedded into.
per_c=math.ceil(len(s_str)/count)
c_cout=0
out_str=''
split_list=[]
for s in s_str:
out_str+=s
c_cout+=1
if c_cout == per_c:
split_list.append(out_str)
out_str=''
c_cout=0
if c_cout!=0:
split_list.append(out_str)
return split_list
def frame_extraction(video):
if not os.path.exists(".tmp"):
os.makedirs(".tmp")
temp_folder=".tmp"
print("[INFO] tmp directory has been created")
vidcap = cv2.VideoCapture(video)
count = 0
while True:
success, image = vidcap.read()
if not success:
break
cv2.imwrite(os.path.join(temp_folder, "{:d}.png".format(count)), image)
count += 1
def encode_video(file_name):
clear()
key_data = beaupy.prompt("Data for key gen")
if not key_data:
clear()
return None
key_data = key_data.encode()
clear()
eKey = gcm.keygen(key_data) #Returns random bytes from Argon2id and will return "None" if what's provided is less than 100 characters.
if not eKey:
return None
save_me = base64.b64encode(eKey) #for saving eKey to decrypt later.
input(f'Save this key so you can decrypt and decode later: {save_me.decode()}\n\nPress "enter" to contine...')
clear()
with open(file_name, 'rb') as rb:
data = rb.read()
data_enc = gcm.stringE(enc_data=data, key=eKey) #encrypts data and returns base64 encoded string
video_file = generate_video()
frame_extraction(video_file)
root=".tmp/"
split_string_list = split_string(data_enc)
for i in range(0, len(split_string_list)):
f_name=f"{root}{i}.png"
secret_enc=lsb.hide(f_name, split_string_list[i])
secret_enc.save(f_name)
print(f"[INFO] frame {f_name} holds {split_string_list[i]}")
output_vid = '.tmp_vid.mp4'
call(["ffmpeg", "-i", ".tmp/%d.png" , "-vcodec", "png", output_vid, "-y"], stdout=open(os.devnull, "w"), stderr=STDOUT)
cwd = os.getcwd()
os.walk(f".tmp/{output_vid}", cwd)
clean_tmp()
os.rename(output_vid, video_file)
def decode_video(video, b64_enc_key):
frame_extraction(video)
secret=[]
root=".tmp/"
for i in range(len(os.listdir(root))):
f_name=f"{root}{i}.png"
try:
secret_dec=lsb.reveal(f_name)
print(f"Found data in: {f_name}. Data: {secret_dec}")
except Exception:
break
secret.append(secret_dec)
result = ''.join([i for i in secret]) # base64 string
clean_tmp()
dKey = base64.b64decode(b64_enc_key)
str_dcr = gcm.stringD(dcr_data=result, key=dKey)
gcm.clear()
return str_dcr
# ... (if __name__ == '__main__': code and the creation of the beaupy menu options would be below this line)
What I am looking for help with is how to deal with compression effectively and simply using LSB/the code shown above. And if the "stegano" library isn't good enough then recommending any other better library or methods would be very appreciated. If anyone has other documentation to share, that'll help too.
Feel free to help write a function or 2 that would aid me in handling the compression issue of videos.
Also..if LSB just will not work no matter what then what would be the better option? And can you provide links/documentation with examples for me to work off of that I could use instead. (that'd work in the context of the code shown above and how I use multiple frames)
Upvotes: 1
Views: 362
Reputation: 345
YouTube recompresses all the videos uploaded to it. Although watermarks are designed to survive recompression, basic steganography (such as LSB) is completely disrupted by this.
Robust steganography which would survive recompression is a field of active research.
I am afraid you will have to wait a long time before there exist a good, established solution to your problem.
Upvotes: 0