diff --git a/Application/img/example.jpg b/Application/img/example.jpg new file mode 100644 index 0000000..1eeb163 Binary files /dev/null and b/Application/img/example.jpg differ diff --git a/Application/img/file_example_PNG_500kB.png b/Application/img/file_example_PNG_500kB.png new file mode 100644 index 0000000..0218b6e Binary files /dev/null and b/Application/img/file_example_PNG_500kB.png differ diff --git a/Application/img/sample_640×426.bmp b/Application/img/sample_640×426.bmp new file mode 100644 index 0000000..ea88c92 Binary files /dev/null and b/Application/img/sample_640×426.bmp differ diff --git a/Application/lib/steganography/img_steg.py b/Application/lib/steganography/img_steg.py new file mode 100644 index 0000000..326ed32 --- /dev/null +++ b/Application/lib/steganography/img_steg.py @@ -0,0 +1,191 @@ +import cv2 +import numpy as np + + +class img_steg: + def __init__(self, image_name: str = "image.png", bit_to_hide: list[int] = None) -> None: + """ + Initialize the class + :param image_name: Name of the image to encode or decode + :type image_name: str + :param bit_to_hide: List of bit position to hide the data (LSB is 1, MSB is 8) + :type bit_to_hide: list[int] + """ + self.image_name = image_name + self.bit_to_hide = [8 - bit_pos for bit_pos in bit_to_hide] if bit_to_hide else [1] # Default is LSB + self.delimiter = "abc-123-a==" + + def encode(self, secret_data: str = "Hello World") -> np.ndarray: + """ + Encode the secret data in the image + :param secret_data: Data to hide in the image + :type secret_data: str + :return: Encoded Image + :rtype: np.ndarray + """ + image = cv2.imread(self.image_name) # read image + n_bytes = image.shape[0] * image.shape[1] * 3 // 8 # Max bytes to encode + print("[*] Maximum bytes to encode:", n_bytes) + secret_data += self.delimiter # Add delimiter at the end of data + if len(secret_data) > n_bytes: + raise ValueError("[!] Insufficient bytes, need a bigger image or less data") + print("[*] Encoding Data...") + + # Convert bit to hide position + bit_to_hide = self.bit_to_hide + data_index = 0 + binary_secret_data = self.to_bin(secret_data) # Convert data to binary + data_len = len(binary_secret_data) # size of data to hide + + print(f"[+] Size of data to hide: {data_len}") + print("[+] Starting encoding...") + + for row in image: + for pixel in row: + r, g, b = self.to_bin(pixel) # Convert RGB Values to binary format + for bit_pos in bit_to_hide: + if data_index < data_len: + r = list(r) + r[bit_pos] = binary_secret_data[ + data_index] # hide data into specified bit position of red pixel + pixel[0] = int(''.join(r), 2) + data_index += 1 + if data_index < data_len: + g = list(g) + g[bit_pos] = binary_secret_data[ + data_index] + pixel[1] = int(''.join(g), 2) + data_index += 1 + if data_index < data_len: + b = list(b) + b[bit_pos] = binary_secret_data[ + data_index] + pixel[2] = int(''.join(b), 2) + data_index += 1 + if data_index >= data_len: + break + pixel[0] = int(''.join(r), 2) # convert modified binary back to integer for red pixel + pixel[1] = int(''.join(g), 2) # convert modified binary back to integer for green pixel + pixel[2] = int(''.join(b), 2) # convert modified binary back to integer for blue pixel + + if data_index >= data_len: + break + + print(f"[+] Encoding completed") + + return image + + def decode(self) -> str: + """ + Decode the data hidden in the image + + :return: Decoded Data: String + """ + print("[+] Decoding...") + image = cv2.imread(self.image_name) # read image + binary_data = "" + bit_to_hide = self.bit_to_hide + + print(f"[+] Extracting data from image...") + for row in image: + for pixel in row: + r, g, b = self.to_bin(pixel) + for bit_pos in bit_to_hide: + binary_data += r[bit_pos] # retrieve data from specified bit position of red pixel + binary_data += g[bit_pos] # retrieve data from specified bit position of green pixel + binary_data += b[bit_pos] # retrieve data from specified bit position of blue pixel + + print(f"[+] Forming binary data completed") + print(f"[+] Decoding binary data...") + + # Split by 8 bits + all_bytes = [binary_data[i: i + 8] for i in range(0, len(binary_data), 8)] + # Convert from bits to characters + decoded_data = "" + for byte in all_bytes: + decoded_data += chr(int(byte, 2)) + if decoded_data[-len(self.delimiter):] == self.delimiter: + break + + print(f"[+] Decoding completed") + return decoded_data[:-len(self.delimiter)] + + def to_bin(self, data: str) -> str | list[str]: + """ + Convert data to binary format as string + + :param data: Data to convert + :type data: str + :return: Binary Data + :rtype: str | list[str] + """ + if isinstance(data, str): + return ''.join([format(ord(i), "08b") for i in data]) + elif isinstance(data, bytes) or isinstance(data, np.ndarray): + return [format(i, "08b") for i in data] + elif isinstance(data, int) or isinstance(data, np.unit8): + return format(data, "08b") + else: + raise TypeError("Type not supported") + + def from_bin(self, data: str) -> str: + """ + # UNUSED + Convert binary `data` back to the original format + + :param data: Binary Data + :type data: str + :return: Original Data + :rtype: str + """ + return ''.join([chr(int(data[i:i + 8], 2)) for i in range(0, len(data), 8)]) + + +def main(): + # Variables + image_name = "test.bmp" + encoded_image_name = "encoded_image.bmp" + secret_data = "" + # with open("../Txt_Steg/secret_data.txt", "r") as f: + # secret_data = f.read() + + # Generate random bit positions to hide data into image for testing + bit_to_hide = np.random.choice(range(1, 9), np.random.randint(1, 9), replace=False) + bit_to_hide = list(bit_to_hide) + bit_to_hide.sort() + print(f"Bits to hide: {bit_to_hide}") + + print("Welcome to Image Steganography") + + # Encode the data into the image + encoded_image = img_steg(image_name=image_name, bit_to_hide=bit_to_hide).encode(secret_data) + cv2.imwrite(encoded_image_name, encoded_image) + + # Decode the data from the image + decoded_data = img_steg(image_name=encoded_image_name, bit_to_hide=bit_to_hide).decode() + print("Decoded Data:", decoded_data) + + +if __name__ == "__main__": + main() + +############################## +# _ _ +# | | | |___ __ _ __ _ ___ +# | | | / __|/ _` |/ _` |/ _ \ +# | |_| \__ \ (_| | (_| | __/ +# \___/|___/\__,_|\__, |\___| +# |___/ +############################## +# +# Initialise class with image name and bit to hide +# +# image_name = image name with extension (String) +# bit_to_hide = [1, 2, 3] (Please Sort the list in ascending order) +# +# To encode: +# encoded_image = img_steg(image_name, bit_to_hide).encode(secret_data) +# +# To decode: +# decoded_data = img_steg(image_name, bit_to_hide).decode() +# Both are string types diff --git a/Application/lsb_rep.py b/Application/lsb_rep.py index b0b63f4..68cfabb 100644 --- a/Application/lsb_rep.py +++ b/Application/lsb_rep.py @@ -1,48 +1,74 @@ -from flask import Flask, render_template, request, redirect +from flask import Flask, render_template, request, redirect, session, send_from_directory +from lib.steganography import img_steg +import cv2 + +WORKING_PATH = 'Application/upload/' + app = Flask(__name__, template_folder='views') +app.secret_key = 'b3a5e8d11fb3d8d3647b6cf2e51ad768' + +def session_clear(): + if len(session) > 0: + session.clear() + @app.route('/') def modeSelection(): + session_clear() return render_template('mode_selection.html') @app.route('/encode') def encode(): + session_clear() return render_template('encode.html') @app.route("/encoding", methods=['POST']) def encoding(): file = request.files['origin'] + b2c = [int(request.form['b2c'])] payload = request.form['payload'] if file.filename != "": - file.save('upload/' + file.filename) - - # run the encoding function - #return encoded file to the result below + file.save(WORKING_PATH + file.filename) + steg = img_steg.img_steg(WORKING_PATH + file.filename, b2c).encode(payload) + cv2.imwrite(WORKING_PATH + "encoded_" + file.filename, steg) + session['image'] = file.filename + session['image2'] = "encoded_" + file.filename return redirect("/encode_result") @app.route('/encode_result') def encode_result(): - return render_template("encode_result.html") - + if len(session) > 0: + return render_template("encode_result.html", image=session.get("image"), image2=session.get('image2')) + else: + return redirect("/encode") + @app.route('/decode') def decode(): + session_clear() return render_template('decode.html') @app.route("/decoding", methods=['POST']) def decoding(): file = request.files['encoded_file'] - + b2c = [int(request.form['b2c'])] if file.filename != "": - file.save('upload/' + file.filename) - - # run the decoding function - #return decoded payload to the result below + file.save(WORKING_PATH + file.filename) + payload = img_steg.img_steg(WORKING_PATH + file.filename, b2c).decode() + session["payload"] = payload + session["image"] = file.filename return redirect("/decode_result") @app.route('/decode_result') def decode_result(): - return render_template("decode_result.html") + if len(session) > 0: + return render_template("decode_result.html", payload=session.get("payload"), image=session.get("image")) + else: + return redirect("/decode") + +@app.route('/upload/') +def upload(filename): + return send_from_directory('upload', filename) if __name__ == "__main__": app.debug = True diff --git a/Application/upload/encoded_example.jpg b/Application/upload/encoded_example.jpg new file mode 100644 index 0000000..57c63a9 Binary files /dev/null and b/Application/upload/encoded_example.jpg differ diff --git a/Application/upload/encoded_file_example_PNG_500kB.png b/Application/upload/encoded_file_example_PNG_500kB.png new file mode 100644 index 0000000..f548cac Binary files /dev/null and b/Application/upload/encoded_file_example_PNG_500kB.png differ diff --git a/Application/upload/example.jpg b/Application/upload/example.jpg new file mode 100644 index 0000000..1eeb163 Binary files /dev/null and b/Application/upload/example.jpg differ diff --git a/Application/upload/file_example_PNG_500kB.png b/Application/upload/file_example_PNG_500kB.png new file mode 100644 index 0000000..0218b6e Binary files /dev/null and b/Application/upload/file_example_PNG_500kB.png differ diff --git a/Application/upload/sample_640×426.bmp b/Application/upload/sample_640×426.bmp new file mode 100644 index 0000000..ea88c92 Binary files /dev/null and b/Application/upload/sample_640×426.bmp differ diff --git a/Application/views/decode.html b/Application/views/decode.html index 76e4ad3..db8474c 100644 --- a/Application/views/decode.html +++ b/Application/views/decode.html @@ -66,6 +66,9 @@
Drag and drop an image here or click to browse
+
+ +
diff --git a/Application/views/decode_result.html b/Application/views/decode_result.html index e69de29..b1a903f 100644 --- a/Application/views/decode_result.html +++ b/Application/views/decode_result.html @@ -0,0 +1,21 @@ + + + + + + + RESULT + + +
+
+

Decoded

+ Image Not Found +
+
+

Secret Text:

+

{{ payload }}

+
+
+ + \ No newline at end of file diff --git a/Application/views/encode.html b/Application/views/encode.html index d12a071..895a47d 100644 --- a/Application/views/encode.html +++ b/Application/views/encode.html @@ -42,6 +42,7 @@ } .form-container input[type="text"], + .form-container input[type="number"], .form-container input[type="file"], .form-container input[type="submit"] { display: inline-block; @@ -68,7 +69,8 @@
- + +
diff --git a/Application/views/encode_result.html b/Application/views/encode_result.html index e69de29..fee870e 100644 --- a/Application/views/encode_result.html +++ b/Application/views/encode_result.html @@ -0,0 +1,25 @@ + + + + + + + RESULT + + + +
+ +
+

Original

+ Image Not Found +
+
+

Encoded

+ Image Not Found +
+ +
+ + + \ No newline at end of file