要想更好的实现该功能,你可以阅读将文件内容隐藏到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;
}
}
源码下载: 源码