Ori
Ori

Reputation: 31

Python3 youtube compression resistance using LSB steganography

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

Answers (1)

Martin Benes
Martin Benes

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.

  • Fan, Zhang, Zhao: Adaptive QIM With Minimum Embedding Cost for Robust Video Steganography on Social Networks. 2022.
  • Mstfa, Elleithy, Abdelfattah: A Robust and Secure Video Steganography Method in DWT-DCT Domains Based on Multiple Object Tracking and ECC. 2017.

I am afraid you will have to wait a long time before there exist a good, established solution to your problem.

Upvotes: 0

Related Questions