将文件内容隐藏到png图片中
微wx笑 2021-10-29【图形图像】 2 0关键字: png 隐藏内容
实现原理:
png文件格式包括固定的文件头部+ 必要的数据块和辅助数据块
每一个数据块包括四个字节的数据块数据长度,4个字节的类型码,可变长度的数据块数据,4个字节的CRC
其中的IEND数据块为最后一块数据块,且默认情况下是不存储数据的,除非人为加入,我们就可以将自己的数据写入到IEND的数据块数据区域
要想更好的实现该功能,你可以阅读将文件内容隐藏到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; } }
源码下载:源码
转自:https://blog.csdn.net/u012009613/article/details/49928343
本文为转载文章,版权归原作者所有,不代表本站立场和观点。