ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

LSB隐写从原理到实战:LSB替换、位平面分析与Python实现

LSB隐写从原理到实战:LSB替换、位平面分析与Python实现 简介面向信息安全初学者与图像隐写研究者的MATLAB实现资源聚焦LSB替换隐写通过修改图像像素最低有效位完成秘密信息嵌入并可从载体图中逆向提取。压缩包为zip格式共5个文件含LSBmain.m主程序、LSB_en.m嵌入脚本、LSB_de.m提取脚本三个MATLAB源文件以及Lena.tiff、Baboon.tiff两张标准测试图像包体仅462KB适合快速下载与本地复现。资源可完整演示预处理、二进制编码、位替换、保存载体与信息提取的流程可结合两幅测试图观察LSB嵌入对人眼视觉影响极小同时了解该方法对压缩、缩放、滤波等操作敏感且易被检测的局限性。目前已有360人浏览学习对正在准备信息隐藏实验或课程设计、希望掌握基础隐写实现与验证思路的读者尤其适用。无论是初学信息隐藏还是复现课程设计都能借助这套轻量代码快速跑通嵌入—提取闭环。1. 先把 LSB 隐写这件事说透把一段文本藏进一张 1024×1024 的 PNG 图片里保存后文件大小只有轻微变化肉眼来回对比也看不出差别但知道规则的人能逐位把消息原样读回来——这就是 LSB 隐写。LSB 是 Least Significant Bit最低有效位LSB 替换是这个家族里最原始也最常被当作出题点的手法直接拿秘密比特覆盖像素通道的最低一位。LSB 隐写、LSB 替换这几个关键词经常一起出现在 CTF 图片隐写、数字水印和取证对抗领域里标题里的 lionvpo 通常是题目或工具代号不影响对解法本身的理解。下面按嵌入原理、可运行代码、参数与抗检测、CTF 实战这条线讲完新手能照着跑通熟手也能拿到参数边界和检测套路的参照。2. LSB 替换的嵌入原理位平面、通道与直方图2.1 从 RGB 像素到 8 个位平面一张 24 位真彩图里每个像素由 R、G、B 三个通道组成每个通道用一个字节表示取值范围 0255。把字节展开成二进制位从高到低依次是第 7 位到第 0 位第 0 位就是最低有效位。图像有 8 个位平面权重差异极大第 7 位是最高有效位决定主颜色基调往低位走每个位置贡献的灰度差按 2 的幂递减第 0 位只贡献 1/255 的亮度差在普通显示器上根本无法单独分辨。举例某个像素通道值是 200二进制是 11001000改成 201即 11001001只动了最低位。显示亮度变化是 200/255 与 201/255 的区别人眼基本感知不到。秘密信息的每个二进制位按约定顺序写进这些最低位嵌入就完成了。载体图片纹理越丰富、边缘越多这 1 个灰度级的扰动就越难被察觉反过来纯色天空、渐变背景这类平滑区域对改动极其敏感几十个字节都可能留下痕迹。2.2 用位平面拆图直观理解藏在哪动手写嵌入代码之前习惯上先把载体拆成 8 个位平面看一眼确认最低位是不是真的像噪声。下面这段代码把灰度图的 8 个位平面分别存成图片from PIL import Image import numpy as np img Image.open(carrier.png).convert(L) arr np.array(img) for i in range(8): plane (arr i) 1 # 取出第 i 位 out (plane * 255).astype(np.uint8) # 0/1 映射成黑白 Image.fromarray(out).save(fplane_{i}.png)arr i把第 i 位移到最低位 1把其他位清零plane里每个位置只有 0 或 1。保存后观察会发现plane_7、plane_6 能清晰看出画面轮廓越往低位平面走轮廓越淡到 plane_0、plane_1 基本就是雪花噪声。高位位平面规律性极强篡改会被一眼看穿低位位平面本来就混乱改掉几个 bit 不具有视觉显著性。LSB 隐写就是利用这个特性把秘密信息埋进这套本就混乱的最低平面里。2.3 同一条消息两种改法LSB 替换与 LSB 匹配LSB 替换是最直接的实现new_val (val 0xFE) | msg_bit最低位被强制覆盖成消息位。LSB 匹配则换了个思路先读原像素最低位如果消息位相同就保持原值不同就执行加减 1。以像素值 200最低位 0和消息位 1 为例替换会固定变成 201匹配可以在 199 和 201 里随机选一个也把最低位置为 1但改动幅度和方向带有随机性。两种改法嵌入后的最低位都等于消息位差别体现在统计特征上嵌入率升高时替换会产生明显的值对频率趋同现象也就是 (2i, 2i1) 这两档灰度出现次数越来越接近匹配因为引入了 ±1 的随机选择保留了原始直方图的部分不规则性。第 4 章会介绍检测器如何利用这个差异。另外要注意这个原理只对 PNG、BMP 这类无损格式成立JPEG 的有损压缩会直接破坏最低位所以 LSB 题的载体几乎不会是 JPEG。3. 用 Python 从零实现 LSB 替换的嵌入与提取3.1 嵌入端加一个 4 字节长度头再写入直接把消息比特平铺进像素虽然简单但提取时不知道消息有多长。常见做法是像网络协议一样加固定长度的头用 4 字节大端整数记录消息字节数再接原始数据。完整嵌入脚本如下import struct import numpy as np from PIL import Image def embed(carrier: str, out: str, secret: bytes): img Image.open(carrier).convert(RGB) arr np.asarray(img) # H x W x 3 # 4 字节长度头 消息逐字节拆成位高位在前 payload struct.pack(I, len(secret)) secret bits [] for byte in payload: for p in range(7, -1, -1): bits.append((byte p) 1) flat arr.reshape(-1) if len(bits) len(flat): raise ValueError( f载体容量不足: 需要 {len(bits)} 位, 只有 {len(flat)} 位) for i, bit in enumerate(bits): flat[i] (flat[i] 0xFE) | bit # 清最低位写入消息位 Image.fromarray(flat.reshape(arr.shape)).save(out) # 用法示例 embed(carrier.png, stego.png, bflag{hello_lsb})每一步的逻辑struct.pack(I, len(secret))把消息字节数按大端序打包成 4 字节提取端必须按同样的字节序解析range(7, -1, -1)保证一个字节内高位在前否则还原时拼出来的字节会左右颠倒flat把 H×W×3 的数组展平成一维逐位写入天然完成了逐行逐像素逐通道的扫描。 0xFE是清零最低位| bit写入消息位。这里有一个容易踩的坑Image.fromarray对 dtype 敏感arr必须是 uint8。Pillow 默认读 RGB 图返回的就是 uint8但如果中途做过归一化、像素值加减或浮点运算务必补一句astype(np.uint8)否则保存时直接报错或写出错误颜色。3.2 提取端先读长度再按格式取消息提取是嵌入的逆过程按同样顺序扫像素取每个通道最低位先凑 32 位还原出消息长度再取后面的字节def extract(stego: str) - bytes: arr np.asarray(Image.open(stego).convert(RGB)) flat arr.reshape(-1) bits [int(v) 1 for v in flat] def take(n: int): chunk bits[:n] del bits[:n] out bytearray() for i in range(0, len(chunk), 8): byte 0 for b in chunk[i:i8]: byte (byte 1) | b out.append(byte) return bytes(out) length struct.unpack(I, take(32))[0] return take(length * 8)int(v) 1取最低位del bits[:n]是原地删除让后面的take每次从剩余头部继续取。这个方法直观但在工程代码里反复删除列表头部会引入不必要的开销。更稳妥的做法是维护一个偏移指针idx每次从bits[idx:idxn]取值再累加idx避免修改共享状态。上面的写法是为了贴合先长度后内容的格式便于阅读。如果提取出来是乱码优先检查三点是否用了大端序读长度I和I结果不同字节内位序是否一致嵌入用高位在前提取就要高位在前拼接载体通道顺序是否被工具改动过比如某些编辑器会把 RGB 转成 BGR 再保存导致位序错位。3.3 容量、载体格式与 3 个必调参数容量计算公式是可用比特数 宽 × 高 × 通道数 × 占用位数。一张 1024×1024 的 PNG三通道各取 1 位可嵌入1024×1024×3 3,145,728位约 393KB 文本。但实际使用不能按满算要扣掉 4 字节长度头还要考虑第 4 章说的检测阈值。参数说明常用取值通道选择只改 R/G/B 还是全部通道全部通道容量最大单用 B 通道人眼更不敏感位平面用第 0 位还是低 2 位默认第 0 位低 2 位容量翻倍但检测特征明显扫描顺序逐行、逐列、Z 字形、随机逐行最简单随机抗检测更好需要额外存种子提示嵌入后可以用 PSNR 粗算一下图片失真公式是10 * log10(255^2 / MSE)。LSB 满嵌时 MSE 的理论值接近 1PSNR 约 48dB人眼无法区分但统计检测器仍能看出问题。高的 PSNR 只代表肉眼安全不代表检测安全。4. LSB 隐写参数与抗检测扫描顺序、随机种子与嵌入率4.1 顺序嵌入的特征为什么左上角会暴露逐行扫描意味着嵌入从图片左上角开始连续写几十 KB 后前几行像素的最低统计特征和整图其余部分明显不一致。用 Stegsolve 打开红/绿/蓝通道的 LSB 平面会发现左上区域出现有规律的条纹或明显色块这在平滑背景的载体上尤其刺眼。避免开头即暴露的常见做法是引入随机间隔用密钥做种生成像素坐标的伪随机序列让消息比特分散到整张图。import random def build_positions(w: int, h: int, channel: int, seed: int) - list[tuple[int, int, int]]: rng random.Random(seed) pos [(x, y, c) for c in range(channel) for y in range(h) for x in range(w)] rng.shuffle(pos) return pos pos build_positions(1024, 1024, 3, 20240517)random.Random(seed)保证同一把种子在任何机器上生成完全一致的序列提取端只要知道种子和通道数就能按相同顺序取位。channel参数决定参与嵌入的通道数3 表示 RGB 全嵌1 表示只嵌蓝色通道。这个方案的代价是把你知道嵌入位置变成了你持有那把种子密钥泄露等于消息泄露所以种子要当作对称密钥管理。4.2 嵌入率与直方图对称检测器在看什么即便嵌入位置做了随机化嵌入率过高仍然会在统计规律上暴露。对 LSB 替换来说最典型的破绽是灰度值成对出现的频率趋于一致。正常照片里相邻灰度值 (2i, 2i1) 的像素数量关系是随机的LSB 替换嵌入率升高后消息位 0/1 近似等概率导致每对值中出现较少的一方向另一方向靠拢两者频率越来越接近。RS 分析和卡方攻击检测的核心就是这个特征。经验阈值大致是嵌入率低于 10% 时常规统计检测在高纹理载体上很难给出确定结论高于 30% 时RS 检测基本一抓一个准。所以实战中我一般反着算先定一个能接受的最大嵌入率再算消息体大小、挑载体。如果消息确实很大优先换更高分辨率或更高噪声水平的载体图而不是硬塞。平滑区域对嵌入率的容忍度显著更低纯色渐变图里哪怕几百字节直方图也能看出异常。4.3 顺序替换、随机替换与 LSB 匹配的选型对比三者对应不同的威胁模型如果对手只是肉眼看图和跑 exiftool顺序替换就够用如果对手会跑 zsteg 和 RS 检测就要考虑随机位置或 LSB 匹配。方案嵌入规则提取条件主要局限顺序 LSB 替换强制覆盖最低位起始点、扫描顺序左上角位平面出现条纹值对频率趋同随机 LSB 替换强制覆盖最低位起始点、扫描顺序、随机种子仍有值对频率趋同的统计特征LSB 匹配最低位不同则 ±1起始点、扫描顺序需要随机源决定 1 还是 -1实现略复杂LSB 匹配的价值不在隐蔽性上限更高而是它不产生 LSB 替换那种成对值频率对称的强特征对卡方检测的抗性更好。但要注意匹配在像素值为 0 或 255 时只能单向调整连续的大块纯色区域会留下细微的梯度痕迹。没有万能的方案只有针对检测方式的取舍。5. CTF 图片隐写拿到题先做这三件事5.1 先跑 file、strings、exiftool快速排除干扰CTF 里 LSB 隐写题通常给 PNG 或 BMP。收到附件先做一套三连检测不要急着拖进画图软件看file mystery.png strings -n 8 mystery.png | head -20 exiftool mystery.png | grep -i comment\|author\|software这三条命令能排除一半以上的干扰项文件头被改修回来就能打开、明文 flag 直接躺在 strings 输出里、Exif 注释字段藏线索。如果file显示是 JPEGLSB 像素思路要立刻停手转去看 DCT 系数检索词里常见的 CTF jpeg 隐写题、pdf 隐写题考的不是像素最低位PDF 的隐写点在对象流、压缩流和字体描述里。5.2 zsteg 自动扫描与 Stegsolve 眼检互补对 PNG/BMP固定流程是先跑 zsteg 全参数扫描再开 Stegsolve 人工核对zsteg -a mystery.pngzsteg 输出里b1,rgb,lsb,xy这类前缀是有效信息b1表示每通道取 1 位rgb是通道顺序lsb是低位平面xy是扫描方向。如果 zsteg 扫出来的内容读不通先别急着排除 LSB试着把位序反着拼字节——不少题目故意把消息做逐字节位反转位平面上能看到文本形状但常规工具读出来是乱的。Stegsolve 的价值在于人眼确认切到 Red/Green/Blue 平面的第 0 位有规律条纹的区域往往就是嵌入区这能帮你判断题目到底嵌入在哪个通道、占了几位。5.3 三个容易漏的 LSB 变体第一个是低 2 位嵌入不只用第 0 位连第 1 位一起占用容量翻倍。提取时要用flat[i] 0x03一次还原两位zsteg 的默认模式不一定覆盖这种情况需要手动写脚本按两位拼字节。第二个是单通道嵌入只改蓝色通道红色和绿色完全干净。视觉上最隐蔽但 Stegsolve 切到 Blue plane 0 时规律性会暴露。判断技巧是把三个通道的 LSB 平面并排看只有其中一块有明显周期性条纹就是单通道 LSB。第三个是音频 WAV 的 LSB原理和图片完全一致只是载体从像素变成 PCM 采样点。16 位采样里改最低位听感毫无变化提取时把采样值展平成一维数组套用第 3 章的嵌入和提取逻辑即可。处理这类题时先确认文件尾有没有附加数据binwalk扫一下比直接听快得多。最后补一个验证技巧嵌入 LSB 会改变 PNG 位流的压缩特性文件体积通常会有百分之几的变化。如果一张图文件的体积与打开后看到的图像复杂度明显不匹配或者文件尾部有多余块这本身就是被动过手脚的旁证。本文还有配套的精品资源点击获取
返回列表