Найти в Дзене
#Habr

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

Прошлая статья нашла своих читателей, а поэтому продолжаем!  В этот раз поговорим о JPEG... Что нам нужно знать про JPEG и почему этот парень заслуживает отдельной статьи? А знать нам для начала нужно то, как JPEG устроен. Начнем с того, что в JPEG не используется RGB, вместо него мы имеем дело с YCbCr. Из чего состоит YCbCr? Продолжим. Второй факт о JPEG — он не хранит цвет каждого пикселя. Вместо этого JPEG использует DCT-сжатие: Каждый блок преобразуется в частотное пространство с помощью DCT (дискретное косинусное преобразование). Высокие частоты, как правило, уменьшаются (зависит от степени сжатия) Что такое DCT? Это способ представить изображение (а в нашем случае каждый блок) в виде матрицы коэффициентов (или же по-другому частот), описывающих его Но сперва посмотрим, как изменения этой матрицы скажутся на изображении. Примеры приведены с изменением частоты на 70% для пущей наглядности.  Но для нашей задачи требуется изменение всего на 1 bit. А следовательно, изменения бу

Прошлая статья нашла своих читателей, а поэтому продолжаем! 

В этот раз поговорим о JPEG...

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

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

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

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

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

  1. Изображение разбивается на компоненты YCbCr и делится на блоки 8×8 пикселей.

Каждый блок преобразуется в частотное пространство с помощью DCT (дискретное косинусное преобразование).

Высокие частоты, как правило, уменьшаются (зависит от степени сжатия)

Что такое DCT?

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

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

Оно же в виде матрицы
Оно же в виде матрицы

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

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

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

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

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

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

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

-7

Техническую реализацию по получению DCT не прикладываю, так как она довольно объемная. Прекрасный пример - owencm/js-steg. Используя его, можно работать с матрицами 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[i][j] = (lumaCoefficients[i][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 (var i = 0; i < lumaCoefficients.length; i++) {

for (var j = 0; j < 64; j++) {

let bit = lumaCoefficients[i][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));

});

Ну и результат
Ну и результат

Хабы:Информационная безопасность