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();
}
}
}