From a5b932c6fa3b46050c1f1e14a8a455fc1dcb9c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C?= Date: Tue, 24 Mar 2026 22:56:32 +0300 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20LSB=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D1=83=D1=82=D0=BE=D1=87=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B2=20gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 +++- core/lsb.py | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3c45826..4e049ec 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,10 @@ output.txt # OS .DS_Store -Thumbs.db \ No newline at end of file +Thumbs.db + +# Тестовые файлы +test_image.png +encoded_lsb.png +test_*.png +*_lsb.png \ No newline at end of file diff --git a/core/lsb.py b/core/lsb.py index e69de29..4f48fcf 100644 --- a/core/lsb.py +++ b/core/lsb.py @@ -0,0 +1,134 @@ +""" +Модуль LSB (Least Significant Bit) стеганографии. + +Скрывает текст в изображении путем замены последних битов пикселей. +Каждый пиксель RGB хранит 3 бита информации (по одному в каждом канале). +""" + +import numpy as np +from PIL import Image +from .utils import text_to_bits, bits_to_text + + +def encode_lsb(image_path: str, message: str, output_path: str) -> bool: + """ + Скрывает текстовое сообщение в изображении методом LSB. + + Алгоритм: + 1. Преобразует текст в битовую строку + 2. Добавляет стоп-маркер (8 нулевых битов) для обозначения конца + 3. Проходит по всем пикселям изображения + 4. В каждом канале (R,G,B) заменяет последний бит на бит сообщения + + Аргументы: + image_path (str): Путь к исходному изображению + message (str): Текст для сокрытия + output_path (str): Путь для сохранения результата + + Возвращает: + bool: True если успешно, False если сообщение слишком длинное + """ + # Загружаем изображение + img = Image.open(image_path).convert('RGB') + pixels = np.array(img, dtype=np.uint8) + height, width, channels = pixels.shape + + # Преобразуем сообщение в биты и добавляем стоп-маркер + bits = text_to_bits(message) + '00000000' + + # Проверяем, поместится ли сообщение + max_bits = height * width * channels + if len(bits) > max_bits: + print(f"Ошибка: сообщение слишком большое.") + print(f"Доступно битов: {max_bits}, требуется: {len(bits)}") + return False + + # Прячем биты в изображение + bit_index = 0 + total_bits = len(bits) + + for i in range(height): + for j in range(width): + for c in range(channels): + if bit_index >= total_bits: + break + + pixel_value = pixels[i, j, c] + bit = int(bits[bit_index]) + + # Обнуляем последний бит и устанавливаем нужный + pixels[i, j, c] = (pixel_value & 0xFE) | bit + bit_index += 1 + + if bit_index >= total_bits: + break + if bit_index >= total_bits: + break + + # Сохраняем измененное изображение + result_img = Image.fromarray(pixels, mode='RGB') + result_img.save(output_path) + + print(f"Успешно! Спрятано {total_bits} бит ({total_bits // 8} байт)") + return True + + +def decode_lsb(image_path: str) -> str: + """ + Извлекает скрытое сообщение из изображения методом LSB. + + Алгоритм: + 1. Проходит по всем пикселям изображения + 2. Из каждого канала извлекает последний бит + 3. Собирает биты в байты + 4. Останавливается при обнаружении стоп-маркера (8 нулевых битов) + + Аргументы: + image_path (str): Путь к изображению со скрытым сообщением + + Возвращает: + str: Извлеченное сообщение + """ + # Загружаем изображение + img = Image.open(image_path).convert('RGB') + pixels = np.array(img, dtype=np.uint8) + height, width, channels = pixels.shape + + # Извлекаем биты + extracted_bits = [] + stop_counter = 0 + stop_marker_length = 8 + + for i in range(height): + for j in range(width): + for c in range(channels): + # Извлекаем последний бит + bit = pixels[i, j, c] & 1 + extracted_bits.append(bit) + + # Проверяем стоп-маркер + if bit == 0: + stop_counter += 1 + if stop_counter == stop_marker_length: + # Убираем стоп-маркер из результата + extracted_bits = extracted_bits[:-stop_marker_length] + break + else: + stop_counter = 0 + + if stop_counter == stop_marker_length: + break + if stop_counter == stop_marker_length: + break + + if len(extracted_bits) == 0: + return "" + + # Преобразуем биты в строку и декодируем + bits_string = ''.join(str(bit) for bit in extracted_bits) + + try: + return bits_to_text(bits_string) + except Exception as e: + print(f"Ошибка при декодировании: {e}") + return "" \ No newline at end of file