├── .idea ├── deployment.xml ├── markdown-navigator.xml ├── vcs.xml └── webServers.xml ├── README.md ├── data ├── chef.mp4 ├── recovered-text.txt └── text-to-hide.txt ├── functions.py ├── images ├── Selection_032.png └── Selection_033.png └── main.py /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/webServers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Caesar-Cipher-Video-Steganography(CCVS) 2 | 3 | CCVS is a simple program written in python to hide encrypted text using simple caesar cipher in video frames, the encryption algorithm could be easily changed because the cipher algorithm is loosly coupled. 4 | 5 | Dependencies : 6 | 7 | * ffmpeg 8 | * opencv 9 | * PIL 10 | * pyfiglet 11 | 12 | Usage : 13 | 14 | * write your plain text in text-to-hide.txt in data folder 15 | 16 | ```bash 17 | python main.py 18 | 19 | ``` 20 | you'll be greet with the main menu to encrypt the plain text and hide it in the video or decrypt and retreive the plain text from the video 21 | 22 | ![main menu](images/Selection_032.png) 23 | 24 | ![main menu](images/Selection_033.png) 25 | 26 | For decryption plain text will be put in recovered-text.txt in data folder 27 | 28 | Warning : 29 | * For encryption the video will be converted into raw .mov video to make sure data in the video won't change after re encoding and decryption, and make sure you got enough space (For comparison : 2 minute of 720p video could result in 2GB raw .mov video) 30 | * temp folder will be created to dump temporary extracted frame , audio, and video. 31 | * the temp foldder will be created in case of extraction either decryption 32 | -------------------------------------------------------------------------------- /data/chef.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r9ht/Caesar-Cipher-Video-Steganography/a51dae6259192a0fb943215a6c7259a48c574c13/data/chef.mp4 -------------------------------------------------------------------------------- /data/recovered-text.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r9ht/Caesar-Cipher-Video-Steganography/a51dae6259192a0fb943215a6c7259a48c574c13/data/recovered-text.txt -------------------------------------------------------------------------------- /data/text-to-hide.txt: -------------------------------------------------------------------------------- 1 | 2 | Shin Nishimura – Fukafunkacid (SEGO ‘Miyabimania’ Remix), Plus Tokyo, 2008 3 | Da Others – Viva La Vida! (SEGO ‘Mi Vida Loca’ Remix), Pilot6 Recordings, 2009 4 | Mehdi D Vs. TheCrosh – Roda (SEGO ‘Roda Gila’ Remix), Cutz, 2009 5 | Tarot – Substance (SEGO Remix), TKC Music, 2009 6 | Alejandro Roman – Un Segundo De Tu Vida (SEGO Remix), Cutz, 2009 7 | Alejandro Roman – El Mundo Del Infinito (SEGO Remix), Cutz, 2009 8 | Andrea Saenz & Sebastian Reza – Sevilla! (SEGO Remix), Nine Records, 2009 9 | Tatsu Mihara – Comet (SEGO ‘Minimal Object’ Remix), Plus Tokyo, 2009 10 | Mhonoral – Voison (SEGO ‘Deus Ex Machina’ Remix), Plus Tokyo, 2009 11 | J.NO – No More Breath (SEGO Remix), BDivision, 2009 12 | Timmo – Shumminal (SEGO ‘Gitar Karatan’ Remix + Dub), BDivision, 2009 13 | Mario Roberti – Slave (Sego Birahi Tinggi Remix), BDivision, 2009 14 | Simone Barbieri Viale – Sunset (SEGO Remix), Cutz, 2009 15 | Simone Barbieri Viale – City Jungle (SEGO Remix), Cutz, 2009 16 | Diego Poblets – Massive Shock (SEGO Remix), Cutz, 2009 17 | Baramuda & Ginkel – Do It Wrong (Sego ‘Hit The Brick Wall’ Remix), Cutz, 2009 18 | Ilya Mosolov, Spacebird (SEGO Re-Busted), Cold Busted, 2009 19 | Ilya Mosolov, Light of Paradise (SEGO Re-Busted), Cold Busted, 2009 20 | Ilya Mosolov, Pax (SEGO Re-Busted), Cold Busted, 2009 21 | So Hattori, Tarot – No Limit (SEGO ‘Nambah Dua’ Remix), TKC Music, 2009 22 | Grunjah – Tighten Your Wings (SEGO ‘Too Tight’ Remix), Quimika, 2009 23 | Marlon D & Pete Lopez – She’s Obsessed (SEGO Remix), TKC Music, 2009 24 | So Hattori & Tarot – No Limit (SEGO ‘Nambah Satu’ Remix), TKC Music, 2010 25 | Shin Nishimura – Phycedelic Technelic (SEGO Remix), Plus Tokyo, 2010 26 | SEGO – Magic Buffer (SEGO Remix), TKC Music, 2010 27 | -------------------------------------------------------------------------------- /functions.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import shutil,cv2,os 3 | 4 | # references : 5 | # 6 | # https://www.daniweb.com/programming/software-development/code/485063/hide-private-message-in-an-image-python 7 | # http://zulko.github.io/blog/2013/09/27/read-and-write-video-frames-in-python-using-ffmpeg/ 8 | # http://tsaith.github.io/combine-images-into-a-video-with-python-3-and-opencv-3.html 9 | 10 | # Fauzanil Zaki , 2017 11 | 12 | def frame_extract(video): 13 | temp_folder = 'temp' 14 | try: 15 | os.mkdir(temp_folder) 16 | except OSError: 17 | remove(temp_folder) 18 | os.mkdir(temp_folder) 19 | 20 | vidcap = cv2.VideoCapture("data/"+str(video)) 21 | count = 0 22 | 23 | while True: 24 | success, image = vidcap.read() 25 | if not success: 26 | break 27 | cv2.imwrite(os.path.join(temp_folder, "{:d}.png".format(count)), image) 28 | count += 1 29 | 30 | 31 | def remove(path): 32 | """ param could either be relative or absolute. """ 33 | if os.path.isfile(path): 34 | os.remove(path) # remove the file 35 | elif os.path.isdir(path): 36 | shutil.rmtree(path) # remove dir and all contains 37 | else: 38 | raise ValueError("file {} is not a file or dir.".format(path)) 39 | 40 | 41 | 42 | def split2len(s, n): 43 | def _f(s, n): 44 | while s: 45 | yield s[:n] 46 | s = s[n:] 47 | return list(_f(s, n)) 48 | 49 | 50 | 51 | 52 | def caesar_ascii(char,mode,n): 53 | if mode == "enc" : 54 | ascii = ord(char) 55 | return chr((ascii + n) % 128) 56 | elif mode == "dec" : 57 | ascii = ord(char) 58 | return chr((ascii - n) % 128) 59 | 60 | 61 | def encode_frame(frame_dir,text_to_hide,caesarn): 62 | 63 | 64 | 65 | # open the text file 66 | 67 | text_to_hide_open = open(text_to_hide, "r") 68 | text_to_hide = repr(text_to_hide_open.read()) 69 | 70 | # split text to max 255 char each 71 | 72 | text_to_hide_chopped = split2len(text_to_hide,255) 73 | 74 | for text in text_to_hide_chopped: 75 | length = len(text) 76 | chopped_text_index = text_to_hide_chopped.index(text) 77 | frame = Image.open(str(frame_dir) +"/" + str(chopped_text_index+1) + ".png") 78 | 79 | if frame.mode != "RGB": 80 | print("Source frame must be in RGB format") 81 | return False 82 | 83 | # use copy of the file 84 | 85 | encoded = frame.copy() 86 | width, height = frame.size 87 | 88 | index = 0 89 | a = object 90 | for row in range(height): 91 | for col in range(width): 92 | r,g,b = frame.getpixel((col,row)) 93 | 94 | # first value is length of the message per frame 95 | if row == 0 and col == 0 and index < length: 96 | asc = length 97 | if text_to_hide_chopped.index(text) == 0 : 98 | total_encoded_frame = len(text_to_hide_chopped) 99 | else: 100 | total_encoded_frame = g 101 | elif index <= length: 102 | c = text[index -1] 103 | # put the encypted character into ascii value 104 | asc = ord(caesar_ascii(c,"enc",caesarn)) 105 | total_encoded_frame = g 106 | else: 107 | asc = r 108 | total_encoded_frame = g 109 | encoded.putpixel((col,row),(asc,total_encoded_frame,b)) 110 | index += 1 111 | if encoded: 112 | encoded.save(str(frame_dir)+"/"+str(chopped_text_index+1) + ".png",compress_level=0) 113 | 114 | def decode_frame(frame_dir,caesarn): 115 | 116 | #take the first frame to get width, height, and total encoded frame 117 | 118 | # first_frame = Image.open(str(frame_dir) + "/0.jpg") 119 | first_frame = Image.open(str(frame_dir)+ "/" + "1.png") 120 | r,g,b = first_frame.getpixel((0,0)) 121 | total_encoded_frame = g 122 | msg = "" 123 | for i in range (1,total_encoded_frame+1): 124 | frame = Image.open(str(frame_dir) + "/" + str(i) + ".png") 125 | width, height = frame.size 126 | index = 0 127 | for row in range(height): 128 | for col in range(width): 129 | try : 130 | r,g,b = frame.getpixel((col,row)) 131 | except ValueError: 132 | 133 | # for some ong a(transparancy) is needed 134 | r, g, b, a = frame.getpixel((col, row)) 135 | if row == 0 and col == 0: 136 | length = r 137 | elif index <= length: 138 | # put the decrypted character into string 139 | msg += caesar_ascii(chr(r),"dec",caesarn) 140 | index +=1 141 | #remove the first and the last quote 142 | msg = msg[1:-1] 143 | recovered_txt = open("data/recovered-text.txt", "w") 144 | recovered_txt.write(str(msg.decode('string_escape'))) 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /images/Selection_032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r9ht/Caesar-Cipher-Video-Steganography/a51dae6259192a0fb943215a6c7259a48c574c13/images/Selection_032.png -------------------------------------------------------------------------------- /images/Selection_033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r9ht/Caesar-Cipher-Video-Steganography/a51dae6259192a0fb943215a6c7259a48c574c13/images/Selection_033.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from pyfiglet import Figlet 2 | from functions import * 3 | from subprocess import call,STDOUT 4 | import os 5 | 6 | # Fauzanil Zaki , 2017 7 | # feel free to use 8 | 9 | if __name__ == '__main__': 10 | 11 | # cool boi 12 | 13 | f = Figlet(font='slant') 14 | print(f.renderText("CCVS")) 15 | print("CaesarCipherVideoSteganography") 16 | print("") 17 | # print("By : ") 18 | # print("") 19 | # print("===Fauzanil Zaki===") 20 | # print("====Galih Dea P.===") 21 | # print("====Johan Eko P.===") 22 | # print("===Wiladhianty Y.==") 23 | # print("") 24 | 25 | print("Menu :") 26 | print("") 27 | print("(a) Encypt & Merge into Video") 28 | print("(b) Decrypt & Get the plain text") 29 | print("-----------------------") 30 | choice = raw_input("(!) Choose option : ") 31 | 32 | if choice == "a": 33 | # refresh terminal 34 | call(["clear"]) 35 | 36 | print(f.renderText("Encrypt")) 37 | print("----------------------------------------") 38 | file_name = raw_input("(1) Video file name in the data folder ? : ") 39 | 40 | try: 41 | caesarn = int(raw_input("(2) Caesar cypher n value ? : ")) 42 | except ValueError: 43 | print("-----------------------") 44 | print("(!) n is not an integer ") 45 | exit() 46 | 47 | try: 48 | open("data/" + file_name) 49 | except IOError: 50 | print("-----------------------") 51 | print("(!) File not found ") 52 | exit() 53 | 54 | print("-----------------------") 55 | print("(-) Extracting Frame(s)") 56 | frame_extract(str(file_name)) 57 | print("(-) Extracting audio") 58 | # using system call 59 | #ffmpeg -i data/chef.mp4 -q:a 0 -map a temp/audio.mp3 -y 60 | # 2>/dev/null for supressing the output from ffmpeg 61 | call(["ffmpeg", "-i", "data/" + str(file_name), "-q:a", "0", "-map", "a", "temp/audio.mp3", "-y"],stdout=open(os.devnull, "w"), stderr=STDOUT) 62 | # useless 63 | print("(-) Reading text-to-hide.txt") 64 | print("(-) Encrypting & appending string into frame(s) ") 65 | encode_frame("temp", "data/text-to-hide.txt", caesarn) 66 | print("(-) Merging frame(s) ") 67 | #ffmpeg -i temp/%d.png -vcodec png data/enc-filename.mov 68 | call(["ffmpeg", "-i", "temp/%d.png" , "-vcodec", "png", "temp/video.mov", "-y"],stdout=open(os.devnull, "w"), stderr=STDOUT) 69 | 70 | print("(-) Optimizing encode & Merging audio ") 71 | # ffmpeg -i temp/temp-video.avi -i temp/audio.mp3 -codec copy data/enc-chef.mp4 -y 72 | call(["ffmpeg", "-i", "temp/video.mov", "-i", "temp/audio.mp3", "-codec", "copy","data/enc-" + str(file_name)+".mov", "-y"],stdout=open(os.devnull, "w"), stderr=STDOUT) 73 | print("(!) Success , output : enc-" + str(file_name)+".mov") 74 | 75 | elif choice == "b" : 76 | # refresh terminal 77 | call(["clear"]) 78 | 79 | print(f.renderText("Decrypt")) 80 | print("----------------------------------------") 81 | file_name = raw_input("(1) Video file name in the data folder ? : ") 82 | 83 | try: 84 | caesarn = int(raw_input("(2) Caesar cypher n value ? : ")) 85 | except ValueError: 86 | print("-----------------------") 87 | print("(!) n is not an integer ") 88 | exit() 89 | 90 | try: 91 | open("data/" + file_name) 92 | except IOError: 93 | print("-----------------------") 94 | print("(!) File not found ") 95 | exit() 96 | 97 | print("-----------------------") 98 | print("(-) Extracting Frame(s)") 99 | frame_extract(str(file_name)) 100 | print("(-) Decrypting Frame(s)") 101 | decode_frame("temp",caesarn) 102 | #useless 103 | print("(-) Writing to recovered-text.txt") 104 | print("(!) Success") 105 | 106 | 107 | else: 108 | exit() 109 | 110 | --------------------------------------------------------------------------------