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

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

<a href='mailto:'>微wx笑</a>的头像微wx笑 2021-10-29图形图像264 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无知

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


aVx无知

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

很赞哦! (24) 有话说 (0)

文章评论