Как спрятать любые данные в JPEG

vaspvort

Ночной дозор
Команда форума
Модератор
ПРОВЕРЕННЫЙ ПРОДАВЕЦ
Private Club
Старожил
Migalki Club
Меценат💰️
Регистрация
10/4/18
Сообщения
5.099
Репутация
10.058
Реакции
15.318
RUB
1.045
Сделок через гаранта
18
В этот раз поговорим о JPEG...


3dd999f24253c302d2ef6ba5313bfc27.png

Что нам нужно знать про JPEG и почему этот парень заслуживает отдельной статьи?

А знать нам для начала нужно то, как JPEG устроен. Начнем с того, что в JPEG не используется RGB, вместо него мы имеем дело с YCbCr.

Из чего состоит YCbCr?


  • Y - Яркость (светлота). Вычисляется по формуле:
  • Y=0.299⋅R+0.587⋅G+0.114⋅B
  • Cb - Разница между яркостью и синим
  • Сr - Разница между яркостью и красным

Продолжим. Второй факт о JPEG — он не хранит цвет каждого пикселя. Вместо этого JPEG использует DCT-сжатие:

  1. Изображение разбивается на компоненты YCbCr и делится на блоки 8×8 пикселей.
  2. Каждый блок преобразуется в частотное пространство с помощью DCT (дискретное косинусное преобразование).
  3. Высокие частоты у каналов Cb и Cr, как правило, уменьшаются (зависит от степени сжатия)
Что такое DCT?

Это способ представить изображение (а в нашем случае каждый блок) в виде матрицы коэффициентов (или же по-другому частот), описывающих его

  • Низкие частоты: описывают плавные изменения (фон, крупные объекты). Изменение низких частот напрямую влияет на яркость изображения.
  • Средние частоты: описывают контуры объектов, крупные текстуры. Их изменения влияют на восприятие объектов и текстур на изображении, делая их более заметными или, наоборот, сглаженными.
  • Высокие частоты: описывают очень мелкие детали. Высокие частоты отвечают за резкость, чёткость и ощущение «детализации» в изображении.
DCT для всех трех каналов, для примера

DCT для всех трех каналов, для примера
Оно же в виде матрицы

Оно же в виде матрицы
Что мы будем делать?

Ясен ч... красен, мы будем менять последний бит в каждом элементе матрицы (на бит, нужный нам)! Подобную механику я описал в о PNG, только там мы меняли бит у цвета, а в этот раз нам предстоит менять бит у чисел в матрице.

Как это может выглядеть

Как это может выглядеть
Но сперва посмотрим, как изменения этой матрицы скажутся на изображении.

Затрагиваем только высокие частоты

Затрагиваем только высокие частоты
Затрагиваем только средние частоты

Затрагиваем только средние частоты
Затрагиваем только низкие частоты

Затрагиваем только низкие частоты
Затрагиваем все частоты

Затрагиваем все частоты
Примеры приведены с изменением частоты на 70% для пущей наглядности.

Но для нашей задачи требуется изменение всего на 1 bit. А следовательно, изменения будут совершенно незаметны человеческому глазу.


Итак! Чтобы спрятать наше сообщение внутри JPEG, нам остается лишь разбить его на биты и пройтись по матрице, меняя последний бит каждого элемента на нужный нам.

35b6aea86a8189a53206b38ef2706400.png


Техническую реализацию по получению DCT не прикладываю, так как она довольно объемная. Прекрасный пример - . Используя его, можно работать с матрицами DCT.

Простой пример сохранения и получения сообщения:

/**
* Переводит наше сообщение в байты
*/
function textToBytes(text){
let encoder = new TextEncoder();
return encoder.encode(text);
}

/**
* Производит изменения с матрифами
*/
function modifyCoefficients(coefficients){

//Наше сообщение
let message = "Message";
let data = textToBytes(message);

//coefficients[0] -> все блоки Y
//coefficients[1] -> все блоки Cb
//coefficients[2] -> все блоки Cr

//coefficients[0][0] //64 элемента матрицы DCT

//Меняем данные в матрице
//Для примера, работаем только с Y-каналом
let lumaCoefficients = coefficients[0];
for (let i = 0, bitIndex = 0; i < lumaCoefficients.length; i++) {
for (let j = 0; j < 64; j++) {

if(bitIndex < data.length * 8){

let bit = (data[Math.floor(bitIndex / 8)] >> (7 - (bitIndex % 8))) & 1;

//Меняем последний бит
lumaCoefficients[j] = (lumaCoefficients[j] & 0xFE) | bit;

bitIndex++;

}

}
}

}

jsSteg.reEncodeWithModifications(objectURL, modifyCoefficients, function (resultUri) {
//resultUri - наш результат (картинка base64)
});
/**
* Переврдит байты в строку
*/
function bytesToText(bytes){
let uint8Array = new Uint8Array(bytes);
let decoder = new TextDecoder();
return decoder.decode(uint8Array);
}

function readCoefficients(coefficients) {

//coefficients[1] - Y
//coefficients[2] - Cb
//coefficients[3] - Cr

let bytes = [];
let dataBitIndex = 0;
let currentByte = 0;

//Работаем так же только с Y
let lumaCoefficients = coefficients[1];

for (let i = 0; i < lumaCoefficients.length; i++) {
for (let j = 0; j < 64; j++) {

let bit = lumaCoefficients[j] & 1;

currentByte = (currentByte << 1) | bit;
dataBitIndex++;

if (dataBitIndex % 8 === 0) {
bytes.push(currentByte);
currentByte = 0;
}

}
}

return bytesToText(bytes);

}

//Чтение сообщения
jsSteg.getCoefficients(objectURL, function(coefficients){
console.log(readCoefficients(coefficients));
});
Ну и результат

Ну и результат
Код более развернутого решения можно найти на (использование AES, сокрытие файлов в картинке).

Кому интересно - можно поиграться с тем, какие частоты мы используем под хранение информации (от этого зависит визуальное изменение изображения).

X от 0 до 7, Y от 0 до 7

X от 0 до 7, Y от 0 до 7

 
  • Теги
    jpeg спрятать информацию стеганография
  • Назад
    Сверху Снизу