How to hide text in an image: steganography using JPEG as an example

01.10.2025 5 minutes Author: Lady Liberty

JPEG steganography is a modern way of hiding data in ordinary images, used both for educational experiments and for practical cybersecurity tasks. Unlike simple methods of changing pixels, JPEG uses DCT coefficients, where only the least significant bit is changed.

The secret is in the picture

JPEG is a format that has become the standard for storing and transmitting photos due to its balance between quality and file size. To understand its features, you need to understand the fundamental principles of how it works.

RGB to YCbCr conversion

Unlike the usual RGB system, JPEG uses the YCbCr space. It consists of three components:

  • Y (luminance) — determines the light component of the image.

  • Cb — reflects the difference between brightness and blue.

  • Cr — reflects the difference between brightness and red.

This division is based on the peculiarities of human vision: people perceive brightness changes better than color differences. This allows for more efficient data compression without significant loss of visual quality.

Block structure and compression

JPEG does not store information about each pixel directly. Instead, it uses DCT (Discrete Cosine Transform) compression:

  • The image is divided into blocks of 8×8 pixels.

  • Each block is translated into frequency space.

  • The high-frequency components, especially in the Cb and Cr channels, are reduced depending on the compression level.

This approach allows for a significant reduction in data volume while preserving visually important information.

DCT and its role

The discrete cosine transform represents a block as a matrix of coefficients that describe different frequency components:

  • Low frequencies are responsible for smooth transitions, large objects and the background. Their change affects the overall brightness.

  • Mid frequencies describe the contours and basic textures, forming the perception of the shape of objects.

  • High frequencies reflect fine details, sharpness and clarity. They are responsible for the feeling of detail.

Why JPEG deserves special attention

This format doesn’t just reduce file size—it’s a unique example of how engineers have harnessed the power of human vision to optimize data storage. JPEG combines mathematics, perceptual psychology, and engineering simplicity to become the foundation for the digital age of photography.

DCT for all three channels, for example
It is in the form of a matrix

What will we do?

In each element of the matrix, the least significant bit will be changed to the one needed to transmit the hidden data.

What might this look like?

But first, let’s see how changes to this matrix will affect the image.

We only affect high frequencies
We only affect mid frequencies
We only affect low frequencies
We touch all frequencies

We touch all frequencies. The examples show a frequency change of 70% for better clarity. However, for a specific task, changes of only 1 bit are sufficient, which makes them completely invisible to the human eye. Thus, the hidden message in JPEG is formed by dividing it into bits and sequentially modifying the last bit of each element of the matrix according to the desired value.

I will not include the technical implementation of obtaining DCT, as it is quite voluminous. An excellent example is owencm/js-steg. Using it, you can work with DCT matrices.

A simple example of saving and retrieving a message:

/**
  * Перетворює наше повідомлення в байти
  */
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;

        //Змінюємо останній біт
        lumaCoefficient
/**
  * Перетворює байти у рядок
  */ 
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[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));
});
Well, the result

The code for a more advanced solution can be found on GitHub (using AES, hiding files in the image). For those interested, you can play around with what frequencies we use to store information (the visual change in the image depends on this).

X from 0 to 7, Y from 0 to 7

Conclusion

JPEG is not only a photo storage format, but also a convenient tool for data hiding. The use of DCT coefficients allows you to change only the least significant bit, making the message invisible to the eye. The choice of frequencies determines the balance between quality and reliability of hiding, and the simplicity of implementation makes the method interesting for both learning and practice.

Subscribe
Notify of
0 Коментарі
Oldest
Newest Most Voted
Found an error?
If you find an error, take a screenshot and send it to the bot.