NO IMAGE

要想更好的實現該功能,你可以閱讀將檔案內容隱藏到bmp圖片中

要實現這個功能,你得了解png檔案的格式,詳情:http://www.w3.org/TR/PNG/

實現原理:
png檔案格式包括固定的檔案頭部 必要的資料塊和輔助資料塊
每一個資料塊包括四個位元組的資料塊資料長度,4個位元組的型別碼,可變長度的資料塊資料,4個位元組的CRC
其中的IEND資料塊為最後一塊資料塊,且預設情況下是不儲存資料的,除非人為加入,我們就可以將自己的資料
寫入到IEND的資料塊資料區域

隱藏實現思路:
1、解析png檔案格式(為了驗證後期的資料的正確性,也為了學習png格式)
2、獲取隱藏檔案的大小
3、修改IEND資料塊的資料長度資訊
4、寫入png檔案的其它資訊,直到IEND資料塊的資料區域
5、寫入隱藏檔案的內容
6、寫入IEND資料塊的CRC資訊(該處記錄要隱藏檔案的大小,方便恢復資料時使用)

恢復隱藏資料實現思路:
1、定位到png格式中IEND資料塊的CRC,獲取其值
2、根據獲取到的值,定位到寫入隱藏檔案資料的開始位置
3、讀取隱藏檔案的資料,直到遇到IEND資料塊的CRC

下面給出關鍵程式碼:

package com.pan.utils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.pan.entity.CommonBlock;
import com.pan.entity.DataBlock;
import com.pan.entity.Png;
import com.pan.entity.PngHeader;
import com.pan.factory.BlockFactory;
/**
* @author yp2
* @date 2015-11-19
* @decription 隱藏檔案內容到png格式圖片中
*/
public class PngUtil {
/**
* 讀取指定png檔案的資訊
* @param pngFileName
* @return
* @throws IOException 
*/
private static Png readPng(String pngFileName) throws IOException {
Png png = new Png();
File pngFile = new File(pngFileName);
InputStream pngIn = null;
//記錄輸入流讀取位置(位元組為單位)
long pos = 0;
try {
pngIn = new FileInputStream(pngFile);
//讀取頭部資訊
PngHeader pngHeader = new PngHeader();
pngIn.read(pngHeader.getFlag());
png.setPngHeader(pngHeader);
pos  = pngHeader.getFlag().length;
while(pos < pngFile.length()) {
DataBlock realDataBlock = null;
//讀取資料塊
DataBlock dataBlock = new CommonBlock();
//先讀取長度,4個位元組
pngIn.read(dataBlock.getLength());
pos  = dataBlock.getLength().length;
//再讀取型別碼,4個位元組
pngIn.read(dataBlock.getChunkTypeCode());
pos  = dataBlock.getChunkTypeCode().length;
//如果有資料再讀取資料
//讀取資料
realDataBlock = BlockFactory.readBlock(pngIn, png, dataBlock);
pos  = ByteUtil.highByteToInt(dataBlock.getLength());
//讀取crc,4個位元組
pngIn.read(realDataBlock.getCrc());
//新增讀取到的資料塊
png.getDataBlocks().add(realDataBlock);
pos  = realDataBlock.getCrc().length;
dataBlock = null;
}
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
try {
if(pngIn != null) {
pngIn.close();
}
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
return png;
}
/**
* 將讀取到的檔案資訊寫入到指定png的檔案中,並指定輸出檔案
* @param png				Png資訊物件
* @param pngFileName		png檔名
* @param inputFileName		要隱藏的檔名
* @param outFileName		輸出檔名,內容包括png資料和要隱藏檔案的資訊
* @throws IOException 
*/
private static void wirteFileToPng(Png png, String pngFileName, String inputFileName, String outFileName) throws IOException {
File pngFile = new File(pngFileName);
File inputFile = new File(inputFileName);
File outFile = new File(outFileName);
InputStream pngIn = null;
InputStream inputIn = null;
OutputStream out = null;
int len = -1;
byte[] buf = new byte[1024];
try {
if(!outFile.exists()) {
outFile.createNewFile();
}
pngIn = new FileInputStream(pngFile);
inputIn = new FileInputStream(inputFile);
out = new FileOutputStream(outFile);
//獲取最後一個資料塊,即IEND資料塊
DataBlock iendBlock = png.getDataBlocks().get(png.getDataBlocks().size() - 1);
//修改IEND資料塊資料長度:原來的長度 要隱藏檔案的長度
long iendLength = ByteUtil.highByteToLong(iendBlock.getLength());
iendLength  = inputFile.length();
iendBlock.setLength(ByteUtil.longToHighByte(iendLength, iendBlock.getLength().length));
//修改IEND crc資訊:儲存隱藏檔案的大小(位元組),方便後面讀取png時找到檔案內容的位置,並讀取
iendBlock.setCrc(ByteUtil.longToHighByte(inputFile.length(), iendBlock.getCrc().length));
//寫入檔案頭部資訊
out.write(png.getPngHeader().getFlag());
//寫入資料塊資訊
String hexCode = null;
for(int i = 0; i < png.getDataBlocks().size(); i  ) {
DataBlock dataBlock = png.getDataBlocks().get(i);
hexCode = ByteUtil.byteToHex(dataBlock.getChunkTypeCode(), 
0, dataBlock.getChunkTypeCode().length);
hexCode = hexCode.toUpperCase();
out.write(dataBlock.getLength());
out.write(dataBlock.getChunkTypeCode());
//寫資料塊資料
if(BlockUtil.isIEND(hexCode)) {
//寫原來IEND資料塊的資料
if(dataBlock.getData() != null) {
out.write(dataBlock.getData());
}
//如果是IEND資料塊,那麼將檔案內容寫入IEND資料塊的資料中去
len = -1;
while((len = inputIn.read(buf)) > 0) {
out.write(buf, 0, len);
}
} else {
out.write(dataBlock.getData());
}
out.write(dataBlock.getCrc());
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
try {
if(pngIn != null) {
pngIn.close();
}
if(inputIn != null) {
inputIn.close();
}
if(out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
/**
* 將指定的檔案資訊寫入到png檔案中,並輸出到指定的檔案中
* @param pngFileName			png檔名
* @param inputFileName			要隱藏的檔名
* @param outFileName			輸出檔名
* @throws IOException 
*/
public static void writeFileToPng(String pngFileName, String inputFileName, String outFileName) throws IOException {
Png png = readPng(pngFileName);
wirteFileToPng(png, pngFileName, inputFileName, outFileName);
}
/**
* 讀取png檔案中儲存的資訊,並寫入到指定指定輸出檔案中
* @param pngFileName		png檔名
* @param outFileName		指定輸出檔名
* @throws IOException 
*/
public static void readFileFromPng(String pngFileName, String outFileName) throws IOException {
File pngFile = new File(pngFileName);
File outFile = new File(outFileName);
InputStream pngIn = null;
OutputStream out = null;
//記錄輸入流讀取位置
long pos = 0;
int len = -1;
byte[] buf = new byte[1024];
try {
if(!outFile.exists()) {
outFile.createNewFile();
}
pngIn = new BufferedInputStream(new FileInputStream(pngFile));
out = new FileOutputStream(outFile);
DataBlock dataBlock = new CommonBlock();
//獲取crc的長度資訊,因為不能寫死,所以額外獲取一下
int crcLength = dataBlock.getCrc().length;
byte[] fileLengthByte = new byte[crcLength];
pngIn.mark(0);
//定位到IEND資料塊的crc資訊位置,因為寫入的時候我們往crc寫入的是隱藏檔案的大小資訊
pngIn.skip(pngFile.length() - crcLength);
//讀取crc資訊
pngIn.read(fileLengthByte);
//獲取到隱藏檔案的大小(位元組)
int fileLength = ByteUtil.highByteToInt(fileLengthByte);
//重新定位到開始部分 
pngIn.reset();
//定位到隱藏檔案的第一個位元組
pngIn.skip(pngFile.length() - fileLength - crcLength);
pos = pngFile.length() - fileLength - crcLength;
//讀取隱藏檔案資料
while((len = pngIn.read(buf)) > 0) {
if( (pos   len) > (pngFile.length() - crcLength) ) {
out.write(buf, 0, (int) (pngFile.length() - crcLength - pos));
break;
} else {
out.write(buf, 0, len);
}
pos  = len;
}
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
try {
if(pngIn != null) {
pngIn.close();
}
if(out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
public static void main(String[] args) throws IOException {
String filePath = PngUtil.class.getClassLoader().getResource("resource/sound_wav.png").getPath();
Png png = readPng(filePath);
wirteFileToPng(png, filePath, PngUtil.class.getClassLoader().getResource("resource/").getPath()   "screct.txt",
PngUtil.class.getClassLoader().getResource("resource/").getPath()   "sound_wavout.png");
readFileFromPng(PngUtil.class.getClassLoader().getResource("resource/").getPath()   "sound_wavout.png",
PngUtil.class.getClassLoader().getResource("resource/").getPath()   "sound_wavscrect.txt");
System.out.println(ByteUtil.byteToHexforPrint(png.getPngHeader().getFlag(), 
0, png.getPngHeader().getFlag().length));
for(DataBlock dataBlock : png.getDataBlocks()) {
System.out.println(ByteUtil.byteToHexforPrint(dataBlock.getLength(), 
0, dataBlock.getLength().length));
System.out.println(ByteUtil.byteToHexforPrint(dataBlock.getChunkTypeCode(), 
0, dataBlock.getChunkTypeCode().length));
if(dataBlock.getData() != null) {
System.out.println(ByteUtil.byteToHexforPrint(dataBlock.getData(), 
0, dataBlock.getData().length));
}
System.out.println(ByteUtil.byteToHexforPrint(dataBlock.getCrc(), 
0, dataBlock.getCrc().length));
}
System.out.println();
}
}

package com.pan.factory;
import java.io.IOException;
import java.io.InputStream;
import com.pan.entity.DataBlock;
import com.pan.entity.IDATBlock;
import com.pan.entity.IENDBlock;
import com.pan.entity.IHDRBlock;
import com.pan.entity.PHYSBlock;
import com.pan.entity.PLTEBlock;
import com.pan.entity.Png;
import com.pan.entity.SRGBBlock;
import com.pan.entity.TEXTBlock;
import com.pan.entity.TRNSBlock;
import com.pan.utils.BlockUtil;
import com.pan.utils.ByteUtil;
/**
* @author yp2
* @date 2015-11-19
* @description 資料塊工廠
*/
public class BlockFactory {
/**
* 讀取輸入流中的資料塊的資料
* @param in		 輸入流
* @param png		png物件
* @param dataBlock	資料塊
* @return			具體細節的資料塊
* @throws IOException
*/
public static DataBlock readBlock(InputStream in, Png png, DataBlock dataBlock) throws IOException {
String hexCode = ByteUtil.byteToHex(dataBlock.getChunkTypeCode(), 
0, dataBlock.getChunkTypeCode().length);
hexCode = hexCode.toUpperCase();
DataBlock realDataBlock = null;
if(BlockUtil.isIHDR(hexCode)) {
//IHDR資料塊
realDataBlock = new IHDRBlock();
} else if(BlockUtil.isPLTE(hexCode)) {
//PLTE資料塊
realDataBlock = new PLTEBlock();
} else if(BlockUtil.isIDAT(hexCode)) {
//IDAT資料塊
realDataBlock = new IDATBlock();
} else if(BlockUtil.isIEND(hexCode)) {
//IEND資料塊
realDataBlock = new IENDBlock();
} else if(BlockUtil.isSRGB(hexCode)) {
//sRGB資料塊
realDataBlock = new SRGBBlock();
} else if(BlockUtil.istEXt(hexCode)) {
//tEXt資料塊
realDataBlock = new TEXTBlock();
} else if(BlockUtil.isPHYS(hexCode)) {
//pHYs資料塊
realDataBlock = new PHYSBlock();
} else if(BlockUtil.istRNS(hexCode)) {
//tRNS資料塊
realDataBlock = new TRNSBlock();
} else {
//其它資料塊
realDataBlock = dataBlock;
}
realDataBlock.setLength(dataBlock.getLength());
realDataBlock.setChunkTypeCode(dataBlock.getChunkTypeCode());
//讀取資料,這裡的測試版做法是: 把所有資料讀取進記憶體來
int len = -1;
byte[] data = new byte[8096];
len = in.read(data, 0, ByteUtil.highByteToInt(dataBlock.getLength()));
realDataBlock.setData(ByteUtil.cutByte(data, 0, len));
return realDataBlock;
}
}

原始碼下載:原始碼