RGB通道加解密实战:文本转像素图片的原理与Python实现

发布时间:2026/8/31 2:09:52
RGB通道加解密实战:文本转像素图片的原理与Python实现 这个项目很有意思标题叫《我想出来了一套RGB加解密法然后鬼脑发力随便写了段加解密》作者标注了“简介开源”。核心玩法一句话就能讲清楚把一段文本或二进制数据拆成字节映射到图片的 R、G、B 三个颜色通道里生成的图片就是密文需要解密时再按同样的顺序把像素值读出来还原原文。这类方法在图像隐写、教学实验里经常出现。如果你关心的是“能不能在普通电脑上跑、能不能批量加密、能不能封装成接口”这篇文章可以接着往下看。先说结论适合学习图像编码、做算法实验、写图片暗号不适合做严肃的数据加密。生产环境替用户保护数据请选择 AES-256-GCM 这类经过公开审计的标准算法RGB 自研算法只适合玩法和原型验证。下面我会把它拆成原理、实现、测试、批量和排错给你一份能直接照着写的 Python 版本。1. RGB 加解密法核心能力速览能力项说明项目形态基于 RGB 颜色通道的自研加解密脚本开源状态作者标注“简介开源”仓库地址与许可证需以发布页为准实现语言Python 3依赖 Pillow可选 NumPy / FastAPI加密思路数据字节映射到像素 RGB 通道或嵌入图片最低有效位输入输出文本/二进制 → PNG 密文图PNG 密文图 → 原文启动方式命令行 / Python 函数调用 / 可选 HTTP 接口批量任务支持可遍历目录批量生成或解密密文图硬件要求无特殊要求普通 CPU 即可安全性适合学习和原型验证不适合生产加密典型场景教学演示、图像编码练习、图片暗号、开源复现实验从表格可以看出这套方法不依赖 GPU也不依赖任何大型模型普通办公电脑就能跑。它的输入是文本或二进制数据输出是一张 PNG 图片反向操作则从 PNG 恢复原文。理解这些信息后下一步的关键问题是它的原理能做什么不能做什么。2. RGB 加解密法的原理拆解RGB 图像的基础知识很简单。每个像素由 R、G、B 三个通道组成每个通道的取值范围是 0 到 255正好占一个字节。也就是说一个像素可以承载 3 个字节的数据。一张 100x100 的图片理论上有 10000 个像素最多可以塞 30000 字节约 29KB实际还要扣除头部元数据。这个容量对文本类数据已经够用。第一种实现思路是“直写式”。加密时把原始数据按每 3 个字节一组依次填入第 1 个像素的 R、G、B第 2 个像素的 R、G、B以此类推。数据不足 3 字节的部分用 0 补齐。这样生成出来的图片看起来是一张彩色噪点图颜色值完全由数据决定。解密时用同样的顺序把像素里的 R、G、B 读出来拼回原始字节流再按编码还原文本。第二种思路是 LSB 隐写式。LSB 指最低有效位也就是每个通道二进制表示的最后一位。把 1 改成 0或者把 0 改成 1对人眼来说几乎不可见。加密时将数据的每个 bit 依次写入宿主图片每个通道的最低位生成的新图片视觉上仍然接近原图隐蔽性明显好于直写式。代价是容量变小一张图片最多只能存“像素数 x 3 / 8”字节。有一点必须反复强调使用这种方案时密文图一定要保存成 PNG。PNG 是无损格式保存和读取像素值不会变化JPEG 是有损压缩压缩过程会修改颜色值解密时大概率得到乱码。如果只是在屏幕上展示然后截图也可能因为压缩或缩放导致像素值变化需要谨慎。密钥和混淆并不是这套方法的必要条件但可以叠加。一个常见做法是逐字节 XOR用一段密钥字节流和原始数据逐字节异或解密时再做一次 XOR 就能还原。XOR 本身只是混淆操作不是密码学意义上的强加密因为算法没有经过混淆和扩散分析很容易通过统计特征或已知明文攻击还原。3. 适用场景与使用边界这套 RGB 加解密方法最合适的场景是加密原理教学、Python 图像处理练习、课堂实验、图片暗号、小型文本的“伪装传输”原型。它让“数据变图片”的过程非常直观适合给新手讲清楚字节、像素、位运算之间的关系。它的实现量不大核心代码甚至可以控制在 100 行以内做开源演示很合适。但它不适合以下场景保护真实用户的敏感数据、大文件加密、要求高可靠性的数据交换。原因很直接自研加密算法没有经过公开的密码学分析也没有标准实现可以参考。XOR 混淆很容易被破解直写式生成的图片颜色分布不均匀人眼一眼就能看出异常LSB 隐写式虽然隐蔽一些但面对隐写分析工具也不能保证安全性。合规边界同样要注意。如果使用别人的图片作为宿主图或者密文图里包含他人肖像、品牌元素需要确认授权不要用这类方法隐藏违法内容也不要用于投递恶意文件。做开源项目时应该在 README 里明确写出“仅用于学习实验禁止用于非法用途”的声明。无论算法多好玩安全底线不能丢。4. 环境准备与工具选择实现这套方法只需要 Python 3 和 Pillow。建议创建一个独立虚拟环境避免污染系统 Python。可以用 venv 或 conda按个人习惯选择。核心依赖安装命令如下pip install pillow numpy如果后面要跑命令行封装不需要额外安装如果要体验 HTTP 接口可以再装 FastAPI 和 uvicornpip install fastapi uvicorn python-multipart安装完成后验证 Pillow 是否可用python -c from PIL import Image; print(Image.__version__)能打印版本号就说明环境没问题。读取图片 RGB 值时Pillow 的默认通道顺序是 RGBOpenCV 的默认通道顺序是 BGR这个差别最容易让人踩坑。如果之前接触过相机 RAW、YUV 转 RGB 或 Bayer 转 RGB会发现颜色空间的顺序和转换矩阵非常影响结果。本文代码统一使用 Pillow 直接读取 RGB 像素可以避免通道顺序混乱的问题。建议的目录结构如下rgb_cipher/ ├── rgb_cipher.py # 核心加解密函数 ├── cli.py # 命令行入口 ├── texts/ # 待加密文本 ├── encrypted/ # 生成密文图 └── decrypted/ # 解密输出这样划分后批量任务只需要遍历输入目录输出到对应目录管理起来很清晰。5. 实现一个 RGB 加解密脚本下面给出的代码是围绕“RGB 加解密法”写的一套通用可复现示例不是原项目的原始源码。原项目目前只看到标题简介具体仓库细节要以作者发布内容为准。这套示例帮读者先跑通一个最小闭环再根据兴趣扩展。5.1 直写式文本转 RGB 噪点图先实现核心函数。数据格式设计为4 字节数据长度 原始数据 4 字节 CRC32 校验值。这样解密时不需要外部提示原文长度还能通过 CRC 判断密文图是否被修改。# rgb_cipher.py import struct import zlib from PIL import Image def _to_payload(data: bytes) - bytes: # 结构4字节数据长度 原始数据 4字节CRC32 crc zlib.crc32(data) 0xFFFFFFFF return struct.pack(I, len(data)) data struct.pack(I, crc) def _from_payload(payload: bytes) - bytes: length struct.unpack(I, payload[:4])[0] body payload[4:4 length] crc struct.unpack(I, payload[4 length:8 length])[0] if zlib.crc32(body) 0xFFFFFFFF ! crc: raise ValueError(CRC32 校验失败密文图可能被修改或压缩过) return body def xor_data(data: bytes, key: bytes) - bytes: if not key: return data klen len(key) return bytes(b ^ key[i % klen] for i, b in enumerate(data)) def encrypt_text_to_rgb_image(text: str, key: str , width: int 0) - Image.Image: payload _to_payload(text.encode(utf-8)) payload xor_data(payload, key.encode(utf-8)) pixel_count (len(payload) 2) // 3 if width 0: width max(1, int(pixel_count ** 0.5) 1) height (pixel_count width - 1) // width img Image.new(RGB, (width, height), (0, 0, 0)) px img.load() for i in range(pixel_count): r payload[i * 3] if i * 3 len(payload) else 0 g payload[i * 3 1] if i * 3 1 len(payload) else 0 b payload[i * 3 2] if i * 3 2 len(payload) else 0 px[i % width, i // width] (r, g, b) return img def decrypt_rgb_image_to_text(img: Image.Image, key: str ) - str: pixels list(img.getdata()) raw_total bytearray() for r, g, b in pixels: raw_total.append(r) raw_total.append(g) raw_total.append(b) if len(raw_total) 8: raise ValueError(图片尺寸过小无法存放数据头) head xor_data(bytes(raw_total[:8]), key.encode(utf-8)) length struct.unpack(I, head[:4])[0] total_len 4 length 4 if len(raw_total) total_len: raise ValueError(图片容量不足或长度字段异常) payload xor_data(bytes(raw_total[:total_len]), key.encode(utf-8)) return _from_payload(payload).decode(utf-8)调用方式很简单from PIL import Image from rgb_cipher import encrypt_text_to_rgb_image, decrypt_rgb_image_to_text img encrypt_text_to_rgb_image(今天测试 RGB 加解密, keydemo) img.save(encrypted.png, formatPNG) back decrypt_rgb_image_to_text(Image.open(encrypted.png), keydemo) print(back)注意_to_payload和_from_payload里的struct.pack(I, ...)使用的是大端序密钥必须一致否则解出来的长度字段是错的后面的数据也全部错位。5.2 LSB 隐写式藏进宿主图片如果不想让密文图看起来像彩色噪点可以用 LSB 隐写式。这种方式需要一张宿主图片把数据比特写入宿主图片每个通道的最低有效位。视觉上基本看不出区别。def encrypt_text_to_lsb_image(host_img: Image.Image, text: str, key: str ) - Image.Image: payload _to_payload(text.encode(utf-8)) payload xor_data(payload, key.encode(utf-8)) bits .join(f{b:08b} for b in payload) capacity host_img.width * host_img.height * 3 if len(bits) capacity: raise ValueError(原始数据过大超过图片LSB容量) img host_img.convert(RGB) pixels list(img.getdata()) new_values [] bit_idx 0 for r, g, b in pixels: channels [r, g, b] for i in range(3): if bit_idx len(bits): channels[i] (channels[i] 0xFE) | int(bits[bit_idx]) bit_idx 1 new_values.append(tuple(channels)) out Image.new(RGB, img.size) out.putdata(new_values) return out def decrypt_lsb_image_to_text(img: Image.Image, key: str ) - str: img img.convert(RGB) bits for r, g, b in img.getdata(): for ch in (r, g, b): bits str(ch 1) raw_total bytearray() for i in range(0, len(bits) - 7, 8): raw_total.append(int(bits[i:i 8], 2)) if len(raw_total) 8: raise ValueError(图片太小无法读取数据头) head xor_data(bytes(raw_total[:8]), key.encode(utf-8)) length struct.unpack(I, head[:4])[0] total_len 4 length 4 if len(raw_total) total_len: raise ValueError(长度字段异常或图片容量不足) payload xor_data(bytes(raw_total[:total_len]), key.encode(utf-8)) return _from_payload(payload).decode(utf-8)使用 LSB 模式时容量公式是像素数 x 3 / 8 字节。一张 800x600 的图容量是 800 x 600 x 3 / 8 180000 字节约 175KB足够放一篇长文本。生成后同样必须保存为 PNG不要保存为 JPEG。LSB 隐写最怕有损压缩和二次保存因为最低有效位很容易被修改。5.3 保存与输出建议直写式生成的图片分辨率由数据量决定数据越多图片越宽。建议程序里设置一个 width 参数比如固定宽度 128 或 256高度按数据量自动计算这样生成的图片有固定比例更适合展示。LSB 模式则完全由宿主图片决定尺寸不需要额外设置。无论哪种模式保存时都要显式指定 formatPNG。Pillow 根据扩展名有时会自动推断格式但显式指定更稳妥。如果保存成 JPG相当于强制有损压缩解密阶段的 CRC32 大概率会失败并抛出“CRC32 校验失败”的异常。6. 功能测试与效果验证6.1 基础文本加解密闭环测试目的验证最基础的文本加解密是否能跑通。输入一段中英文混合文本加密后保存为 PNG再读取 PNG 解密观察输出是否和原文本一致。python cli.py --mode encrypt --input msg.txt --output msg.png --key demo python cli.py --mode decrypt --input msg.png --output msg.out.txt --key demo判断成功的标准是msg.out.txt和msg.txt内容完全一致。常见失败原因是密钥不一致、保存格式不是 PNG、输入文件编码不是 UTF-8。6.2 中文、英文、emoji 与长文本测试测试目的验证非 ASCII 字符是否正常。明文可以包含中文、英文、数字、换行和 emoji。代码中使用encode(utf-8)理论上都支持。测试时建议准备几个不同长度的文本文件1 个字节的“a”、1 段 1000 字的中文文章、1 个包含 emoji 的测试文本。依次加密解密检查输出是否一致。如果解密出现乱码优先检查文本编码和长度字段是否被破坏。6.3 容量边界测试测试目的找到当前图片尺寸能承载的数据上限。直写式可以不断增大文本直到加密函数报错或生成图片过大。LSB 模式则可以选一张固定图片逐步增加文本长度观察是否抛出“原始数据过大超过图片LSB容量”。这类测试建议用脚本批量跑不要手工复制文本。将不同长度的文本文件放到texts/目录遍历目录加密把失败文件记录下来就能画出容量边界。6.4 JPEG 破坏性测试测试目的验证“有损压缩会破坏密文图”的判断。把生成的 PNG 用 Pillow 重新保存为 JPEG再尝试解密img Image.open(encrypted.png) img img.convert(RGB) img.save(encrypted.jpg, quality85)然后用decrypt_rgb_image_to_text读取encrypted.jpg预期结果是抛出 CRC32 校验失败异常。这个测试能帮读者理解为什么图像隐写必须使用无损格式。6.5 错误密钥测试测试目的确认密钥的作用。用密钥 A 加密再用密钥 B 解密。预期结果是抛出“CRC32 校验失败”或解出的文本乱码。这个特性说明密钥虽然只是 XOR 混淆但至少能挡住“随手打开图片看内容”的情况不能挡住专业分析。7. 命令行封装与批量任务为了让工具更可用可以封装一个简单的命令行入口。下面示例只演示直写式路径LSB 模式增加一个--host参数即可。# cli.py import argparse from pathlib import Path from PIL import Image from rgb_cipher import ( encrypt_text_to_rgb_image, decrypt_rgb_image_to_text, encrypt_text_to_lsb_image, decrypt_lsb_image_to_text, ) def main(): parser argparse.ArgumentParser(descriptionRGB加解密命令行工具) parser.add_argument(--mode, choices[encrypt, decrypt], requiredTrue) parser.add_argument(--input, requiredTrue, help输入文件) parser.add_argument(--output, requiredTrue, help输出文件) parser.add_argument(--key, default, help混淆密钥) parser.add_argument(--lsb, actionstore_true, help使用LSB隐写模式) parser.add_argument(--host, helpLSB加密时的宿主图片路径) args parser.parse_args() if args.mode encrypt: text Path(args.input).read_text(encodingutf-8) if args.lsb: if not args.host: raise SystemExit(LSB模式需要 --host 指定宿主图片) img encrypt_text_to_lsb_image(Image.open(args.host), text, args.key) else: img encrypt_text_to_rgb_image(text, args.key) img.save(args.output, formatPNG) print(f[OK] 密文图片已生成: {args.output}) else: img Image.open(args.input) if args.lsb: text decrypt_lsb_image_to_text(img, args.key) else: text decrypt_rgb_image_to_text(img, args.key) Path(args.output).write_text(text, encodingutf-8) print(f[OK] 原文已写入: {args.output}) if __name__ __main__: main()批量任务可以直接用 Python 脚本遍历目录不需要每次手动指定文件。下面是一个批量加密示例from pathlib import Path from rgb_cipher import encrypt_text_to_rgb_image key batch-demo in_dir Path(texts) out_dir Path(encrypted) out_dir.mkdir(exist_okTrue) for txt_file in in_dir.glob(*.txt): text txt_file.read_text(encodingutf-8) img encrypt_text_to_rgb_image(text, key) img.save(out_dir / f{txt_file.stem}.png, formatPNG) print(fencrypted: {txt_file.name} - {txt_file.stem}.png)批量解密类似遍历encrypted/下的 PNG调用decrypt_rgb_image_to_text把结果写入decrypted/目录。批量任务最需要注意的是异常处理建议这样写for png_file in out_dir.glob(*.png): try: text decrypt_rgb_image_to_text(Image.open(png_file), key) except Exception as e: print(ffailed: {png_file.name} - {e}) continue (Path(decrypted) / f{png_file.stem}.txt).write_text(text, encodingutf-8)这样单张图片失败不会中断整个批次还能把失败原因打印出来方便排查。如果想把能力开放给其他工具不一定要写 Web 服务直接当 Python 函数调用最简单。需要 HTTP 接口时可以用 FastAPI 包一层返回 PNG 图片数据from fastapi import FastAPI from fastapi.responses import Response from pydantic import BaseModel from io import BytesIO from rgb_cipher import encrypt_text_to_rgb_image app FastAPI() class EncryptReq(BaseModel): text: str key: str app.post(/encrypt) def encrypt_api(req: EncryptReq): img encrypt_text_to_rgb_image(req.text, req.key) buf BytesIO() img.save(buf, formatPNG) return Response(contentbuf.getvalue(), media_typeimage/png)启动命令为uvicorn api_server:app --host 127.0.0.1 --port 8000这个接口只做示例没有加入并发控制、鉴权和大小限制。如果要在公网部署必须加访问限制和请求体大小限制。8. 性能与资源占用观察这类脚本的性能瓶颈不是算法复杂度而是 Python 逐像素循环。纯 Pillow 逐