Привет, Хабр! Когда речь заходит о защите конфиденциального файла, на ум приходят два пути: шифрование и стеганография. Первый делает файл нечитаемым для посторонних. Второй — делает сам факт существования файла незаметным. А что, если объединить эти два подхода, создав по-настояшему надежное "двойное дно" для ваших данных?

В этой статье мы не просто обсудим теорию, а пошагово, с подробным разбором кода, создадим собственный простой и надежный формат шифрования .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". Шифрование
    Программа "ChameleonLab". Шифрование
  • Уровень 2: Стеганография (Скрытность и правдоподобное отрицание): Вы берете полученный my_plan.docx.cha и встраиваете его в фотографию kitten.png. Теперь у постороннего наблюдателя нет даже оснований подозревать, что какой-то секретный файл вообще существует. Файл не просто защищен — он невидим.

Программа "ChameleonLab". Встраивание
Программа "ChameleonLab". Встраивание

Эта двухступенчатая защита — и есть философия ChameleonLab. Сначала мы делаем данные нечитаемыми, а затем делаем их невидимыми. Это обеспечивает правдоподобное отрицание: вы всегда можете утверждать, что это просто картинка, и доказать обратное будет практически невозможно.

Последнюю версию программы «Steganographia» от ChameleonLab для Windows и macOS можно скачать на нашем официальном сайте. https://chalab.ru

А чтобы быть в курсе обновлений, обсуждать новые функции и общаться с единомышленниками, присоединяйтесь к нашему Telegram-каналу: https://t.me/ChameleonLab

Спасибо за ваше внимание и интерес к проекту!

Комментарии (10)


  1. JBFW
    03.09.2025 06:42

    Не раскрыт метод встраивания в картинку.

    Не, то что так сделать можно - это понятно, вопрос как именно в данном случае? Может ли быть создана утилита, которая в автоматическом режиме просканирует картинку и извлечёт скрытые данные в пригодном для расшифровки виде, с сигнатурой например?


    1. Lomakn Автор
      03.09.2025 06:42

      Спасибо за интересные и правильные вопросы. Дополнили статью.


  1. andreymal
    03.09.2025 06:42

    Теперь у постороннего наблюдателя нет даже оснований подозревать, что какой-то секретный файл вообще существует. Файл не просто защищен — он невидим.

    Замечаем, что картинка какая-то подозрительно шумная, достаём младшие биты, видим S6KB и идём за паяльником

    Хотя в общем-то даже замечать не надо, можно автоматизировать и просканировать вообще все картинки


    1. Lomakn Автор
      03.09.2025 06:42

      Спасибо. Ваши комментарии бесценны.


  1. kuza2000
    03.09.2025 06:42

    Статья, бесспорно, полезная для изучения технологий. Но сигнатуру оставлять нельзя, уже написали.

    Ну и метод встраивания в младшие биты обнаруживается очень легко, даже без сигнатуры. Перспективны методы, не изменяющие энтропии данных. Они есть, но что-то я не видел хорошей реализации.


  1. kuza2000
    03.09.2025 06:42

    Файл не просто защищен — он невидим.

    Увы. Хорошо виден, даже если уберите сигнатуру. Не глазом виден, конечно, а с помощью простых и распространенных детекторов.


    1. Lomakn Автор
      03.09.2025 06:42

      Спасибо за комментарии и развёрнутые ответы. Мы методы будем менять и добавлять новые: DCT, F5 и другие. Нам важно на данном этапе, чтобы программа работала правильно и все работало с коробки. Дальше будем делать профессиональный режим.


      1. kuza2000
        03.09.2025 06:42

        Попробуйте это реализовать. Вот это было бы реально чума!)

        https://arxiv.org/abs/2210.14889


        1. Lomakn Автор
          03.09.2025 06:42

          Спасибо. Над PDF форматом работаем. Там и правда можно сломать голову.


        1. Lomakn Автор
          03.09.2025 06:42

          Спасибо еще раз pа статью. Это интересная обзорная статья (Survey Paper). Она не предлагает один конкретный новый метод для реализации, а анализирует и классифицирует множество уже существующих техник стеганографии и стегоанализа, в основном в контексте глубокого обучения и нейронных сетей.

          Реализовать методы из этой статьи "в лоб" — это задача уровня научной диссертации, так как она требует создания сложных генеративных моделей (как GPT-2 для текста или WaveRNN для аудио), а не просто написания скрипта. Если это реализовать, тогда и вес программы будет в разы больше.

          Но мы придумали свой 100% новый рабочий способ для игр в прятки с PDF. Думаю, через несколько недель его реализуем. Он будет связан со структорой PDF документов.