图形图像您现在的位置是:首页 > 博客日志 > 图形图像

将文件内容隐藏到png图片中

<a href='mailto:'>微wx笑</a>的头像微wx笑 2021-10-29图形图像 2 0关键字: png  隐藏内容  

实现原理:
png文件格式包括固定的文件头部+ 必要的数据块和辅助数据块
每一个数据块包括四个字节的数据块数据长度,4个字节的类型码,可变长度的数据块数据,4个字节的CRC
其中的IEND数据块为最后一块数据块,且默认情况下是不存储数据的,除非人为加入,我们就可以将自己的数据写入到IEND的数据块数据区域

要想更好的实现该功能,你可以阅读将文件内容隐藏到bmp图片中aVx无知

要实现这个功能,你得了解png文件的格式,详情:http://www.w3.org/TR/PNG/aVx无知

实现原理:
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
aVx无知


aVx无知

下面给出关键代码:aVx无知

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 aVx无知


aVx无知

本文为转载文章,版权归原作者所有,不代表本站立场和观点。

很赞哦! () 有话说 ()