Steganography in lossy compression (JAVA)
Asked Answered
L

1

7

I have this for encoding data in jpeg images in java. I am converting the text to its binary form and insert it to the LSB (depending on what the user has chosen.1,2,3,4) of the RGB in each pixel from (0,0) till (width,height).

outer:
    for(int i = 0; i < height; i++){
        for(int j = 0; j < width; j++){
            Color c = new Color(image.getRGB(j, i));                  

            int red = binaryToInteger(insertMessage(integerToBinary((int)(c.getRed())),numLSB));
            int green = binaryToInteger(insertMessage(integerToBinary((int)(c.getGreen())),numLSB));
            int blue = binaryToInteger(insertMessage(integerToBinary((int)(c.getBlue())),numLSB));

            Color newColor = new Color(red,green,blue);
            image.setRGB(j,i,newColor.getRGB());

        }
    }
    gui.appendStatus("Binarized message is: " + binarizedMessage);
    File output = new File(gui.getOutput()+".jpg");

    ImageIO.write(image, "png", output);

Currently, im writing it as a png and it works well but I am hoping to make this in jpeg. I am successful getting those data in png. But as expected, failing in jpeg.

I am able to decode the hidden bits in the image written and see the message given that the correct LSB was chosen.

I am currently reading about about JPEG steganography but not getting it quite exactly how should I start it. I've seen algorithms, didnt helped me either.

I saw a code that doesnt have any main classes found.

Do I have to call it in my application? Modify it? How would I decode?

Here's a link to a code I have seen.

Lauro answered 16/4, 2015 at 14:26 Comment(0)
K
11

The jpeg uses a lossy compression method to achieve smaller file sizes. Unfortunately, that very method directly affects the value of (some) pixels, thereby destroying the information the way you have embedded it. You need to save the file in a lossless format to avoid this problem, such as bmp or png.

Jpeg steganography is somewhat more complex to code, but the concept is straightforward. You will either need to write up a jpeg encoder, or use one in existence already. The code you linked to is indeed an encoder and with some minor modifications you can use it for your project.

If you want to understand the code, you can read the wikipedia article on jpeg encoding. I will briefly summarise some of its key steps.

  • Split the image in 8x8 blocks.
  • Use the discrete cosine transform (DCT) on each to obtain the float DCT coefficients and quantise them to integers.
  • Store the quantised coefficients to a file using Huffman coding and run length encoding.

The quantisation in the second step is the lossy bit, but everything that follows afterwards is lossless. So basically, obtain the quantised coefficients from the second step, modify them with your steganography algorithm and continue with the third step.

Onto the practical modifications of the linked code. The Compress method is what you need to call to store an rgb image to a file. It takes care of writing the header data and the compressed coefficients. You just need to add a bit of code in the WriteCompressedData method. What it does for now is loop over each 8x8 image block, apply the dct and quantise the coefficients, which are stored in dctArray3. This data is then compressed written to file. That's where you have to intervene, by modifying dctArray3 before calling Huf.HuffmanBlockEncoder.

For example, let's say you have a byte array of your secret, called message, and you want to embed one bit per 8x8 block in the lsb of a specific coefficient.

public void WriteCompressedData(BufferedOutputStream outStream, byte[] message) {
    byte currentByte;
    int nBytes = message.length;
    int iByte = 0;
    int iBit = 7;
    if (nBytes > 0) {
        currentByte = message[0];
    } else {
        currentByte = (byte) 0;
    }
    // Original method code up until the following line
    dctArray3 = dct.quantizeBlock(dctArray2, JpegObj.QtableNumber[comp]);
    // ******************** our stuff *******************
    if (iByte < nBytes) {
        int bit = (currentByte >> iBit) & 1;
        iBit--;
        if (iBit == -1) {
            iBit = 7;
            iByte++;
            if (iByte < nBytes) {
                currentByte = message[iByte];
            }
        }
        dctArray3[23] = (dctArray3[23] & 0xfffffffe) | bit;
    }
    // **************************************************
    Huf.HuffmanBlockEncoder(outStream, dctArray3, lastDCvalue[comp], JpegObj.DCtableNumber[comp], JpegObj.ACtableNumber[comp]);
    ...
}

The decoding is the reverse of this, where you read the DCT coefficients and extract your secret from them with the appropriate algorithm. You will require a jpeg decoder for this, so I just borrowed the relevant files from the F5 Steganography project. Specifically, you need the files in the ortega folder and then you can use it like this.

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import ortega.HuffmanDecode;

public class Extract {
    private static byte[] deZigZag = {
            0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31,
            40, 44, 53, 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61,
            35, 36, 48, 49, 57, 58, 62, 63 };

    private static int[] extract(InputStream fis, int flength) throws IOException {
        byte[] carrier = new byte[flength];
        fis.read(carrier);
        HuffmanDecode hd = new HuffmanDecode(carrier);
        int[] coeff = hd.decode();
        return coeff;
    }

    public static void main(String[] args) {
        // run with argument the stego jpeg filename
        try {
            File f = new File(args[0]);
            FileInputStream fis = new FileInputStream(f);
            int[] coeff = extract(fis, (int) f.length());

            int idx = deZigZag[23];
            // The coeff array has all of the DCT coefficients in one big
            // array, so that the first 64 elements are the coefficients 
            // from the first block, the next 64 from the second and so on.
            //
            // idx is the position of the embedding DCT coefficient.
            // You can start with that and extract its lsb, then increment
            // by 64 to extract the next bit from the next "block" and so on.
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Kisung answered 16/4, 2015 at 15:1 Comment(6)
i'll check this answer of yours later this weekend. Thanks for the answer :)Lauro
I corrected a mistake in the logic of the encoding sequence (should be if (iBit == -1) and not 0) and added a decoder example.Kisung
can I encode the image using my method, call the original JpegEncoder and let it write the image. and then I reverse it's process, then lastly, I apply my decoding process. Is it viable?Lauro
What do you mean encode the image with your method? Do you mean embed the information in the pixels and save as a jpeg afterwards?Kisung
That's what i've thought. But I think that wouldnt be possible.. I never thought this would be so complex.Lauro
Like it was been pointed out before, you can't embed data in the pixels and then encode that to a jpeg. Jpeg files achieve compression by storing the DCT coefficients. That's a lossless process as demonstrated above. You gotta work with what the file format gives you. For storing the pixels losslessly, you need to use different formats.Kisung

© 2022 - 2024 — McMap. All rights reserved.