JPEG图像隐写

发布于 2023-06-17  278 次阅读


  现在我们有如下需求:在一张图片中添加一段语音注释。对于JPEG格式来说,其存在JPEG_COM(0xFF 0xFE),因此可以用来写入注释,但其长度只有65536个字节(JPEG_COM后两个字节表示长度),因此并不适用于长数据流的存放。对此我们考虑使用文件隐写(Steganography)的方式来完成图像后(文件尾标识符之后)添加其他信息的功能。

一、JPEG格式和标记

在本篇文章中,我们不去讨论JPEG的具体编码问题,而直接从标记入手,快速了解JPEG的大概样貌。

名称 标记码 说明
SOI D8 Start of Image
SOF0 C0 Start of Frame
SOF1 C1 Start of Frame
DHT C4 Define Huffman Tables
DQT DB Define Quantization Tables
DRI DD Define Restart Interval
SOS DA Start of Scan
RSTn Dn Restart n∈{0...7}
APPn En Application Specific n∈{0...7}
COM FE Comment
EOI D9 End of Image

上面的标记均为两位,第一位为0xFF,第二位为标记。例如,COM的完整标识符为FF FE。对于上面的标记,我们只需要关注:EOICOM即可。对于JPEG的解析,读取到EOI之后就结束了,因此我们可以在EOI之后追加我们想要添加的内容。

txt = '你好 PyHub!'

# 读取原始JPEG图像数据
with open("input.jpg", "rb") as f:
    f_bytes = f.read()
    print(f_bytes[:2])
    print(f_bytes[-2:])

# 编码
with open("input.jpg", "rb") as f:
    with open("output.jpg", "wb") as output:
        output.write(f.read())
        output.write('你好 PyHub!'.encode())

# 解码
with open("output.jpg", "rb") as f:
    content = f.read()
    eoi = content.find(b'\xff\xd9')
    print(content[eoi+2:].decode())

在上面的代码中,我们读取了一张图片,然后在文件的末尾直接添加字符串txt(对于普通的JPEG来说,EOI位于文件末尾)。在解码的时候,我们查找到0xFF 0xD9的位置,然后读取之后的代码,即可实现隐写。

二、隐写文件

对于隐写文件,则要多一步。如果文件以二进制形式被添加在了EOI之后,我们需要担心是否会出现0xFF 0xNN的情况,因此这里推荐使用base64编码之后再进行追加,避免发生意外情况。

import base64

# 读取WAV文件并转换为Base64字符串
with open("CantinaBand3.wav", "rb") as wav_file:
    wav_data = wav_file.read()
    base64_data = base64.b64encode(wav_data).decode()

# 读取原始JPEG图像数据
with open("input.jpg", "rb") as f:
    f_bytes = f.read()
    print(f_bytes[:2])
    print(f_bytes[-2:])

# 编码
with open("input.jpg", "rb") as f:
    with open("output.jpg", "wb") as output:
        output.write(f.read())
        output.write(base64_data.encode())

# 解码
with open("output.jpg", "rb") as f:
    content = f.read()
    eoi = content.find(b'\xff\xd9')
    base64_str = content[eoi+2:].decode()
    wav_data = base64.b64decode(base64_str)

# 将Base64字符串转换为WAV文件
with open("output.wav", "wb") as wav_file:
    wav_file.write(wav_data)