-->

I am reading an image and changing it. But the cha

2019-08-01 22:08发布

问题:

I am trying to implement a steganography. I am reading an image "a.jpeg" and inserting a byte in it by changing its consecutive 7 bytes at the least significant bit starting from offset 50. This is done successfully as when I print the bytes the last bits are changed accordingly. Then I saved it as "ao.jpeg". But when I am reading the byte values from 50, they are not the same as the once i saved. here's my code

public static void main(String[] args) throws IOException {
        BufferedImage inputImage = ImageIO.read(new File("a.jpeg"));
        int offset=50;
        byte data = 7;
        byte[] image = get_byte_data(inputImage);//function converts bufferedimage to byte array
        //add data at end of each byte starting from offset
        System.out.println("bytes altered are :");
        for(int bit=7; bit>=0; --bit, ++offset)//for each bit of data
            {
            int b = (data >>> bit) & 1;
            image[offset] = (byte)((image[offset] & 0xFE) | b );
            String s1 = String.format("%8s", Integer.toBinaryString(image[offset] & 0xFF)).replace(' ', '0');
            System.out.println(s1);
            }
        //write changed image to ao.jpeg
        BufferedImage outputImage = ImageIO.read(new ByteArrayInputStream(image)); 
        File outputfile = new File("ao.jpeg");
        ImageIO.write(outputImage,"jpeg",outputfile);
        //read data from ao.jpeg
        System.out.println("bytes from encoded image are :");
        byte result=0;
        offset=50;
        BufferedImage oImage = ImageIO.read(new File("aoc.jpeg"));
        byte[] image1 = get_byte_data(oImage);//function converts bufferedimage to byte array
            for(int i=0; i<8; i++, ++offset)
             {
             result = (byte)((result << 1) | (image1[offset] & 1));
             String s1 = String.format("%8s", Integer.toBinaryString(image1[offset] & 0xFF)).replace(' ', '0');
             System.out.println(s1);
             }
            System.out.println("recovered data is :");
            System.out.print(result);
        }

output sample : data inserted is 7. If you notice the least significant bit of each byte it is forming 7. But when i read it again it is random bytes.

bytes altered are :
00010100
00011100
00011010
00011110
00011110
00011101
00011011
00011101
bytes from encoded image are :
00011110
00011101
00011010
00011100
00011100
00100000
00100100
00101110
recovered data is :
64

As suggested by Konstantin V. Salikhov I tried different file format (gif) and it worked. But is there any way i can use "jpeg"?

回答1:

Why your approach fails

As Salinkov suggested, the jpeg format causes data compression. To summarise what you did:

  • You loaded the image data in a byte array.
  • You modified some of the bytes.
  • You loaded the byte array as an image.
  • You saved this image in jpeg format, which causes recompression.

That's where your method falls apart. A lossless format will not produce these problems, which is why gif works. Or png, bmp, etc...

Can you use your method for jpeg?

Weeeeell, no, not really. First, we need to understand what kind of data a jpeg image holds.

The short answer is that individual bytes don't correspond to actual pixels in the image.

The long story is that you split the image in 8x8 arrays and take the DCT to obtain the frequency coefficients. After a quantisation step, many of them will become 0, especially the higher frequency coefficients (bottom right of the DCT array - see here). This is the lossy step of jpeg. You sacrifice higher frequency coefficients for some tolerable image distortion (loss of information). Now, what you save is the non-zero coefficients, based on their location in the matrix, i.e. (0, 0, -26), (0, 1, -3), etc. This can be further compressed with Huffman coding. By the way, changing any one frequency component affects all 64 pixels.

So how is jpeg steganography normally done? It mostly follows the jpeg encoding process:

  • Split image in 8x8 arrays.
  • DCT each 8x8 array and quantise the coefficients.

Now we have obtained the quantised DCT coefficients and we take a break from the jpeg encoding process.

  • Apply some steganographic algorithm by changing the value of the quantised coefficients.

  • Huffman compress the coefficients and continue with the rest of the jpeg encoding process with these modified DCT coefficients.



回答2:

JPEG is a lossy storage mechanism. That means it is NOT required (or even desirable) that is represent every byte exactly as the original. In deed, that is the whole point, it sacrifices small imperfections in order to achieve large space savings. If you need byte-perfect storage you will have to find choose another format such as GIF, PNG, or some flavors of BMP.

As pointed out below, it is technically possible to create a lossless JPEG but it was a late addition, not fully supported and in particular Java does not natively support it. See this answer for more information.