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

原创 2015年11月19日 15:27:17

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

源码下载:源码


版权声明:本文为博主原创文章,未经博主允许不得转载。

CTF解密图片得到flag

这一篇是转载他人的,不是自己写的,因为做到了这一题,想要去借鉴一下别人的经验。网址:http://blog.csdn.net/fuzz_nancheng/article/details/5338435...
  • sinat_38134380
  • sinat_38134380
  • 2017年04月09日 23:53
  • 4202

图片隐写术

在CTF题目中图片隐写题属于杂项的一部分,题目较为简单。本文大致梳理了下CTF比赛套路。...
  • Binwalker
  • Binwalker
  • 2017年08月30日 15:12
  • 3284

CTF中常见的隐藏手段总结

隐藏术总结
  • bajinsheng
  • bajinsheng
  • 2016年11月28日 15:38
  • 841

隐写术总结

0x00 前言 之前还没有见到drops上有关于隐写术的总结,我之前对于隐写术比较有兴趣,感觉隐写术比较的好玩。所以就打算总结总结一些隐写术方面的东西。写的时候,可能会有错误的地方,请不吝赐教,...
  • tmdsb38
  • tmdsb38
  • 2015年10月31日 19:57
  • 542

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

  • 2015年11月19日 15:32
  • 45KB
  • 下载

图片隐藏数据的技术

图片中隐藏数据是一件可繁可简的事情。 ★尾部追加法   先介绍最简单的一种方法。 ◇技术原理   顾名思义,"尾部追加法"就是把要隐藏的文件追加到图片尾部。这种方法不会破坏...
  • typ2004
  • typ2004
  • 2017年06月14日 22:07
  • 582

隐写术入门篇——png文档格式

1 简介 PNG是20世纪90年代中期开始开发的图像文件存储格式,其目的是企图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。流式网络图形格式(Portable Netw...
  • zhang_dashuai
  • zhang_dashuai
  • 2015年09月02日 08:47
  • 6830

图片内容管家 (把文字隐藏到图片里)

最近学习 javafx 自己编写了一款软件  软件官方网址: http://pcm.chujianyun.com 注:安装说明、使用说明、关于等更多详细信息,请访问官方网址。 360网盘下载地...
  • w605283073
  • w605283073
  • 2015年06月23日 08:39
  • 1523

用图片隐藏信息的技术实现

说明:本文件为转载,为便于以后查看特意转载记录 来源: 【编程随想】的技术博客  原帖网址:http://blog.csdn.net/program_think/article/deta...
  • lidawei201
  • lidawei201
  • 2015年05月19日 21:12
  • 2602

教你如何利用灰度图透明效果制作隐藏图片.part1

效果图:电脑端查看也具有类似效果 聊天界面缩略图查看效果: 聊天界面点击查看详情效果 原理使用了两张图片进行拼接,并使用PNG透明特性,使得图片在白色背景和黑色背景下具有不相同的显示,在白色...
  • chenzhuyu
  • chenzhuyu
  • 2016年09月09日 01:58
  • 3796
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:将文件内容隐藏到png图片中
举报原因:
原因补充:

(最多只允许输入30个字)