Привет, Хабр! Когда речь заходит о защите конфиденциального файла, на ум приходят два пути: шифрование и стеганография. Первый делает файл нечитаемым для посторонних. Второй — делает сам факт существования файла незаметным. А что, если объединить эти два подхода, создав по-настояшему надежное «двойное дно» для ваших данных?
В этой статье мы не просто обсудим теорию, а пошагово, с подробным разбором кода, создадим собственный простой и надежный формат шифрования .cha (сокращение от Chameleon) на Python. А затем покажем, как именно работает механизм LSB-встраивания в картинку и как наша программа «ChameleonLab» автоматически находит эти скрытые данные.

Часть 1: Пошаговая реализация .CHA на Python
Цель — создать утилиту, которая:
-
Надежно шифрует файл с помощью пароля.
-
Сохраняет оригинальное имя файла внутри зашифрованного контейнера.
-
Проверяет целостность данных при расшифровке.
Для этого мы будем использовать отличную библиотеку cryptography.
Шаг 1: Генерация ключа из пароля (KDF)
Никогда не используйте пароль напрямую в качестве ключа шифрования! Его нужно «растянуть» до криптографически стойкого ключа с помощью функции деривации ключей (KDF). Мы используем PBKDF2 со 100 000 итераций.
# Из crypto_utils.py from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import base64 import os def generate_key_from_password(password: str, salt: bytes) -> bytes: """ Генерирует 32-байтный ключ из пароля и соли с помощью PBKDF2-HMAC-SHA256. """ kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, # Длина ключа в байтах (256 бит для AES) salt=salt, iterations=100000, # Рекомендованное количество итераций ) # Кодируем ключ в URL-safe Base64 для совместимости с Fernet return base64.urlsafe_b64encode(kdf.derive(password.encode()))
Шаг 2: Упаковка и шифрование файла
Структура нашего .cha файла: [Магическое число] + [Соль] + [Зашифрованные данные].
-
Магическое число (
b'CHAMELEON'): Уникальная сигнатура для опознания формата. -
Соль: 16 случайных байт для PBKDF2.
-
Зашифрованные данные: Результат шифрования по стандарту Fernet (AES-128-CBC + HMAC-SHA256), который гарантирует конфиденциальность и целостность данных.
# Из crypto_utils.py from cryptography.fernet import Fernet from pathlib import Path MAGIC_NUMBER = b'CHAMELEON' def encrypt_cha_file(input_path: str, output_path: str, password: str): """Шифрует файл в формат .cha, сохраняя имя файла.""" input_path = Path(input_path) with open(input_path, 'rb') as f: plaintext = f.read() # Упаковываем имя файла и его содержимое через разделитель '|' packaged_data = input_path.name.encode('utf-8') + b'|' + plaintext salt = os.urandom(16) key = generate_key_from_password(password, salt) f = Fernet(key) encrypted_data = f.encrypt(packaged_data) with open(output_path, 'wb') as f_out: f_out.write(MAGIC_NUMBER) f_out.write(salt) f_out.write(encrypted_data)
Эта функция реализована в UI на вкладке «Шифрование файлов».
Шаг 3: Расшифровка и проверка
Процесс расшифровки обратный, но с ключевым моментом: fernet.decrypt() автоматически проверит HMAC-подпись. Если данные повреждены или пароль неверен, функция выбросит исключение InvalidToken, защищая от работы с некорректными данными.
# Из crypto_utils.py def decrypt_cha_file(input_path: str, output_path: str, password: str) -> str: """Расшифровывает .cha файл, возвращает оригинальное имя файла.""" with open(input_path, 'rb') as f: if f.read(len(MAGIC_NUMBER)) != MAGIC_NUMBER: raise ValueError("Это не файл формата .cha или он поврежден.") salt = f.read(16) encrypted_data = f.read() key = generate_key_from_password(password, salt) f = Fernet(key) decrypted_packaged_data = f.decrypt(encrypted_data) filename_bytes, file_content = decrypted_packaged_data.split(b'|', 1) original_filename = filename_bytes.decode('utf-8') with open(output_path, 'wb') as f_out: f_out.write(file_content) return original_filename
Часть 2: Прячем зашифрованный файл в картинке
Итак, у нас есть надежно зашифрованный файл. Теперь задача — сделать его невидимым. Как именно происходит встраивание в картинку и, что еще важнее, как потом найти эти данные автоматически?
Шаг 2a: Создание «Стего-Пакета»
Чтобы утилита могла автоматически находить данные, мы «заворачиваем» наш .cha файл в еще один контейнер со служебным заголовком. Структура этого стего-пакета:
[Длина данных (4б)] + [Число бит (1б)] + [СИГНАТУРА (4б)] + [Флаг шифрования (1б)] + [Сам файл]
Ключевой элемент здесь — СИГНАТУРА (b'S6KB'). Это уникальный «маячок», который наша программа ищет в файлах для автоматического обнаружения.
Шаг 2b: Алгоритм встраивания (hide)
Функция hide берет этот пакет и последовательно записывает его биты в младшие биты (LSB) пикселей изображения.
# steganography_core.py def hide(carrier_bytes: np.ndarray, final_payload: bytes, n_bits: int, is_encrypted: bool) -> np.ndarray: encryption_flag = b'\x01' if is_encrypted else b'\x00' data_to_hide = MAGIC_NUMBER + encryption_flag + final_payload metadata_len = len(data_to_hide).to_bytes(4, 'big') n_bits_val = int(n_bits).to_bytes(1, 'big') full_data = metadata_len + n_bits_val + data_to_hide full_bits = data_to_binary(full_data) carrier_flat = carrier_bytes.flatten().copy().astype(np.uint8) mask = ~((1 << n_bits) - 1) & 0xFF carrier_flat &= mask bit_index = 0 for i in range(math.ceil(len(full_bits) / n_bits)): chunk = full_bits[bit_index : bit_index + n_bits].ljust(n_bits, '0') carrier_flat[i] |= int(chunk, 2) bit_index += n_bits return carrier_flat.reshape(carrier_bytes.shape)
Шаг 2c: Автоматическое извлечение (reveal)
Программа для извлечения не знает, сколько LSB было использовано, поэтому она перебирает все варианты от 1 до 8. Для каждого варианта она пытается прочитать заголовок и найти сигнатуру.
# steganography_core.py def reveal(carrier_bytes: np.ndarray) -> Tuple[bytes, bool, bool]: flat = carrier_bytes.flatten().astype(np.uint8) METADATA_BITS = 40 for candidate_n in range(1, 9): bytes_for_metadata = math.ceil(METADATA_BITS / candidate_n) if flat.size < bytes_for_metadata: continue bitstream = _get_bitstream(flat, candidate_n, bytes_for_metadata) if len(bitstream) < METADATA_BITS: continue data_len = int(bitstream[:32], 2) declared_n = int(bitstream[32:40], 2) if declared_n != candidate_n: continue total_bytes_to_read = math.ceil((5 + data_len) * 8 / declared_n) full_bitstream = _get_bitstream(flat, declared_n, total_bytes_to_read) full_bytes = binary_to_data(full_bitstream) data_to_hide = full_bytes[5 : 5 + data_len] if data_to_hide.startswith(MAGIC_NUMBER): header_len = len(MAGIC_NUMBER) is_encrypted = (data_to_hide[header_len] == 1) packaged_data = data_to_hide[header_len + 1:] return packaged_data, is_encrypted, True return b'', False, False
Часть 3: Синергия: Шифрование + Стеганография = ❤️
Использование связки .cha и стеганографии дает два независимых уровня защиты:
-
Уровень 1: Шифрование (Конфиденциальность и целостность): Вы берете ваш
my_plan.docxи превращаете его вmy_plan.docx.cha. Даже если злоумышленник его найдет, он не сможет его прочитать без пароля или незаметно изменить.
Программа «ChameleonLab». Шифрование -
Уровень 2: Стеганография (Скрытность и правдоподобное отрицание): Вы берете полученный
my_plan.docx.chaи встраиваете его в фотографиюkitten.png. Теперь у постороннего наблюдателя нет даже оснований подозревать, что какой-то секретный файл вообще существует. Файл не просто защищен — он невидим.
Эта двухступенчатая защита — и есть философия ChameleonLab. Сначала мы делаем данные нечитаемыми, а затем делаем их невидимыми. Это обеспечивает правдоподобное отрицание: вы всегда можете утверждать, что это просто картинка, и доказать обратное будет практически невозможно.
Последнюю версию программы «Steganographia» от ChameleonLab для Windows и macOS можно скачать на нашем официальном сайте. https://chalab.ru
А чтобы быть в курсе обновлений, обсуждать новые функции и общаться с единомышленниками, присоединяйтесь к нашему Telegram-каналу: https://t.me/ChameleonLab
Спасибо за ваше внимание и интерес к проекту!
ссылка на оригинал статьи https://habr.com/ru/articles/943402/
Добавить комментарий