-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSteganography.java
437 lines (328 loc) · 12.4 KB
/
Steganography.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
// Steganography.java
// Andrew Davison, May 2009, ad@fivedots.coe.psu.ac.th
/* A steganography class for hiding text inside a PNG image, and for
extracting the text later.
The class has two public static methods:
* boolean hide(String textFnm, String imFnm)
// the modified image is stored in <imFnm>Msg.png
* boolean reveal(String imFnm)
// the extracted message is stored in <imFnm>.txt
This class stores a stego message (stego for short), which has 2 fields:
<size of binary message>
<binary message>
The stego is spread out over the image's bytes by modifying each byte's
right most bit (the least significant bit, LSB). So 1 byte of stego data requires
the modification of 8 bytes of the image (i.e. 1 stego data bit is stored in
1 image byte).
The stego is added once into the LSBs of the image's bytes at its beginning.
More details on the stego's fields:
The message size is a Java integer (i.e. 4 bytes long),
which requires 4*8 bytes in the image.
Each byte of the message requires 8 bytes
of storage in the image.
*/
import java.io.*;
import java.awt.*;
import java.util.*;
import java.awt.image.*;
import javax.imageio.*;
public class Steganography
{
private static final int MAX_INT_LEN = 4;
private static final int DATA_SIZE = 8;
// number of image bytes required to store one stego byte
public static boolean hide(String textFnm, String imFnm)
/* hide message read from textFnm inside image read from imFnm;
the resulting image is stored in <inFnm>Msg.png */
{
// read in the message
String inputText = readTextFile(textFnm);
if ((inputText == null) || (inputText.length() == 0))
return false;
byte[] stego = buildStego(inputText);
// access the image's data as a byte array
BufferedImage im = loadImage(imFnm);
if (im == null)
return false;
byte imBytes[] = accessBytes(im);
if (!singleHide(imBytes, stego)) // im is modified with the stego
return false;
// store the modified image in <fnm>Msg.png
String fnm = getFileName(imFnm);
return writeImageToFile( fnm + "Msg.png", im);
} // end of hide()
private static String readTextFile(String fnm)
// read in fnm, returning it as a single string
{
BufferedReader br = null;
StringBuffer sb = new StringBuffer();
try {
br = new BufferedReader(new FileReader( new File(fnm) )); String text = null;
while ((text = br.readLine()) != null)
sb.append(text + "\n");
}
catch (Exception e) {
System.out.println("Could not completely read " + fnm);
return null;
}
finally {
try {
if (br != null)
br.close();
}
catch (IOException e) {
System.out.println("Problem closing " + fnm);
return null;
}
}
System.out.println("Read in " + fnm);
return sb.toString();
} // end of readTextFile()
private static byte[] buildStego(String inputText)
/* Build a stego (a byte array), made up of 2 fields:
<size of binary message>
<binary message> */
{
// convert data to byte arrays
byte[] msgBytes = inputText.getBytes();
byte[] lenBs = intToBytes(msgBytes.length);
int totalLen = lenBs.length + msgBytes.length;
byte[] stego = new byte[totalLen]; // for holding the resulting stego
// combine the 2 fields into one byte array
// public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
System.arraycopy(lenBs, 0, stego, 0, lenBs.length); // length of binary message
System.arraycopy(msgBytes, 0, stego, lenBs.length, msgBytes.length); // binary message
// System.out.println("Num. pixels to store fragment " + i + ": " + totalLen*DATA_SIZE);
return stego;
} // end of buildStego()
private static byte[] intToBytes(int i)
// split integer i into a MAX_INT_LEN-element byte array
{
// map the parts of the integer to a byte array
byte[] integerBs = new byte[MAX_INT_LEN];
integerBs[0] = (byte) ((i >>> 24) & 0xFF);
integerBs[1] = (byte) ((i >>> 16) & 0xFF);
integerBs[2] = (byte) ((i >>> 8) & 0xFF);
integerBs[3] = (byte) (i & 0xFF);
// for (int j=0; j < integerBs.length; j++)
// System.out.println(" integerBs[ " + j + "]: " + integerBs[j]);
return integerBs;
} // end of intToBytes()
private static BufferedImage loadImage(String imFnm)
// read the image from the imFnm file
{
BufferedImage im = null;
try {
im = ImageIO.read( new File(imFnm) );
System.out.println("Read " + imFnm);
}
catch (IOException e)
{ System.out.println("Could not read image from " + imFnm); }
return im;
} // end of loadImage()
private static byte[] accessBytes(BufferedImage image)
// access the data bytes in the image
{
WritableRaster raster = image.getRaster();
DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer();
return buffer.getData();
} // end of accessBytes()
private static boolean singleHide(byte[] imBytes, byte[] stego)
// store stego in image bytes
{
int imLen = imBytes.length;
System.out.println("Byte length of image: " + imLen);
int totalLen = stego.length;
System.out.println("Total byte length of message: " + totalLen);
// check that the stego will fit into the image
// multiply stego length by number of image bytes required to store one stego byte
if ((totalLen*DATA_SIZE) > imLen) {
System.out.println("Image not big enough for message");
return false;
}
hideStego(imBytes, stego, 0); // hide at start of image
return true;
} // end of singleHide()
private static void hideStego(byte[] imBytes, byte[] stego, int offset)
// store stego in image starting at byte posn offset
{
for (int i = 0; i < stego.length; i++) { // loop through stego
int byteVal = stego[i];
for(int j=7; j >= 0; j--) { // loop through the 8 bits of each stego byte
int bitVal = (byteVal >>> j) & 1;
// change last bit of image byte to be the stego bit
imBytes[offset] = (byte)((imBytes[offset] & 0xFE) | bitVal);
offset++;
}
}
} // end of hideStego()
private static String getFileName(String fnm)
// extract the name from the filename without its suffix
{
int extPosn = fnm.lastIndexOf('.');
if (extPosn == -1) {
System.out.println("No extension found for " + fnm);
return fnm; // use the original file name
}
return fnm.substring(0, extPosn);
} // end of getFileName()
private static boolean writeImageToFile(String outFnm, BufferedImage im)
// save the im image in a PNG file called outFnm
{
if (!canOverWrite(outFnm))
return false;
try {
ImageIO.write(im, "png", new File(outFnm));
System.out.println("Image written to PNG file: " + outFnm);
return true;
}
catch(IOException e)
{ System.out.println("Could not write image to " + outFnm);
return false;
}
} // end of writeImageToFile();
private static boolean canOverWrite(String fnm)
/* If fnm already exists, get a response from the
user about whether it should be overwritten or not. */
{
File f = new File(fnm);
if (!f.exists())
return true; // can overewrite since the file is new
// prompt the user about whether the file can be overwritten
Scanner in = new Scanner(System.in);
String response;
System.out.print("File " + fnm + " already exists. ");
while (true) {
System.out.print("Overwrite (y|n)? ");
response = in.nextLine().trim().toLowerCase();
if (response.startsWith("n")) // no
return false;
else if (response.startsWith("y")) // yes
return true;
}
} // end of canOverWrite()
// --------------------------- reveal a message -----------------------------------
public static boolean reveal(String imFnm)
/* Retrieve the hidden message from imFnm from the beginning
of the image after first extractibg its length information.
The extracted message is stored in <imFnm>.txt
*/
{
// get the image's data as a byte array
BufferedImage im = loadImage(imFnm);
if (im == null)
return false;
byte[] imBytes = accessBytes(im);
System.out.println("Byte length of image: " + imBytes.length);
// get msg length at the start of the image
int msgLen = getMsgLength(imBytes, 0);
if (msgLen == -1)
return false;
System.out.println("Byte length of message: " + msgLen);
// get message located after the length info in the image
String msg = getMessage(imBytes, msgLen, MAX_INT_LEN*DATA_SIZE);
if (msg != null) {
String fnm = getFileName(imFnm);
return writeStringToFile(fnm + ".txt", msg); // save message in a text file
}
else {
System.out.println("No message found");
return false;
}
} // end of reveal()
private static int getMsgLength(byte[] imBytes, int offset)
// retrieve binary message length from the image
{
byte[] lenBytes = extractHiddenBytes(imBytes, MAX_INT_LEN, offset);
// get the binary message length as a byte array
if (lenBytes == null)
return -1;
// for (int j=0; j < lenBytes.length; j++)
// System.out.println(" lenBytes[ " + j + "]: " + lenBytes[j]);
// convert the byte array into an integer
int msgLen = ((lenBytes[0] & 0xff) << 24) |
((lenBytes[1] & 0xff) << 16) |
((lenBytes[2] & 0xff) << 8) |
(lenBytes[3] & 0xff);
// System.out.println("Message length: " + msgLen);
if ((msgLen <= 0) || (msgLen > imBytes.length)) {
System.out.println("Incorrect message length");
return -1;
}
// else
// System.out.println("Revealed message length: " + msgLen);
return msgLen;
} // end of getMsgLength()
private static String getMessage(byte[] imBytes, int msgLen, int offset)
/* Extract a binary message of size msgLen from the image, and
convert it to a string
*/
{
byte[] msgBytes = extractHiddenBytes(imBytes, msgLen, offset);
// the message is msgLen bytes long
if (msgBytes == null)
return null;
String msg = new String(msgBytes);
// check the message is all characters
if (isPrintable(msg)) {
// System.out.println("Found message: \"" + msg + "\"");
return msg;
}
else
return null;
} // end of getMessage()
private static byte[] extractHiddenBytes(byte[] imBytes, int size, int offset)
// extract 'size' hidden data bytes, starting from 'offset' in the image bytes
{
int finalPosn = offset + (size*DATA_SIZE);
if (finalPosn > imBytes.length) {
System.out.println("End of image reached");
return null;
}
byte[] hiddenBytes = new byte[size];
for (int j = 0; j < size; j++) { // loop through each hidden byte
for (int i=0; i < DATA_SIZE; i++) { // make one hidden byte from DATA_SIZE image bytes
hiddenBytes[j] = (byte) ((hiddenBytes[j] << 1) | (imBytes[offset] & 1));
// shift existing 1 left; store right most bit of image byte
offset++;
}
}
return hiddenBytes;
} // end of extractHiddenBytes()
private static boolean isPrintable(String str)
// is the string printable?
{
for (int i=0; i < str.length(); i++)
if (!isPrintable(str.charAt(i))) {
System.out.println("Unprintable character found");
return false;
}
return true;
} // end of isPrintable()
private static boolean isPrintable(int ch)
// is ch a 7-bit ASCII character that could (sensibly) be printed?
{
if (Character.isWhitespace(ch) && (ch < 127)) // whitespace, 7-bit
return true;
else if ((ch > 32) && (ch < 127))
return true;
return false;
} // end of isPrintable()
private static boolean writeStringToFile(String outFnm, String msgStr)
// write the message string into the outFnm text file
{
if (!canOverWrite(outFnm))
return false;
try {
FileWriter out = new FileWriter( new File(outFnm) );
out.write(msgStr);
out.close();
System.out.println("Message written to " + outFnm);
return true;
}
catch(IOException e)
{ System.out.println("Could not write message to " + outFnm);
return false;
}
} // end of writeStringToFile()
} // end of Steganography class