Стеганография в dlib
Моя статья 2016 года, изначально написанная для блога LightHouse Software. Приведенный код актуален и сегодня.
Думаю, нет необходимости лишний раз говорить о том, насколько в наше время важна защита личных данных и тайна переписки. Конечно, к нашим услугам имеются криптографические алгоритмы, но одного только шифрования порой бывает мало – иногда нужно не просто передать секретное сообщение, но и скрыть сам факт передачи. И здесь приходят на помощь алгоритмы стеганографии.
В древности для скрытия информации от посторонних глаз записи делали невидимыми чернилами, которые проявлялись в пламени свечи. Компьютерным аналогом этого вида стеганографии можно назвать метод LSB (Least Significant Bit, наименьший значащий бит). Суть этого метода в том, чтобы «замаскировать» секретное сообщение под безобидную, на первый взгляд, картинку. Наименьшие значащие биты каждого пикселя изображения-оригинала заменяются на биты скрываемого изображения. Дело в том, что слабые изменения канала мало влияют на цвет пикселя в целом, и, если использовать сильно зашумленную фотографию – например, пейзаж с деревьями – то на глаз никакой разницы заметно не будет.
Естественно, скрываемое изображение сохранится с сильными потерями: обычно его кодируют в 2 бита на канал – следовательно, при использовании модели RGB получается палитра из 4х4х4=64 цветов. Но и этого более чем достаточно, чтобы зашифровать, например, текст или чертеж, где не требуется большое количество цветовых оттенков.
Заметим также, что метод LSB применим только для lossless-форматов (BMP, PNG, TGA). Алгоритмы сжатия с потерями (такие, как JPEG) неизбежно модифицируют информацию в каналах, поэтому закодировать в них секретное сообщение не получится. Для JPEG, кстати, существуют собственные разновидности стеганографии, работающие в частотной области.
При помощи библиотеки dlib создание стеганографического файла PNG выглядит весьма тривиально – мы просто проходим циклом по всем байтам RGB-изображения и, с помощью побитовых сдвигов и операции «ИЛИ», записываем в два младших бита нашу секретную информацию:
import dlib.image;
// Входные изображения должны быть одинакового размера
auto input = loadPNG("input.png");
auto secret = loadPNG("secret.png");
auto stego = image(input.width, input.height, 3);
foreach(i, b; input.data)
{
ubyte compressed = secret.data[i] / 85;
stego.data[i] = ((b >> 2) << 2) | compressed;
}
stego.savePNG("stego.png");
Перекодирование секретного байта в 2-битный формат делается путем деления на 85 – максимальное значение для 2-битного канала равно 3, следовательно, 255 / 3 = 85.
Обратная операция – извлечение секретного сообщения из картинки – и того проще. При помощи операции «И» мы извлекаем два младших бита по маске 00000011 и переводим обратно в 8-битное представление путем умножения на 85:
output.data[i] = (stego.data[i] & 0b00000011) * 85;