news 2026/8/31 2:08:55

自制RGB图像加解密:用图片像素做密钥的字节变换实验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自制RGB图像加解密:用图片像素做密钥的字节变换实验

最近整理了一个挺有意思的小项目:自制的 RGB 加解密法。思路不复杂,就是拿一张图片的 RGB 像素值,去对文本做加解密。你输入一段文字,程序读图的像素,把像素展开成字节流,再和文本字节做异或;解密时,用同一张图的像素还原。标题里的“鬼脑发力”挺贴切,因为它不算正经密码算法,更像一个把文本和图片强行缝合在一起的实验玩具。

先给结论:这玩意能跑,能加解密,过程也挺好玩,但绝不能用来保护真实数据。它真正适合的场景是学习、练手、做课堂作业,或者作为一个有话题性的开源小项目。下面按我实际跑通的过程,把思路、代码、坑和开源发布建议都拆开讲。

1. 这套 RGB 加解密到底在做什么,适合谁玩

1.1 名字听起来很玄,本质是字节流变换

“RGB 加解密”这个叫法,容易让人误以为是一种新的密码学标准。实际拆开看,就是把三样东西串起来:

  • 文本:要加密的内容,先编码成字节。
  • 图片:每个像素都有 R、G、B 三个分量,取值都是 0 到 255。
  • 字节流:把像素分量拉直,就得到一串数字,本质上就是字节。

加密时,程序把文本字节和图片像素字节做某种可逆运算,得到密文;解密时,再拿同一张图片的像素字节做逆运算,把密文变回原文。整个过程不神秘,核心就是字节和字节之间的变换。

1.2 适合人群和学习价值

这个项目适合三类人:

  1. Python 初学者:可以从头到尾看到文本、字节、进制、异或、图片像素这些概念怎么在实际代码里串起来。
  2. 图像处理入门者:想搞清楚 PIL、OpenCV 怎么读取像素,RGB 和 BGR 到底有什么区别。
  3. 想给开源仓库攒一个有趣项目的人:这个主题有话题性,写 README 时比较好讲清楚。

不推荐把它当安全工具用。后面我会专门解释原因。

2. 核心思路拆解:文本、字节、RGB 三者怎么串起来

2.1 从文本到字节

Python 里一行代码就能把字符串转成字节:

plain = "你好,世界" data = plain.encode("utf-8") print(data)

加密之后再想还原,就调用decode("utf-8")。这里用 UTF-8 而不是 GBK,是为了让中文在不同系统之间保持一致,也避免表情符号这类字符编码失败。

2.2 从 RGB 到字节

PIL 读一张图,每个像素是 (R, G, B) 三元组:

from PIL import Image img = Image.open("key.png").convert("RGB") pixels = list(img.getdata()) print(pixels[:3])

三元组展开后就是一长串 0 到 255 的整数,可以直接当成字节来用。

2.3 两种方向:RGB 当钥匙,还是 RGB 当密文

我整理思路时发现“RGB 加解密”可以往两个方向做,都有意思:

方向思路密文形态
RGB 当钥匙用图片像素字节生成密钥流,和文本字节做异或一段 HEX 字符串
RGB 当密文把文本加解密后的结果按三个字节一组写进像素一张长得像噪点的 PNG 图片

两种方向代码都不长,下面分别给出一个可运行的示例版本。

3. 本地环境准备:Python 读取图片 RGB 值的正确姿势

3.1 安装依赖

本地环境我用的是 Python 3.10,核心依赖只需要 Pillow。顺手装上 NumPy 和 OpenCV,方便对比不同读取方式。

pip install pillow numpy opencv-python

如果只是跑下面的示例,执行pip install pillow就够了。

3.2 三种读取 RGB 的方式

方式一,PIL:

from PIL import Image img = Image.open("key.png").convert("RGB") pixels = list(img.getdata())

方式二,NumPy:

import numpy as np arr = np.array(Image.open("key.png").convert("RGB")) print(arr.shape) # (height, width, 3)

方式三,OpenCV:

import cv2 img = cv2.imread("key.png") rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

3.3 RGB 和 BGR 的坑

OpenCV 默认读出来是 BGR,不是 RGB。如果直接把img[:, :, 0]当 R 通道,通道顺序就反了。解决办法是cv2.cvtColor(img, cv2.COLOR_BGR2RGB),或者用img[:, :, ::-1]把通道倒过来。

还有一个更隐蔽的坑:很多图片查看器显示正常,但像素实际做过色彩管理。做实验时最好统一用 PNG,不要用 JPEG。JPEG 是有损压缩,同一张图保存两次,像素值就可能变,密钥就变了。我用的是自己生成的 256x256 纯色测试图,先把格式问题排除掉。

4. 第一版实现:把图片 RGB 当密钥流,对文本做异或

4.1 生成密钥流的思路

最简单的做法是把图片像素字节拉长重复,和文本字节逐位异或。但这样做有一个明显弱点:如果图片是纯色块或大面积渐变,密钥流会有规律,密文会出现可观察的重复模式。

我用的思路是:先把图像素字节做 SHA-256,得到 32 字节种子,再用种子加计数器不断做哈希,展开成足够长的密钥流。这个思路接近“用哈希构造流密码”,但只是为了好玩,不代表它安全。

import hashlib def derive_key_stream(pixel_bytes: bytes, data_len: int) -> bytes: seed = hashlib.sha256(pixel_bytes).digest() stream = b"" counter = 0 while len(stream) < data_len: stream += hashlib.sha256(seed + counter.to_bytes(8, "big")).digest() counter += 1 return stream[:data_len]

counter.to_bytes(8, "big")的作用是让每次哈希输入都不同,避免生成重复块。

4.2 加密和解密函数

from PIL import Image def load_pixel_bytes(image_path: str, size=(256, 256)) -> bytes: img = Image.open(image_path).convert("RGB").resize(size) pixels = list(img.getdata()) return bytes(v for pixel in pixels for v in pixel) def xor_encrypt(text: str, image_path: str) -> str: data = text.encode("utf-8") stream = derive_key_stream(load_pixel_bytes(image_path), len(data)) cipher = bytes([a ^ b for a, b in zip(data, stream)]) return cipher.hex() def xor_decrypt(hex_cipher: str, image_path: str) -> str: cipher = bytes.fromhex(hex_cipher) stream = derive_key_stream(load_pixel_bytes(image_path), len(cipher)) data = bytes([a ^ b for a, b in zip(cipher, stream)]) return data.decode("utf-8")

加密和解密都依赖同一个image_path。解密必须使用同一张图,换成任何其他图片,密钥流就不同,解出来就是乱码。

4.3 用一张测试图验证

key_image = "key.png" origin = "这是一段用来测试 RGB 加解密的中文文本。" cipher_text = xor_encrypt(origin, key_image) print(cipher_text) plain_text = xor_decrypt(cipher_text, key_image) print(plain_text)

如果输出末尾能看到原始中文,说明流程通了。我建议先跑这一版,再去看下一版,因为这一版已经把“图片像素”和“文本字节”的关系讲清楚了。

5. 第二版实现:把密文写进 RGB 像素,生成一张噪点图

5.1 文本转 RGB 图片

第二版更视觉化:把文本字节按三个一组塞进像素的 R、G、B 通道,生成一张图片。为了还原时不丢尾部空字节,我在开头用 4 个字节记录原始数据长度。

import struct from PIL import Image def text_to_rgb_image(text: str, output_path: str, width=128): data = text.encode("utf-8") header = struct.pack(">I", len(data)) payload = header + data if len(payload) % 3 != 0: payload += b"\x00" * (3 - len(payload) % 3) pixels = [tuple(payload[i:i+3]) for i in range(0, len(payload), 3)] height = (len(pixels) + width - 1) // width pixels += [(0, 0, 0)] * (width * height - len(pixels)) img = Image.new("RGB", (width, height)) img.putdata(pixels) img.save(output_path) print(f"output: {output_path}, size: {width}x{height}")

struct.pack(">I", len(data))用 4 字节大端整数存长度。这一步很重要,否则尾部填充的\x00会被当成正常数据,导致中文解码失败。

5.2 从 RGB 图片还原文本

def rgb_image_to_text(image_path: str) -> str: img = Image.open(image_path).convert("RGB") pixels = list(img.getdata()) payload = b"".join(bytes(pixel) for pixel in pixels) data_len = struct.unpack(">I", payload[:4])[0] data = payload[4:4 + data_len] return data.decode("utf-8")

逻辑是先读长度,再按长度截取,剩下的填充字节直接丢弃。

5.3 验证方法和边界

origin = "Hello, RGB Cipher! 这是一张会说话的图片。" text_to_rgb_image(origin, "cipher.png", width=100) restored = rgb_image_to_text("cipher.png") print(restored == origin) # True

生成出来的 cipher.png 看起来就是一堆彩色噪点,因为任意字节映射到 RGB 后基本没有视觉规律。这个效果本身也适合当演示素材。

边界情况有三点:

  • 空文本:长度是 0,生成一张只有头部 4 个字节的图,可以正常运行。
  • 非 ASCII 文本:只要编码用 UTF-8,中文、日文、表情符号都能放。
  • 超长文本:图片会变高,建议控制文本长度在几 KB 级别,或者适当加大 width。

6. 性能和安全性:和 AES、3DES 比,差距在哪里

6.1 速度差距很大,而且不在一个量级

有人会拿这个项目和“3des 与 aes 加解密速度对比”放一起讨论。实际上,AES 在主流 CPU 上有硬件加速,速度非常快;3DES 属于老算法,虽然不如 AES,但也比纯 Python 循环快很多。

而这个 RGB 方案,每次加解密要经历:

  1. 打开图片文件;
  2. 转 RGB、缩放、展开像素;
  3. 做 SHA-256 展开密钥流;
  4. 再逐字节异或。

其中图片 I/O 和哈希展开都是 Python 层耗时。我拿一张 256x256 的 PNG 当密钥,加密几千字节文本,耗时在毫秒级到几十毫秒之间。单次看不算慢,但放到批量任务里,几百次调用叠加起来就很明显。

一句话:这个项目适合教学和实验,不适合做高性能数据通道。

6.2 为什么不建议用于真实安全场景

必须把话说清楚:这是自制算法,不是密码学意义上的安全方案。

问题至少有四层:

  1. 算法没有公开审计。我写的时候优先保证“能跑通、能还原”,没有考虑侧信道、时序攻击、选择明文攻击这些模型。
  2. 密钥本质是一张图片。图片可能被复制,也可能带有可预测内容,密钥熵不稳定。
  3. 没有完整性校验。密文被篡改,解密出来就是乱码,程序不会主动发现“数据被改了”。
  4. 没有标准填充和认证机制。工程上要求的安全边界,这个项目都没有。

真实保护数据,建议用成熟库:

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

或者直接用 GPG、age 这类工具。自制算法最大的价值是帮你理解加解密的基本原理,而不是替代标准算法。

6.3 哪些场景可以放心玩

  • 课堂作业和课程设计,把原理讲清楚。
  • 给朋友做一个“用照片解锁文本”的小谜题。
  • 用文本生成艺术噪点图,再还原文字,作为编程分享的演示。
  • 作为学习开源项目维护的载体,练习 README、许可证、issue 管理。

7. 开源发布:仓库结构、许可证和 README 建议

7.1 仓库结构

既然是开源项目,就不要只丢一个 .py 文件。我建议仓库结构这样放:

rgb-cipher/ ├── README.md ├── LICENSE ├── requirements.txt ├── rgb_cipher.py ├── examples/ │ ├── key.png │ ├── demo_encode.py │ └── demo_decode.py ├── tests/ │ └── test_rgb_cipher.py └── .gitignore

.gitignore至少要排除__pycache__/.venv/.idea/.vscode/。不然别人克隆下来,第一眼看到一堆缓存文件,观感很差。

7.2 许可证怎么选

仓库里有代码,就要有 LICENSE 文件。常见选择:

许可证特点适合场景
MIT允许商用、修改、再发布,只要保留版权声明玩具项目、教学项目
Apache-2.0类似 MIT,额外包含专利授权条款想考虑专利风险的开发者
GPL-3.0要求衍生作品同样开源希望别人改完也必须开源

这类 RGB 加解密项目,我建议直接选 MIT。最省事,别人拿去改、拿去演示,都不需要专门来问你。

7.3 README 怎么写

README 至少要回答四个问题:

  • 这个项目能干什么:用文本生成 RGB 图片,或用图片当密钥做异或加解密。
  • 怎么安装:给出pip install -r requirements.txt
  • 怎么运行:给出完整命令行示例。
  • 有什么限制:不是安全算法,不要用于真实数据。

可以放一张生成出来的噪点图当截图,视觉上很直观。

8. 常见报错与排查顺序

8.1 图片打不开、像素读不全

先看路径。路径有中文、空格,或者相对路径和运行目录不一致,都容易报FileNotFoundError。其次看图片格式,统一换成 PNG 再试。最后看图片模式,Image.open()之后一定要.convert("RGB"),否则灰度图或 RGBA 图的通道数不一样,展开成字节时会出问题。

8.2 解密出来乱码

乱码是这类项目最高频的问题,按这个顺序排查:

  1. 密钥流不一致:换过图片、图片被缩放、图片被重新保存,都会导致像素值变化。
  2. 长度不一致:密文 HEX 被截断或拼接错误。
  3. 编码不一致:加密时用 UTF-8,解密时也要用 UTF-8。
  4. 填充处理错误:第二版里如果没读头部长度,直接用rstrip(b"\x00"),文本末尾原本有\x00时会被误删。

我一般会先打印密文长度和密钥流长度,再打印解密后的字节。如果能看到b'\xe4\xbd\xa0'这样中文 UTF-8 前缀,说明字节流对上了,问题出在最后的 decode 环节。

8.3 和图片处理相关的其他坑

如果做图像处理时遇到“RGB 转 YCbCr 为负数”的问题,先别急着当异常处理。用 BT.601 或 BT.709 的矩阵做 RGB 转 YCbCr 时,Cb、Cr 在部分色域下出现负数是正常的。显示时一般要位移或裁剪到 0 到 255,但做计算时不要随便把负数裁掉,否则颜色会偏。

放到这个项目里的提示是:凡是基于像素做加解密,像素值必须严格保持 0 到 255 的原始字节范围,不要做任何裁剪、归一化、色彩空间转换。任何一步改变像素值,都等于改变密钥。

8.4 内存和性能问题

如果把一张几千万像素的大图直接list(img.getdata()),内存会涨得很快。建议先resize((256, 256))再用。如果文本很长,密钥流展开时间也会明显上升,可以先跑小段文本确认逻辑正确,再考虑扩展。


这轮玩下来的感受是:所谓“RGB 加解密”,本质就是文本字节和图片字节互相转换的实验。它最大的价值不是发明了新算法,而是把编码、字节、异或、像素、文件 I/O 这些零散知识点串在了一起。如果想继续深入,可以把两个方向合并:先用图片像素生成密钥流,再把密文写进另一张图片,玩法会更有意思。唯一要记住的是,真正要保护数据时,请回到 AES-GCM 这类标准方案,而不是自己拍脑袋拼一个字节变换。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 2:08:50

LPL骑士之路赛制解析:NIP击败WBG后IG争第一的博弈逻辑

最近 LPL 夏季赛的骑士之路阶段打得非常胶着&#xff0c;NIP 和 WBG 这场 BO5 结束之后&#xff0c;网上关于“谁赢对 IG 更有利”“骑士之路第一和第二到底差在哪”的讨论一下子多了起来。知名教练朱开在直播里也聊了他的看法&#xff1a;他的角度其实希望 WBG 能赢&#xff0…

作者头像 李华
网站建设 2026/8/31 2:07:36

Barret Zoph加盟Google背后:强化学习主导语言模型后训练

Barret Zoph 离开 OpenAI、加入 Google 出任研究副总裁&#xff0c;这条消息在 AI 社区很快引发了讨论。多数讨论集中在人才流动、公司竞争和薪酬待遇上&#xff0c;但技术从业者可以从中读到的&#xff0c;其实是一个更明确的研究方向信号&#xff1a;强化学习在语言模型后训练…

作者头像 李华
网站建设 2026/8/31 2:06:04

Matlab多分类混淆矩阵绘制全攻略:从原理到论文级出图

简介&#xff1a;本资源面向计算机、电子信息工程及数学等专业的本科生&#xff0c;聚焦多分类问题中混淆矩阵的可视化实现&#xff0c;适用于课程设计、期末大作业或毕业设计阶段的算法验证与结果展示需求。压缩包共17个文件&#xff08;10个MATLAB脚本文件用于核心绘图与指标…

作者头像 李华
网站建设 2026/8/31 2:06:02

直播录像工程实践:从RTMP拉流到自动归档的完整方案

前几天整理直播内容归档时&#xff0c;我拿到一个看起来非常规范的文件名&#xff1a; 李一恩直播录像-2026-08-13-晚上场.mp4 。文件名本身没什么技术含量&#xff0c;但它背后其实藏着一整套值得较真的工程问题&#xff1a;这场直播是怎么录下来的&#xff1f;录制过程中断…

作者头像 李华
网站建设 2026/8/31 2:05:18

舞台直拍全流程指南:从设备准备、现场收音到后期对轨

看多了官方多机位直播&#xff0c;很多人会对“舞台直拍”产生一个误解&#xff1a;认为它只是用手机随手一录&#xff0c;画质、声音、剪辑都随缘。等到自己真正站进 Livehouse&#xff0c;打开相机准备录一整场演出时&#xff0c;才发现现场灯光乱闪、低频轰头、对焦拉风箱、…

作者头像 李华
网站建设 2026/8/31 2:04:46

STM32驱动蜂鸣器实战:GPIO、三极管与PWM完整解析

之前帮一个刚接触 STM32 的朋友调试板子&#xff0c;遇到一个非常典型的问题&#xff1a;他在 PA0 引脚上直接接了一个有源蜂鸣器&#xff0c;代码里调用 HAL_GPIO_WritePin 把引脚拉高&#xff0c;结果蜂鸣器纹丝不动&#xff0c;反而是 STM32 芯片外壳有点发热。后来用万用…

作者头像 李华