Привет, Хабр! При работе со стеганографией первый и самый важный вопрос, который возникает перед пользователем: «А мой файл вообще поместится в эту картинку?». Попытка спрятать 10-мегабайтный архив в иконку размером 64x64 пикселя обречена на провал. Именно поэтому оценка стеганографической емкости контейнера — это краеугольный камень любой операции по сокрытию данных.

Емкость — это не просто размер файла. Это сложное понятие, которое кардинально меняется в зависимости от формата контейнера (PNG, JPEG, DOCX) и метода сокрытия (LSB, DCT и др.).
Сегодня на примере кода из нашего проекта ChameleonLab мы подробно разберем, как вычисляется емкость для разных типов файлов, и напишем соответствующие функции на Python.
Основы основ — LSB-емкость в растровых изображениях (PNG, BMP)
Это самый простой и интуитивно понятный случай. Метод LSB (Least Significant Bit) позволяет заменять наименее значимые биты в каждом байте цвета пикселя.
Математика
Расчет здесь — простая арифметика:
Считаем общее число байт: Изображение состоит из пикселей, каждый пиксель — из каналов (обычно 3: Red, Green, Blue). Общее число байт, доступных для модификации, это
ширина × высота × 3
.Умножаем на
n_bits
:n_bits
— это количество "младших" бит, которые мы решаем использовать в каждом байте (от 1 до 8). Общая емкость в битах =(ширина × высота × 3) × n_bits
.Переводим в байты: Делим общее число бит на 8.
Вычитаем накладные расходы: Всегда нужно оставить немного места (10-20 байт) для служебной информации: сигнатуры, длины сообщения, флага шифрования и т.д.
Код
Эта логика реализована в функции calculate_capacity
в нашем файле steganography_
core.py
:
# steganography_core.py
import numpy as np
def calculate_capacity(carrier_bytes: np.ndarray, n_bits: int) -> int:
"""
Вычисляет максимальную стеганографическую емкость контейнера в байтах для LSB.
"""
if n_bits < 1 or n_bits > 8:
raise ValueError("n_bits должен быть в диапазоне 1..8")
# carrier_bytes.size — это и есть (ширина * высота * каналы) для NumPy массива
total_bits = int(carrier_bytes.size) * int(n_bits)
# Делим на 8, чтобы получить байты, и вычитаем небольшой запас на заголовок
return (total_bits // 8) - 10
Например, для картинки 1920x1080 (Full HD) с 3 каналами (RGB) и n_bits = 2
:
1920 1080 3 = 6 220 800
байт данных.6 220 800 * 2 = 12 441 600
доступных бит.12 441 600 / 8 = 1 555 200
байт.1 555 200 - 10 ≈ 1.55
мегабайт полезной нагрузки.


Продвинутый уровень — емкость в JPEG (DCT-метод)
С JPEG все гораздо сложнее. Это формат сжатия с потерями, и прямой LSB-анализ пикселей здесь не работает — при сохранении файла все ваши изменения будут уничтожены.
Встраивание происходит в коэффициенты ДКП (DCT) — это числовые представления частотных компонент изображения. Мы можем прятать по 1 биту в LSB каждого такого коэффициента.
Логика и ограничения
Только AC-коэффициенты: В каждом блоке 8x8 есть один DC-коэффициент (основная яркость) и 63 AC-коэффициента (детали). DC-коэффициент очень важен для изображения, и его изменение сильно заметно, поэтому мы его не трогаем.
Проблема нулей: Если значение коэффициента равно
1
или-1
, изменение его LSB превратит его в0
или-2
. Нулевые AC-коэффициенты часто отбрасываются при сжатии, что может повредить всю цепочку скрытых данных.Итог: Надежная емкость JPEG — это количество AC-коэффициентов, абсолютное значение которых больше 1.
Код
Для работы с JPEG мы используем библиотеку jpegio
. Наша функция calculate_capacity_dct
из файла steganography_
dct.py
реализует описанную логику:
# steganography_dct.py
try:
import jpegio
except ImportError:
jpegio = None
def calculate_capacity_dct(carrier_path: str) -> int:
"""
Рассчитывает емкость, считая все ненулевые AC-коэффициенты,
изменение которых не приведет к 0.
"""
if jpegio is None:
raise ImportError("Библиотека 'jpegio' не найдена.")
try:
jpeg = jpegio.read(carrier_path)
except Exception:
return 0
available_coeffs = 0
# Считаем коэффициенты только в канале яркости (Y) - он первый
y_coeffs = jpeg.coef_arrays[0]
# Проходим по каждому блоку 8x8
for block_idx in range(y_coeffs.shape[0] * y_coeffs.shape[1] // 64):
block = y_coeffs.reshape(-1, 64)[block_idx]
# Проходим по AC-коэффициентам (индексы с 1 по 63)
for i in range(1, 64):
# Считаем только те, что не превратятся в 0 при изменении LSB
if abs(block[i]) > 1:
available_coeffs += 1
# Каждый такой коэффициент может хранить 1 бит. Делим на 8 и вычитаем запас.
return available_coeffs // 8 - 20
Особый случай — емкость в офисных документах (DOCX, ODF)
Документы .docx
, .xlsx
, .odf
и т.п. — это, по сути, ZIP-архивы. Метод стеганографии здесь заключается не в изменении существующего контента, а в добавлении нового файла внутрь этой архивной структуры.
Это означает, что у таких документов нет фиксированной стеганографической емкости. Предел ограничен лишь файловой системой и здравым смыслом — ведь файл .docx
размером 500 МБ вызовет гораздо больше подозрений, чем обычный. В нашем приложении для таких форматов расчет емкости не выводится, а сообщается, что можно встроить файл практически любого размера.
Практическое применение — зачем все это нужно?
В интерфейсе «ChameleonLab» расчет емкости происходит автоматически при выборе файла-контейнера. Программа немедленно сообщает пользователю, сколько места доступно, и сравнивает это с размером выбранного секрета. Это позволяет избежать ошибок и сразу понять, какой метод и какие настройки (n_bits
) лучше использовать для конкретной задачи.
Заключение
Мы увидели, что стеганографическая емкость — это не одна простая формула, а целый набор подходов, зависящих от формата файла-контейнера.
Для PNG/BMP это простая арифметика на основе пикселей и
n_bits
.Для JPEG это сложный подсчет "безопасных" DCT-коэффициентов.
Для DOCX/ODF понятие емкости практически отсутствует.
Понимание этих различий — ключ к эффективному и безопасному сокрытию данных.
Последнюю версию программы «Steganographia» от ChameleonLab для Windows и macOS можно скачать на нашем официальном сайте. https://chalab.ru
А чтобы быть в курсе обновлений, обсуждать новые функции и общаться с единомышленниками, присоединяйтесь к нашему Telegram-каналу: https://t.me/ChameleonLab