You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

采用隐写术嵌入QR Code Payload:字节转存XOR操作异常求助

问题:QR Code水印嵌入Payload时的字节转存异常

我尝试用某论文所述方法,以水印形式给QR Code嵌入特殊Payload,流程逻辑看似没问题,但在把Payload保存为可与QR Code做XOR运算的字节数据时出了问题。

我的Python代码

import qrcode
from PIL import Image, ImageChops

qr = qrcode.QRCode(
    version=5,
    error_correction=qrcode.constants.ERROR_CORRECT_H,
    box_size=10,
    border=2,
)

msg = "25.61795.?000001?.907363.02"
sct = "secret message test"


def save_qrcode(data, path):
    qr.add_data(data)
    qr.make(fit=True)
    img = qr.make_image(fill_color="black", back_color="white")
    img.save(path)


save_qrcode(msg, "out.png")
save_qrcode(sct, "out2.png")
pure_qr_code = Image.open("out.png")

encoded_data_as_img = Image.new(mode=pure_qr_code.mode, size=pure_qr_code.size)
encoded_data_pre_xor = [ord(e) for e in sct]
print(encoded_data_pre_xor)

# Encoding
encoded_data_as_img.putdata(encoded_data_pre_xor)
encoded_data_as_img.save("out2.png")
encoded_data_as_img = Image.open("out2.png")
result = ImageChops.logical_xor(pure_qr_code, encoded_data_as_img)
result.save("result.png")

# Decoding
result = Image.open("result.png")
result2 = ImageChops.logical_xor(result, pure_qr_code)
result2.save("result2.png")
img_data_as_bytes = Image.open("result2.png").getdata()

encoded_data_after_xor = []
i = 0
while img_data_as_bytes[i]:
    encoded_data_after_xor.append(img_data_as_bytes[i])
    i += 1
print(encoded_data_after_xor)

代码运行输出

[115, 101, 99, 114, 101, 116, 32, 109, 101, 115, 115, 97, 103, 101, 32, 116, 101, 115, 116]
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]

当前现象与问题定位

对比result2.png和out2.png未发现差异,说明问题出在encoded_data_as_img.putdata(encoded_data_pre_xor)这一步的字节转存环节。


问题根源与修复方案

核心问题

  1. 数据长度不匹配:Payload的字节数远小于QR图像素总数,putdata会自动用0填充剩余像素,导致后续XOR运算完全偏离预期。
  2. 图像格式转换干扰:将Payload图像保存为PNG再读取时,ASCII值会被转换为图像像素值(白色为255、黑色为0),破坏原始字节数据。
  3. 解码逻辑错误:用while img_data_as_bytes[i]读取数据时,遇到像素值0会提前终止,无法完整提取Payload。

修复后的代码

import qrcode
from PIL import Image, ImageChops

# 每次生成QR都创建新对象,避免数据累积
def create_qrcode(data):
    qr = qrcode.QRCode(
        version=5,
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=10,
        border=2,
    )
    qr.add_data(data)
    qr.make(fit=True)
    return qr.make_image(fill_color="black", back_color="white")

msg = "25.61795.?000001?.907363.02"
sct = "secret message test"

# 生成原始QR图
pure_qr_code = create_qrcode(msg)
pure_qr_code.save("out.png")

# 扩展Payload到与QR图像素总数一致
pixel_count = pure_qr_code.size[0] * pure_qr_code.size[1]
encoded_data_pre_xor = []
while len(encoded_data_pre_xor) < pixel_count:
    encoded_data_pre_xor.extend([ord(c) for c in sct])
encoded_data_pre_xor = encoded_data_pre_xor[:pixel_count]

# 内存中直接创建Payload图像,跳过保存再读取的步骤
encoded_data_as_img = Image.new(mode=pure_qr_code.mode, size=pure_qr_code.size)
encoded_data_as_img.putdata(encoded_data_pre_xor)

# 编码:执行XOR运算
result = ImageChops.logical_xor(pure_qr_code, encoded_data_as_img)
result.save("result.png")

# 解码:反向XOR还原Payload
result_img = Image.open("result.png")
decoded_img = ImageChops.logical_xor(result_img, pure_qr_code)
decoded_img.save("result2.png")

# 提取并还原原始Payload
img_data = decoded_img.getdata()
encoded_data_after_xor = img_data[:len(sct)]
decoded_sct = ''.join([chr(val) for val in encoded_data_after_xor])

print("原始Payload:", sct)
print("解码后Payload:", decoded_sct)

关键修改点

  • 每次生成QR都实例化新的QRCode对象,避免旧数据残留
  • 将Payload扩展到与QR图像素总数一致,避免自动填充0
  • 直接在内存中处理Payload图像,跳过PNG格式转换环节
  • 解码时直接提取对应长度的数据,避免提前终止读取

内容的提问来源于stack exchange,提问作者Victor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 23:05:00