当前位置: 首页 > news >正文

用Python把文字或小图藏进照片里:基于RGB最低位的隐写工具

本文还有配套的精品资源,点击获取

简介:这个工具用Python实现LSB(最低有效位)图像隐写,把秘密信息悄悄塞进普通图片的像素颜色值里。支持两种嵌入方式:一是把任意文本字符串转成二进制,逐位写入载体图每个像素R/G/B通道的最低位;二是把一张小尺寸PNG或JPEG图片作为隐藏内容,同样按位嵌入到大图中。整个过程不改变原图尺寸、格式和视觉效果,输出仍是标准PNG或JPEG文件。程序用OpenCV读写图像,numpy做位运算,自动按位层顺序填充——先填所有像素的第1个最低位,再填第2位,直到数据全部写完。附带requirements.txt列明依赖(opencv-python、numpy),README.md有详细命令示例,比如如何编码文本、如何提取、如何嵌入另一张图,还有对应解码脚本的使用说明。MIT开源协议,无需编译,下载即用,适合信息安全教学、CTF入门练习或轻量级隐蔽传输需求。

1. 项目概述:为什么一张普通照片能变成“数字保险箱”

你有没有试过把一张风景照发给朋友,结果对方打开后,发现图里藏着一段加密留言,甚至是一张小猫的缩略图?这不是魔术,也不是AI生成——这是LSB隐写术(Least Significant Bit Steganography)在真实世界里的轻量级落地。我从2016年开始带信息安全入门课,每年第一堂实操课必讲这个:用Python把文字或小图“塞进”照片里,不改尺寸、不换格式、肉眼几乎看不出差别。它不像密码学那样强调“不可破解”,而是追求“不可察觉”。就像往一桶清水里滴一滴蓝墨水,整桶水还是透明的,但懂行的人知道里面藏了东西。

这个工具的核心关键词就是你看到的三个:LSB隐写、Python隐写、图像隐写。它不依赖任何黑盒库或云端服务,全程本地运行,只靠opencv-python读取像素矩阵、numpy做高效位运算。整个逻辑干净得像一张白纸:每个像素由R、G、B三个字节组成(各0–255),而每个字节的最低位(bit 0)对颜色影响极小——把255(11111111)改成254(11111110),人眼根本分不出区别;同理,把128(10000000)改成129(10000001),红色通道只浮动了0.4%。正是这种“视觉冗余”,成了我们藏信息的黄金缝隙。

它解决的实际问题很具体:教学场景中,学生需要亲手验证“信息可以隐藏而非加密”这一基本概念;CTF新手赛里,一道题给出一张看似普通的PNG,flag就藏在最低位;甚至在内部协作中,临时传个短口令或流程图截图,又不想走IM或邮件留痕——这时候导出一张“无害”的风景图,比发一段base64字符串更自然。它不适合传GB级视频或高敏密钥,但对几百字文本、32×32图标、二维码截图这类轻量内容,嵌入后PS打开对比原图,直方图重叠度>99.7%,连专业修图师都懒得调色差。我试过把《小王子》第一章前200字藏进一张1920×1080的夕阳照,用ImageMagick的compare -metric AE测差异像素数,结果是0——不是近似,是真·零像素变动(因为只动bit0,高位全保留)。这就是LSB的底气:不破坏结构,只微调末梢。

2. 整体设计与思路拆解:为什么选LSB?为什么是“按位层填充”?

2.1 为什么LSB是入门隐写的最优解?

在图像隐写领域,方案其实不少:DCT域(JPEG常用)、DWT小波、边缘区域替换、甚至神经网络生成对抗隐写。但对初学者和轻量需求,LSB几乎是唯一合理选择。原因有三:

第一,原理透明,可验证性强。每个像素RGB值都是整数,二进制表示直观可见。比如像素(231, 124, 67),转二进制是(11100111, 01111100, 01000011),最低位就是最后那个1、0、1。你手算就能验证嵌入是否正确,不需要理解傅里叶变换或卷积核。我在课堂上让学生用Excel手动填10个像素的bit0,30分钟内所有人都能写出自己的嵌入逻辑——这是其他方法做不到的。

第二,实现成本极低,无外部依赖陷阱。DCT隐写必须处理JPEG压缩的量化表,稍有不慎就丢数据;DWT需要PyWavelets等重型库,安装还常报错。而LSB只需要读像素、改bit、写回,OpenCV+NumPy两库包打天下。更重要的是,它天然兼容PNG(无损)和JPEG(有损但bit0抗扰强),不用纠结格式转换损耗。

第三,安全边界清晰,不制造虚假安全感。很多人误以为“隐藏=加密”,但LSB本身不提供加密!它只是把数据藏起来,一旦被检测(比如用RS分析、SPA攻击),信息立刻暴露。这恰恰是教学价值所在:它逼你正视一个事实——隐蔽性(steganography)和机密性(cryptography)是两回事。我们教学生先用LSB藏,再用AES加密后再藏,这才是完整链路。工具里没加AES不是缺陷,而是刻意留白——就像木工课先教凿子怎么握,再教榫卯怎么设计。

2.2 “按位层填充”设计背后的工程权衡

原文提到“先用所有像素的第1位,填满后再用第2位”,这个设计看似简单,实则经过多次踩坑验证。早期版本我试过“逐像素填满3位再进下一个”,结果发现两个致命问题:

  • 数据碎片化导致提取失败:假设载体图有10000像素,每个像素R/G/B共3字节→24位可用空间。若嵌入19200位数据(2400字节),按“逐像素填满3位”逻辑,前8000像素填满24000位,但最后800位可能跨像素溢出,提取时需精确追踪“当前像素已填几位”,代码极易出错。而“按位层”模式下,第1位层(bit0)覆盖全部10000像素→10000位空间,第2位层(bit1)再覆盖全部10000位……逻辑彻底线性化,提取时只需按层遍历,无状态依赖。

  • 视觉干扰可控性更强:虽然单个bit0改动人眼难辨,但若同一像素的R、G、B三个通道bit0同时被改(比如全从1变0),该像素整体亮度会微降。而“按位层”确保同一像素在第1层只动R的bit0,第2层动G的bit0,第3层动B的bit0……改动被均匀摊开,避免局部色块突变。我用Photoshop的“通道混合器”单独拉高R通道bit0层,发现噪点分布比随机填更均匀,直方图峰谷更平滑。

提示:这个设计牺牲了理论最大容量(传统LSB每像素3位,本方案每像素也是3位,但分层后实际利用率略低约0.3%,因末尾层可能未填满),换来的是鲁棒性和可维护性。对教学和轻量场景,这是值得的trade-off。

2.3 为什么支持“文本”和“小图”双模式?它们的本质区别是什么?

文本嵌入和图像嵌入,表面都是“塞数据”,底层逻辑却完全不同:

  • 文本模式:本质是字符编码转换。输入字符串经UTF-8编码成字节流,再转二进制串。例如”Hi” → b’\x48\x69’ → ‘0100100001101001’(16位)。嵌入时直接按序写入bit0层。关键约束是长度可预知:UTF-8编码后字节数×8=总位数,因此能精确计算需多少像素,提前截断或报错。

  • 图像模式:本质是像素矩阵重组。待隐藏的小图(如32×32 PNG)被OpenCV读为(height×width×3)数组,展平为一维字节流,再转二进制。但这里有个隐藏陷阱:PNG自带IHDR、IDAT等chunk头,直接读像素会漏掉这些元数据!所以工具强制要求小图为已解码的原始像素(即cv2.imread()返回的ndarray),而非原始文件字节。这意味着你不能直接塞一张带alpha通道的PNG进去——OpenCV默认读为BGR三通道,alpha会被丢弃。解决方案很简单:用PIL先转为RGB再转numpy,或提前用convert -alpha off input.png output.png剥离alpha。

注意:小图尺寸限制不是为了“防爆内存”,而是视觉保真。实验表明,当隐藏图面积>载体图5%时,LSB嵌入后局部色阶偏移开始可察(尤其在天空、水面等平滑区域)。所以工具默认限制小图长宽≤载体图的1/4,且总面积≤3%。这个阈值是我用200张不同场景图测试得出的经验值,不是拍脑袋定的。

3. 核心细节解析与实操要点:从像素读取到bit操作的全流程拆解

3.1 图像读取与预处理:为什么必须用OpenCV而不是PIL?

虽然PIL也能读图,但本工具坚持用OpenCV,原因有三:

  • 通道顺序统一性:OpenCV默认读BGR,而LSB操作需明确区分R/G/B。工具内部做了标准化处理:读入后立即cv2.cvtColor(img, cv2.COLOR_BGR2RGB)转为RGB,确保后续索引img[i,j,0]恒为R通道。PIL读图虽默认RGB,但遇到CMYK或RGBA模式需额外判断,增加分支逻辑。

  • 数据类型严格控制:OpenCV读图返回uint8ndarray(0–255),而PIL.Image转numpy可能产生int32float64,位运算前需强制.astype(np.uint8),否则& 1操作会出错。工具中所有像素操作前都有assert img.dtype == np.uint8校验。

  • 写入兼容性保障:OpenCV的cv2.imwrite()对PNG支持无损,对JPEG自动处理色彩空间;PIL保存JPEG时若不指定quality=95,默认压缩率过高,bit0可能被二次压缩抹掉。曾有学员用PIL保存后提取失败,查了3小时才发现是JPEG量化表捣鬼。

实操中一个易错点:读取灰度图需主动拒绝。LSB需R/G/B三通道,灰度图只有单通道。工具在load_image()函数开头就检查len(img.shape) != 3 or img.shape[2] != 3,直接抛ValueError("Input image must be RGB with 3 channels")。别指望自动转——自动转会引入插值噪声,污染bit0。

3.2 文本编码:UTF-8、长度标记与终止符的设计哲学

文本嵌入不是简单把字符串转二进制就完事。核心挑战是:提取端如何知道“数据到此为止”?如果不标记长度,提取程序会一直读到图像末尾,把噪声当数据,解出一堆乱码。

本工具采用三层标记机制:

  1. 前置长度头(4字节):用struct.pack('>I', len(text_bytes))将UTF-8编码后的字节数打包为大端32位整数。例如”Hello”编码为5字节,头就是b'\x00\x00\x00\x05'。选择大端是为了跨平台一致(x86/ARM都认)。

  2. UTF-8正文:直接写入编码后的字节流,不做任何base64或hex编码——那些是传输层的事,LSB只管位存储。

  3. 隐式终止:长度头已声明数据长度,提取时读够位数即停,无需额外终止符(如\x00)。这避免了“文本含\x00”导致提前截断的bug。

实操心得:曾有学员输入中文”你好”,UTF-8编码为6字节(b'\xe4\xbd\xa0\xe5\xa5\xbd'),但长度头写成小端struct.pack('<I', 6),提取端用大端读,得到错误长度256。后来我们在README里加粗强调:“长度头必须大端,否则跨平台失效”。

3.3 小图嵌入:尺寸适配、通道对齐与边界处理

小图嵌入比文本复杂得多,关键在空间映射。假设载体图1920×1080(2,073,600像素),小图32×32(1024像素),如何把1024像素的RGB值,精准塞进207万像素的bit0层?

答案是:不按像素位置映射,而按bit序号线性映射

  • 步骤1:小图读入后,flatten()成一维数组(1024×3=3072字节),再转二进制串(3072×8=24576位)。
  • 步骤2:计算所需最小像素数:ceil(24576 / 3) = 8192像素(因每像素提供3位:R-bit0, G-bit0, B-bit0)。
  • 步骤3:从载体图左上角开始,按行优先顺序取前8192个像素,依次填入24576位。

这样设计的好处是:完全规避坐标计算错误。如果按“小图(i,j)→载体图(istride, jstride)”缩放,涉及浮点除法和取整,不同尺寸组合下边界像素可能重复或遗漏。而线性索引,只要total_bits <= carrier_pixels * 3,就100%可嵌入。

但有个硬约束:小图必须能被完整嵌入,不能截断。所以工具在嵌入前校验:

hidden_bits = hidden_img.size * 8 # hidden_img.size = height*width*3 max_carrier_bits = carrier_img.size // 3 * 3 # 确保整除 if hidden_bits > max_carrier_bits: raise ValueError(f"Hidden image too large: need {hidden_bits} bits, only {max_carrier_bits} available")

注意:carrier_img.size返回总元素数(height×width×3),除以3得像素数,再×3得可用bit数——这个计算必须用整数运算,避免浮点误差。我见过有人用int(carrier_img.size * 0.999),结果在10000像素图上少算3位,嵌入失败。

3.4 位操作核心:numpy向量化 vs Python循环的性能生死线

LSB最耗时的操作是“把二进制位写入像素字节”。早期用纯Python循环:

for i in range(len(bits)): pixel_idx = i // 3 channel_idx = i % 3 bit_pos = 0 # always LSB carrier_flat[pixel_idx, channel_idx] = (carrier_flat[pixel_idx, channel_idx] & 0xFE) | int(bits[i])

处理100万像素要12秒。

换成numpy向量化后:

# bits_arr: shape=(N,) uint8 array of 0/1 # carrier_flat: shape=(M,3) uint8 array of pixels n = len(bits_arr) carrier_flat[:n//3, 0] = (carrier_flat[:n//3, 0] & 0xFE) | bits_arr[0::3] carrier_flat[:n//3, 1] = (carrier_flat[:n//3, 1] & 0xFE) | bits_arr[1::3] carrier_flat[:n//3, 2] = (carrier_flat[:n//3, 2] & 0xFE) | bits_arr[2::3]

时间降到0.18秒,提速66倍。

原理很简单:numpy用C实现,内存连续访问,CPU缓存友好;而Python循环每次都要查字典、类型检查、引用计数。工具中所有位操作都封装在_embed_bits_vectorized()函数里,连注释都写着:“此处禁止用for循环,否则性能归零”。

实操警告:向量化要求bits_arr长度能被3整除(因R/G/B三通道轮询)。若总位数24577,末尾1位无法分配。解决方案是补零对齐bits_arr = np.pad(bits_arr, (0, 3 - len(bits_arr)%3), 'constant')。补的零写入后不影响视觉(bit0=0),提取时按长度头截断即可。

4. 实操过程与核心环节实现:从命令行到完整嵌入流程

4.1 环境准备与依赖验证:为什么requirements.txt只列两行?

requirements.txt内容极简:

opencv-python>=4.5.0 numpy>=1.21.0

没有clickargparse、甚至没typing——因为Python 3.7+已内置。精简依赖是工程洁癖:每多一个依赖,就多一分CI失败风险、多一分用户安装报错概率。我统计过,学员环境里pip install opencv-python失败率约12%(主要因网络或wheel不匹配),而numpy失败率<0.5%。所以工具启动时第一件事就是:

try: import cv2, numpy as np except ImportError as e: print(f"Missing dependency: {e}. Run 'pip install -r requirements.txt'") exit(1)

验证OpenCV版本也很关键:4.5.0以下不支持cv2.IMWRITE_PNG_COMPRESSION参数,无法控制PNG压缩质量;而JPEG写入在4.2.0以下有bit0被意外清零的bug(已提交PR修复)。所以版本锁是刚需。

4.2 命令行接口设计:为什么用subcommand而非一堆flag?

工具支持三种操作:
-python LSBSteg.py encode-text --carrier cat.jpg --text "secret" --output stego.png
-python LSBSteg.py encode-image --carrier beach.jpg --hidden logo.png --output stego.jpg
-python LSBSteg.py decode --input stego.png --output recovered.txt

采用argparse的subcommand模式,而非--mode encode-text,原因很实在:用户记忆成本。当学员第一次用,他记不住--mode text还是--mode image,但encode-textencode-image一看就懂。而且subcommand天然隔离参数集——encode-text需要--textencode-image需要--hidden,不会出现“传了–text又传–hidden”的冲突。

每个subcommand的参数都带required=True,缺失时报错明确:

usage: LSBSteg.py encode-text [-h] --carrier CARRIER --text TEXT --output OUTPUT LSBSteg.py encode-text: error: the following arguments are required: --carrier, --text, --output

4.3 文本嵌入全流程实录:以”Hello World”为例

我们一步步走完encode-text全过程(假设载体图cat.jpg为1280×720):

步骤1:加载并校验载体图

python LSBSteg.py encode-text --carrier cat.jpg --text "Hello World" --output stego.png
  • OpenCV读cat.jpgshape=(720,1280,3),dtype=uint8,size=2764800(像素数)
  • 计算可用bit数:2764800 × 3 = 8,294,400位

步骤2:编码文本并计算长度头
-"Hello World"UTF-8编码 →b'Hello World'(11字节)
- 长度头:struct.pack('>I', 11)b'\x00\x00\x00\x0b'(4字节)
- 总数据:4 + 11 = 15字节 = 120位

步骤3:生成bit数组
-length_bits = np.unpackbits(np.frombuffer(b'\x00\x00\x00\x0b', dtype=np.uint8))→ 32位
-text_bits = np.unpackbits(np.frombuffer(b'Hello World', dtype=np.uint8))→ 88位
- 合并:all_bits = np.concatenate([length_bits, text_bits])→ 120位
- 补零对齐:120 % 3 == 0,无需补

步骤4:向量化嵌入
-carrier_flat = carrier_img.reshape(-1, 3)(2764800, 3)
- 取前ceil(120/3)=40个像素:carrier_flat[:40]
- 分配:bits[0::3]→R通道,bits[1::3]→G通道,bits[2::3]→B通道
- 执行:carrier_flat[:40, 0] = (carrier_flat[:40, 0] & 0xFE) | bits[0::3]

步骤5:保存输出
-cv2.imwrite('stego.png', cv2.cvtColor(carrier_img, cv2.COLOR_RGB2BGR))
- 关键:转回BGR再保存,因OpenCV写图要求BGR顺序

最终stego.pngcat.jpgdiff命令对比,二进制差异仅在像素字节的bit0位,其余7位完全相同。

4.4 小图嵌入全流程实录:以32×32二维码为例

假设logo.png是32×32黑白二维码(无alpha),beach.jpg为1920×1080:

步骤1:计算空间需求
-logo.png读入:shape=(32,32,3)size=3072字节 →3072×8=24576
-beach.jpg1920×1080×3=6,220,800像素 →6,220,800×3=18,662,400位可用
- 24576 < 18662400,满足条件

步骤2:展平与位转换
-hidden_flat = hidden_img.reshape(-1)(3072,) uint8
-hidden_bits = np.unpackbits(hidden_flat)(24576,) uint8(0/1)

步骤3:嵌入定位
- 需像素数:ceil(24576/3)=8192
- 从beach.jpg左上角取8192像素:carrier_flat[:8192]
- 分配:hidden_bits[0::3]→R,hidden_bits[1::3]→G,hidden_bits[2::3]→B

步骤4:视觉效果验证
用ImageMagick对比:

compare -metric RMSE beach.jpg stego.jpg null: 2>&1 # 输出:0 (0) # RMSE=0,表示无像素值差异(仅bit0变,高位不变)

用GIMP打开stego.jpg,切换到“通道”面板,单独查看R通道bit0层(用“颜色→阈值”拉到极端),能看到二维码轮廓——这就是隐藏内容的“影子”。

4.5 解码流程:如何从stego.png里捞出原始数据?

解码是嵌入的逆过程,但更需谨慎:

文本解码关键步骤:
- 读stego.pngcarrier_img
-carrier_flat = carrier_img.reshape(-1, 3)
- 提取bit0层:r_bits = carrier_flat[:, 0] & 1g_bits = carrier_flat[:, 1] & 1b_bits = carrier_flat[:, 2] & 1
- 合并:all_bits = np.empty(carrier_flat.shape[0] * 3, dtype=np.uint8)
all_bits[0::3] = r_bits; all_bits[1::3] = g_bits; all_bits[2::3] = b_bits
- 取前32位(长度头):len_bits = all_bits[:32]
- 转整数:data_len = int.from_bytes(np.packbits(len_bits).tobytes(), 'big')
- 取后续data_len*8位:text_bits = all_bits[32:32+data_len*8]
- 转字节:text_bytes = np.packbits(text_bits).tobytes()
- 解码:text = text_bytes.decode('utf-8')

小图解码关键步骤:
- 已知隐藏图尺寸(工具不存尺寸头,需用户指定或从长度推断)
- 本工具采用用户指定尺寸decode-image --input stego.png --output logo.png --hidden-size 32x32
- 提取bit0层得all_bits
- 计算需位数:32*32*3*8 = 24576
- 取前24576位 →hidden_bits
- 转字节:hidden_bytes = np.packbits(hidden_bits).tobytes()
- 重塑:hidden_array = np.frombuffer(hidden_bytes, dtype=np.uint8).reshape(32,32,3)
- 保存:cv2.imwrite('logo.png', cv2.cvtColor(hidden_array, cv2.COLOR_RGB2BGR))

注意:np.packbits()默认每8位打包成1字节,但若hidden_bits长度非8倍数,末尾会补0。所以hidden_bytes长度可能略大于理论值,reshape前需截断:hidden_bytes = hidden_bytes[:32*32*3]

5. 常见问题与排查技巧实录:那些文档里不会写的坑

5.1 视觉异常:为什么嵌入后图片出现彩色噪点?

现象:嵌入文本后,原图天空区域出现细密红绿噪点。

根因:载体图是JPEG格式,且压缩质量低(如手机直出JPEG常为quality=75)。JPEG有损压缩会修改像素值,尤其是高频区域(边缘、纹理),bit0可能被二次量化抹掉或翻转。

排查
- 用identify -verbose cat.jpg | grep Quality查JPEG质量
- 用convert cat.jpg -colorspace Gray -define histogram:unique-colors=true -format "%k" histogram:info:看灰度级数,若<255说明压缩严重

解决
- 预处理:convert cat.jpg -quality 95 cat_highq.jpg
- 或换PNG载体:ffmpeg -i cat.jpg cat.png

实操心得:我建了个preprocess.sh脚本,自动检测JPEG质量,低于90则重压缩。学员再也不用问“为什么我的图嵌入后花屏”。

5.2 解码失败:为什么recover.txt全是乱码?

现象decode命令输出文件,但用cat recovered.txt显示\x00\x00\x00...

根因:长度头读取错误。常见于两种情况:
-字节序不匹配:嵌入用小端,解码用大端(或反之)
-起始偏移错位:解码时没从第一个像素开始读,而是从中间某像素读起

排查
- 用xxd -b stego.png | head -20看PNG文件头,确认IDAT数据块起始位置(LSB只动像素数据,不动chunk头)
- 检查carrier_flat是否真的从(0,0)开始:print(carrier_flat[0])应输出左上角像素RGB值

解决
- 统一用struct.unpack('>I', ...)读长度头
- 解码函数开头加断言:assert carrier_img[0,0,0] == original_first_pixel_r(需用户传原图比对)

5.3 小图失真:为什么recover_logo.png颜色发灰?

现象:隐藏的彩色logo解出来变成灰度图。

根因:OpenCV读图时,若源图是PNG带alpha通道,cv2.imread()默认丢弃alpha,但BGR转RGB时通道错位。例如原图RGBA,读成BGRA,转RGB后变成AGBR,颜色全乱。

排查
-print(hidden_img.shape),若为4通道,则含alpha
-print(hidden_img.dtype),确认是uint8

解决
- 预处理小图:convert logo.png -alpha off logo_noalpha.png
- 或代码中强制转RGB:from PIL import Image; img = Image.open('logo.png').convert('RGB'); hidden_img = np.array(img)

5.4 性能瓶颈:为什么10MB JPEG嵌入要2分钟?

现象:大图嵌入卡顿,CPU占用100%但进度条不动。

根因:OpenCV读JPEG时,默认启用多线程解码,但LSB嵌入是单线程位操作,线程竞争导致锁死。尤其在Mac M1上,OpenCV 4.5.5有已知bug。

排查
-time python LSBSteg.py ...看耗时分布
- 用htop观察线程数

解决
- 强制单线程:cv2.setNumThreads(1)
- 或换读图方式:PIL.Image.open().convert('RGB'),再np.array()

注意:PIL读大图内存占用高,但CPU友好。我们加了个--fast-mode开关,自动选最优路径。

5.5 安全警示:LSB能防什么?不能防什么?

必须说清楚,避免用户产生错误信任:

攻击类型LSB是否有效原因应对建议
人眼观察bit0变化<1%亮度
直方图分析LSB会使直方图偶数bins升高、奇数bins降低加入随机扰动(本工具暂未实现)
RS分析统计像素对关系可检测嵌入教学用途忽略,生产环境禁用
文件哈希比对嵌入后SHA256必然改变不用于完整性校验
JPEG重压缩⚠️高质量重压(quality≥95)可存活传输前告知接收方用lossless工具

最后分享个小技巧:想快速验证是否成功嵌入?用Linux命令:
```bash

提取所有R通道bit0,转ASCII

python -c “import numpy as np; print(‘’.join(chr(int(‘’.join(map(str, np.unpackbits(np.frombuffer(open(‘stego.png’,’rb’).read()[1000:],dtype=np.uint8)[:1000])[:8])),2)) for _ in range(10)))”
```
虽然丑,但5秒内看到”Hello”就说明通了——这才是工程师的debug哲学。

本文还有配套的精品资源,点击获取

简介:这个工具用Python实现LSB(最低有效位)图像隐写,把秘密信息悄悄塞进普通图片的像素颜色值里。支持两种嵌入方式:一是把任意文本字符串转成二进制,逐位写入载体图每个像素R/G/B通道的最低位;二是把一张小尺寸PNG或JPEG图片作为隐藏内容,同样按位嵌入到大图中。整个过程不改变原图尺寸、格式和视觉效果,输出仍是标准PNG或JPEG文件。程序用OpenCV读写图像,numpy做位运算,自动按位层顺序填充——先填所有像素的第1个最低位,再填第2位,直到数据全部写完。附带requirements.txt列明依赖(opencv-python、numpy),README.md有详细命令示例,比如如何编码文本、如何提取、如何嵌入另一张图,还有对应解码脚本的使用说明。MIT开源协议,无需编译,下载即用,适合信息安全教学、CTF入门练习或轻量级隐蔽传输需求。


本文还有配套的精品资源,点击获取

http://www.jsqmd.com/news/972544/

相关文章:

  • C语言代码考古神器:用cflow深度分析多文件项目,快速定位核心函数与依赖
  • 2026年靠谱的多节电动缸/江苏折返式电动缸厂家哪家好 - 行业平台推荐
  • LabWindows/CVI:电子工程师的GUI开发利器,C语言实现高效上位机
  • 从机器人到VR:用PCL点云库搞定3D数据处理,这份保姆级入门指南请收好
  • MATLAB vs Python:模糊控制实战,用洗衣机案例说透两者差异与选型
  • 从智能手表到电动汽车:拆解OTA差分升级背后的BSDiff算法与实战
  • Python 3.10安装后必做的5件事:从环境配置到写出你的第一个自动化脚本
  • 单片机PWM语音播放:ADPCM压缩与硬件滤波实战
  • 用MATLAB的LMgist工具箱5分钟搞定图像GIST特征提取(附完整代码)
  • MATLAB与Python双平台音频时频分析工具:STFT语谱图+小波能量分布可视化
  • 2026年靠谱的煤矿液压支架普阀/矿用液压支架阀/液压支架普阀/安徽矿用液压支架阀公司选择指南 - 品牌宣传支持者
  • 智能车竞赛避坑指南:如何用Apriltag实现稳定可靠的厘米级定位?
  • Zynq-7000 PL程序固化避坑指南:从Vivado Block Design配置到Vitis生成BOOT.BIN,这些细节错了就白干
  • 别再死记硬背CNN结构了!用PyTorch实战MNIST,带你真正理解卷积和池化
  • πMPC:并行预测时域与免构造的非线性MPC求解器
  • ARC-2随机信标验证实战:从VRF证明到可信任随机种子
  • SAP MM实战:跨公司采购组织配置详解(SPRO路径+避坑指南)
  • 旧安卓手机别扔!用Termux+Frp把它变成你的私人远程服务器(保姆级教程)
  • 电子工程师成长实战:从售后到研发的硬件设计核心能力与学习路径
  • 实战避坑:用Matplotlib和Seaborn画三维图时,你可能会遇到的5个常见问题及解决
  • 告别裸机I2C!用STM32 HAL库HAL_I2C驱动BH1750光照传感器的正确姿势
  • 网络海鲜市场系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
  • 告别数据打架!STM32G4 HAL库ADC多通道采集,这样管理数据才靠谱
  • 还在为Android支付集成头疼?试试这个2024年依然好用的EasyPay库(附避坑指南)
  • Snowflake与Domo Cloud Amplifier数据协同实战指南
  • QtChart动态曲线实战:用200ms定时器模拟工业数据采集与实时刷新(附完整源码)
  • 树莓派4B到手后必做的10件事:从开箱到流畅远程桌面(含VNC卡顿修复)
  • VC6写的九宫格拼图求解器:A*算法动态演示+手动/文件加载
  • Type-I与Type-II错误:产品与数据决策中的统计权衡实战指南
  • 别再傻傻分不清了!给网络新手的VLAN和WLAN超全对比指南(附家庭/公司场景选择建议)