现在我们有如下需求:在一张图片中添加一段语音注释。对于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
。对于上面的标记,我们只需要关注:EOI
、COM
即可。对于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)