وبلاگ فربد | Farbod Blog

توی این وبلاگ راجع به پروژه هام صحبت می کنم

مخفی کردن اطلاعات در عکس با استفاده از پایتون

  • ۱۰۱۰

سلام دوستان :)

توی پست امروز قراره نحوه ساخت یک اسکریپت پایتونی که یک سری اطلاعات رو داخل یک عکس مخفی می کنه رو ببینیم.

خب اول از همه باید بهتون توضیح بدم که اینکار اصلا چطور انجام میشه.

Steganography چیست؟

کاری که قراره توی این پست بکنیم اسمش Steganography هست که در واقع از واژه های یونانی steganos به معنای مخفی و graphe به معنای نوشتن گرفته شده.

هکر ها معمولا از این تکنیک برای مخفی کردن پیام های محرمانه یا داده و یا مخفی کردن بد افزار در قالب فایل های ویدیویی، فایل های صوتی، فایل های متنی و... استفاده می کنن.

 توی این پست ما یک اسکریپت پایتون می نویسیم که با استفاده از تکنیک Least Significant Bit اطلاعاتمون رو داخل یک فایل عکس مخفی کنیم.

 

Least Significant Bit چیست؟

Least Significant Bit یا LSB تکنیکی هست که آخرین بیت هر pixel دستکاری میشه و با بیت اطلاعات مورد نظرمون عوض میشه.

این تکنیک فقط روی عکس های Lossless-compression یا فشرده سازی بی اتلاف به کار میاد، یعنی اینکه عکس در قالب یک فرمت فشرده سازی شده ذخیره میشه که البته از کیفیت عکس کم نمیشه.

PNG, TIFF و BMP مثال هایی از فرمت های فشرده سازی بی اتلاف هستن.

 

همونطوری که احتمالا میدونید عکس ها دارای چندین پیکسل هستن که هر کدومشون سه تا مقدار دارن: قرمز، سبز و آبی. این مقادیر از عدد 0 تا 250 یا اعداد 8 بیتی هستن.

 

برای یک مثال فرض کنید من میخوام کلمه "hi" رو داخل یک عکس با این پیکسل ها مخفی کنم:

[[(225, 12, 99), (155, 2, 50), (99, 51, 15), (15, 55, 22)],
[(155, 61, 87), (63, 30, 17), (1, 55, 19), (99, 81, 66)],
[(219, 77, 91), (69, 39, 50), (18, 200, 33), (25, 54, 190)]]

با نگاه کردن به جدول ASCII میتونیم متن "hi" رو به عدد decimal یا ده دهی تبدیل کنیم و بعد هم به عدد binary یا مبنای دو تبدیلش کنیم:

0110100 0110101

حالا تمام پیکسل های عکس رو به binary تبدیل میکنیم.

مثلا 225 میشه 1110000حالا آخرین بیت(bold شده) رو با اولین بیت متنمون عوض میکنیم که میشه 1110000یعنی 224.

بعدش همینطوری اطلاعاتمون رو با آخرین بیت هر مقدار یک پیکسل عوض میکنیم.

نتیجه رو ببینین:

[[(224, 13, 99), (154, 3, 50), (98, 50, 15), (15, 54, 23)],
[(154, 61, 87), (63, 30, 17), (1, 55, 19), (99, 81, 66)],
[(219, 77, 91), (69, 39, 50), (18, 200, 33), (25, 54, 190)]]

 

ما اینجا فقط پیکسل ها رو -1 یا +1 کردیم اما شما میتونین این عدد رو به دو یا سه هم تغییر بدین. اما بهتون توصیه نمیکنم که بیشتر از 6 بیت رو تغییر بدین چون معلوم میشه که یک جای عکس مشکل داره و شکل ظاهریش عوض میشه.

 

خب حالا که تکنیک ها و روش ها رو بهتون توضیح دادم بریم شروع کنیم!

 

برای انجام این کار باید یک کتابخونه برای کار کردن با عکس هامون انتخاب کنیم. من opencv رو انتخاب کردم شما میتونین از یک کتابخونه دیگه مثلا PIL هم استفاده کنین.

 

برای نصب کتابخونه های مورد نیاز توی ویندوز وارد cmd بشین و دستور زیر رو وارد کنین:

pip install opencv-python numpy

برای نصب کتابخونه های مورد نیاز توی مک یا لینوکس هم وارد Terminal بشین و دستور زیر رو وارد کنین:

sudo pip3 install opencv-python numpy

 

خب چیزی که میخوایم اینه:

یک اسکریپت که بهش دو تا فایل، یکی "فایل داده" و یکی "عکسی" که میخوایم داده رو داخلش مخفی کنه بدیم و بعد اون اسکریپت، داده رو داخل اون عکس رمزنگاری کنه، یا اینکه اطلاعات رو از فایلی که بهش دادیم استخراج کنه.

 

اول یک فایل پایتون باز کنین و کد های زیر رو داخلش وارد کنین:

import cv2
import numpy as np
import os

def to_bin(data):
    """Convert `data` to binary format as string"""
    if isinstance(data, str):
        return ''.join([ format(ord(i), "08b") for i in data ])
    elif isinstance(data, bytes):
        return ''.join([ format(i, "08b") for i in data ])
    elif isinstance(data, np.ndarray):
        return [ format(i, "08b") for i in data ]
    elif isinstance(data, int) or isinstance(data, np.uint8):
        return format(data, "08b")
    else:
        raise TypeError("Type not supported.")


def encode(image_name, secret_data, n_bits=2):
    # read the image
    image = cv2.imread(image_name)
    # maximum bytes to encode
    n_bytes = image.shape[0] * image.shape[1] * 3 * n_bits // 8
    print("[*] Maximum bytes to encode:", n_bytes)
    print("[*] Data size:", len(secret_data))
    if len(secret_data) > n_bytes:
        raise ValueError(f"[!] Insufficient bytes ({len(secret_data)}), need bigger image or less data.")
    print("[*] Encoding data...")
    # add stopping criteria
    if isinstance(secret_data, str):
        secret_data += "====="
    elif isinstance(secret_data, bytes):
        secret_data += b"====="
    data_index = 0
    # convert data to binary
    binary_secret_data = to_bin(secret_data)
    # size of data to hide
    data_len = len(binary_secret_data)
    for bit in range(1, n_bits+1):
        for row in image:
            for pixel in row:
                # convert RGB values to binary format
                r, g, b = to_bin(pixel)
                # modify the least significant bit only if there is still data to store
                if data_index < data_len:
                    if bit == 1:
                        # least significant red pixel bit    
                        pixel[0] = int(r[:-bit] + binary_secret_data[data_index], 2)
                    elif bit > 1:
                        # replace the `bit` least significant bit of the red pixel with the data bit
                        pixel[0] = int(r[:-bit] + binary_secret_data[data_index] + r[-bit+1:], 2)
                    data_index += 1
                if data_index < data_len:
                    if bit == 1:
                        # least significant green pixel bit
                        pixel[1] = int(g[:-bit] + binary_secret_data[data_index], 2)
                    elif bit > 1:
                        # replace the `bit` least significant bit of the green pixel with the data bit
                        pixel[1] = int(g[:-bit] + binary_secret_data[data_index] + g[-bit+1:], 2)
                    data_index += 1
                if data_index < data_len:
                    if bit == 1:
                        # least significant blue pixel bit
                        pixel[2] = int(b[:-bit] + binary_secret_data[data_index], 2)
                    elif bit > 1:
                        # replace the `bit` least significant bit of the blue pixel with the data bit
                        pixel[2] = int(b[:-bit] + binary_secret_data[data_index] + b[-bit+1:], 2)
                    data_index += 1
                # if data is encoded, just break out of the loop
                if data_index >= data_len:
                    break
    return image


def decode(image_name, n_bits=1, in_bytes=False):
    print("[+] Decoding...")
    # read the image
    image = cv2.imread(image_name)
    binary_data = ""
    for bit in range(1, n_bits+1):
        for row in image:
            for pixel in row:
                r, g, b = to_bin(pixel)
                binary_data += r[-bit]
                binary_data += g[-bit]
                binary_data += b[-bit]

    # 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
    if in_bytes:
        # if the data we'll decode is binary data, 
        # we initialize bytearray instead of string
        decoded_data = bytearray()
        for byte in all_bytes:
            # append the data after converting from binary
            decoded_data.append(int(byte, 2))
            if decoded_data[-5:] == b"=====":
                # exit out of the loop if we find the stopping criteria
                break
    else:
        decoded_data = ""
        for byte in all_bytes:
            decoded_data += chr(int(byte, 2))
            if decoded_data[-5:] == "=====":
                break
    return decoded_data[:-5]


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Steganography encoder/decoder, this Python scripts encode data within images.")
    parser.add_argument("-t", "--text", help="The text data to encode into the image, this only should be specified for encoding")
    parser.add_argument("-f", "--file", help="The file to hide into the image, this only should be specified while encoding")
    parser.add_argument("-e", "--encode", help="Encode the following image")
    parser.add_argument("-d", "--decode", help="Decode the following image")
    parser.add_argument("-b", "--n-bits", help="The number of least significant bits of the image to encode", type=int, default=2)
    
    args = parser.parse_args()
    if args.encode:
        # if the encode argument is specified
        if args.text:
            secret_data = args.text
        elif args.file:
            with open(args.file, "rb") as f:
                secret_data = f.read()
        input_image = args.encode
        # split the absolute path and the file
        path, file = os.path.split(input_image)
        # split the filename and the image extension
        filename, ext = file.split(".")
        output_image = os.path.join(path, f"{filename}_encoded.{ext}")
        # encode the data into the image
        encoded_image = encode(image_name=input_image, secret_data=secret_data, n_bits=args.n_bits)
        # save the output image (encoded image)
        cv2.imwrite(output_image, encoded_image)
        print("[+] Saved encoded image.")
    if args.decode:
        input_image = args.decode
        if args.file:
            # decode the secret data from the image and write it to file
            decoded_data = decode(input_image, n_bits=args.n_bits, in_bytes=True)
            with open(args.file, "wb") as f:
                f.write(decoded_data)
            print(f"[+] File decoded, {args.file} is saved successfully.")
        else:
            # decode the secret data from the image and print it in the console
            decoded_data = decode(input_image, n_bits=args.n_bits)
            print("[+] Decoded data:", decoded_data)

توضیح: خب اول از همه کتابخونه های مورد نیازمون رو import کردیم.

بعدش یک تابع به اسم to_bin تعریف کردیم که برای تبدیل کردن اطلاعات به binary بعدا استفادش می کنیم.

حالا یک تابع به نام encode تعریف کردیم که در واقع کارش اینه که اطلاعات رو رمزنگاری کنه. این تابع رو یک جوری تعریف کردیم که هم متن و هم فایل های دیگه رو تبدیل به باینری کنه و با تعداد بیت هایی که خودمون توی ورودی های برنامه مشخص می کنیم داخل عکس رمزنگاری کنه.

 

بعد از اون تابع decode رو تعریف کردیم که اول عکس رو بخونه و بعد اطلاعات binary رو تبدیل به 8 بیتی کنه و بعدش با تعداد بیت هایی که بهش دادیم اطلاعات رو رمزنگاری کنه.

 

حالا توی برنامه اصلی گفتیم که ورودی های مورد نیاز رو از کاربر بگیره و با استفاده از توابعی که تعریف کردیم اطلاعات رو رمزنگاری یا رمزگشایی کنه.

 

فقط حواستون باشه که حجم عکس باید از حجم داده ای که میخواین مخفی کنین بیشتر باشه وگرنه داده داخلش جا نمیشه.

 

بریم برناممون رو تست کنیم!

 

اینجا من دو تا عکس دارم:

 

test:

 

test2:

 

خب کاری که میخوایم انجام بدیم اینه که فایل test2.jpeg رو داخل test.png مخفی کنیم!

 

پس وارد ترمینال میشیم و دستور زیر رو وارد می کنیم:

python hide_data.py -f test2.jpeg -e test.png -b 2

برنامه این رو به ما نشون میده:

[*] Maximum bytes to encode: 423225
[*] Data size: 132550
[*] Encoding data...
[+] Saved encoded image.

خب این یعنی اینکه عملیات با موفقیت انجام شده و فایل رمزنگاری شده رو ذخیره کرده:

خب همونطور که میبینین یک فایل به نام test_encoded.png اضافه شده که هیچ فرقی با فایل اصلی نداره و اصلا معلوم نیست که یک عکس دیگه رو داخلش جا دادیم!

 

حالا بریم سراغ decode کردن عکس.

پس وارد ترمینال میشیم و کد زیر رو وارد می کنیم:

python hide_data.py -d test_encoded.png -f test_decoded.png -b 2

خروجی برنامه:

[+] Decoding...
[+] File decoded, test_decoded.png is saved successfully.

خب دیدیم که فایل test_decoded.png توی همون فولدر ذخیره شده و عین همون عکس اصلیه:

 

پس ما در واقع عکس 1 رو داخل عکس 2 مخفی کردیم بدون اینکه عکس 2 تغییر کنه و بعدش عکس 1 رو دوباره از توی عکس 2 در آوردیم و میبینیم که هر دوتا عکس عین روز اولشونن! :)))

 

امیدوارم براتون مفید بوده باشه D:


  • عالی و فوق العاده کاربردی
    پاسخ:
    سلام دوست عزیز
    ممنون بابت نظرتون
    موفق باشید
  • سپاس استاد
    بهترین هستید
    پاسخ:
    سلام دوست عزیز
    سپاس از نظر لطف و همراهی شما.
    موفق باشید
  • کارای باحالی میشه باهاش انجام داد
    ایول
    خوشم اومد
    پاسخ:
    سلام دوست عزیز
    خوشحالم که این مطلب بریتون مفید بوده
    موفق باشید
  • ضمن تشکر بابت وبلاگ بینظیرتون
    می خواستم بدونم شما تدریس خصوصی هم دارید؟
    پاسخ:
    سلام دوست عزیز
    خیلی ممنون از لطفتون
    نه متاسفانه، تدریس خصوصی ندارم
    اما شما میتونین هر وقت خواستین همینجا توی نظرات یا از طریق ایمیل farbodmblog@gmail.com با من در ارتباط باشین و مشکلات و سوالاتتون رو حل کنین.
    موفق باشید
  • آرمان شاکری
    معلومه که برای هر پستی که تو وبلاگتون میذارین خیلی زحمت می کشین
    من از خیلی هاش واقعا استفاده کردم و برام مفید بودن
    خواستم تشکر کنم
    در پناه خدا باشید
    پاسخ:
    سلام دوست عزیز.
    خیلی ممنونم از محبتتون و خوشحالم از اینکه پست ها براتون مفید بوده.
    شاد و موفق باشید.
ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی