☰
RGB通道加解密实战:文本转像素图片的原理与Python实现
2026/10/21 19:59:49 网站建设 项目流程

这个项目很有意思,标题叫《我想出来了一套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 和 uvicorn:

pip install fastapi uvicorn python-multipart

安装完成后,验证 Pillow 是否可用:

python -c "from PIL import Image; print(Image.__version__)"

能打印版本号就说明环境没问题。读取图片 RGB 值时,Pillow 的默认通道顺序是 RGB,OpenCV 的默认通道顺序是 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 加解密", key="demo") img.save("encrypted.png", format="PNG") back = decrypt_rgb_image_to_text(Image.open("encrypted.png"), key="demo") 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 模式则完全由宿主图片决定尺寸,不需要额外设置。

无论哪种模式,保存时都要显式指定 format="PNG"。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", quality=85)

然后用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(description="RGB加解密命令行工具") parser.add_argument("--mode", choices=["encrypt", "decrypt"], required=True) parser.add_argument("--input", required=True, help="输入文件") parser.add_argument("--output", required=True, help="输出文件") parser.add_argument("--key", default="", help="混淆密钥") parser.add_argument("--lsb", action="store_true", help="使用LSB隐写模式") parser.add_argument("--host", help="LSB加密时的宿主图片路径") args = parser.parse_args() if args.mode == "encrypt": text = Path(args.input).read_text(encoding="utf-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, format="PNG") 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, encoding="utf-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_ok=True) for txt_file in in_dir.glob("*.txt"): text = txt_file.read_text(encoding="utf-8") img = encrypt_text_to_rgb_image(text, key) img.save(out_dir / f"{txt_file.stem}.png", format="PNG") print(f"encrypted: {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(f"failed: {png_file.name} -> {e}") continue (Path("decrypted") / f"{png_file.stem}.txt").write_text(text, encoding="utf-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, format="PNG") return Response(content=buf.getvalue(), media_type="image/png")

启动命令为:

uvicorn api_server:app --host 127.0.0.1 --port 8000

这个接口只做示例,没有加入并发控制、鉴权和大小限制。如果要在公网部署,必须加访问限制和请求体大小限制。

8. 性能与资源占用观察

这类脚本的性能瓶颈不是算法复杂度,而是 Python 逐像素循环。纯 Pillow 逐

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询