将文件内容隐藏到png图片中
微wx笑
2021-10-29【图形图像】
264
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
下面给出关键代码:
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 | 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(); } } |
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 | 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
本文为转载文章,版权归原作者所有,不代表本站立场和观点。