png图片文件存放自定义信息 您所在的位置:网站首页 png文件格式的描述怎么写 png图片文件存放自定义信息

png图片文件存放自定义信息

2024-07-10 02:04| 来源: 网络整理| 查看: 265

PNG文件格式详解 (https://blog.mythsman.com/2015/12/08/1/)

原文地址:将文件内容隐藏到png图片中(https://blog.csdn.net/u012009613/article/details/49928343)

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

}

源码下载:源码--------------------- 作者:来了就走下去 来源:CSDN 原文:https://blog.csdn.net/u012009613/article/details/49928343 版权声明:本文为博主原创文章,转载请附上博文链接!

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有