通常情况下,PGP 公钥只是一堆无聊的 Base64 字符
但是其实可以通过Tag 11来添加自定义欢迎语等,甚至通过ascii来清除报错信息
先看效果:

一、 OpenPGP 密钥格式详解
PGP(Pretty Good Privacy)公钥遵循 RFC 4880 标准。它并不是一个单一的文件,而是一个数据包序列(Packet Stream)。
1. 数据包结构
每个 PGP 文件都由多个 Packet 组成,每个 Packet 包含:
- Tag (包标签):标识包的类型(如公钥、用户ID、签名等)。
- Length (长度):标识后续数据的字节数。
- Body (包主体):存储实际的二进制数据。
2. 常见的 Tag 类型
- Tag 6 (Public-Key Packet):存储核心数学参数(如 RSA 的 $n, e$ 或 ECC 的曲线点)。
- Tag 13 (User ID Packet):存储姓名和邮箱。
- Tag 11 (Literal Data Packet):关键点。这个包设计用于存储“原始数据”。但是当其放置到第一位, GPG 解析时,它就会直接输出其中的内容。
二、 如何添加自定义欢迎字符
要在公钥中显示欢迎字符,其核心逻辑是在公钥包(Tag 6)之前插入一个字面数据包(Tag 11)。
1. 构造 ANSI 艺术
由于现代终端大多支持 ANSI 转义码(Escape Codes),我们可以通过特定序列控制终端
例如:
\x1b[36m:切换前景色为青色。
\x1b[1A:光标上移(用于覆盖 GPG 原有的导入提示,实现动画效果)。
2. 封装过程
- 准备二进制 Payload:将你的文字或 ASCII 图形转换为字节流。
- 构建 Packet 头部:
- OpenPGP 的旧格式头部计算公式为:
0x80 | (Tag << 2) | LengthType。
- 对于 Tag 11,头部通常为
0xA1 或 0xA2。
- 合并数据流:将构造好的 Tag 11 包放在整个公钥序列的最顶端。
三、 关键的校验机制:CRC24
当你修改了公钥的二进制内容后,直接保存会导致 GPG 报错“Checksum error”。这是因为 PGP 使用了 CRC24 (循环冗余校验)。
1. 校验的作用
在 PGP 的 Base64 编码(即 Armor 格式)底部,你会看到类似 =A3fG 的字符串。这就是前面所有二进制数据的 CRC24 值。它用于确保公钥在传输过程中没有被篡改或损坏。
2. CRC24 算法参数
PGP 定义的 CRC24 具有特定的参数:
- 多项式 (Generator):$0x1864CFB$
- 初始值 (Init):$0xB704CE$
3. 计算流程
- 计算自定义内容和原始密钥的所有二进制字节。
- 应用 CRC24 算法得出 24 位结果。
- 将该结果转换为 3 个字节,并进行 Base64 编码。
- 将编码后的字符串放在公钥末尾的
= 后面。
实现:
注:不知道咋描述,上文ai含量90%,但是下文人类含量90%(因为蠢ai浪费我半天时间没写明白)
import base64
import re
INPUT_FILE = "key.asc" # 你的原公钥
OUTPUT_ASC = "new_key.asc" # 将会生成的漂亮密钥
# 不知道从哪里偷得crc校验
def crc24(data):
crc = 0xB704CE
for byte in data:
crc ^= byte << 16
for _ in range(8):
crc <<= 1
if crc & 0x1000000:
crc ^= 0x1864CFB
return crc & 0xFFFFFF
with open(INPUT_FILE, "r") as f:
asc_content = f.read()
b64_match = re.search(r'\n\n([A-Za-z0-9+/=\s]+)\n=', asc_content)
raw_key = base64.b64decode("".join(b64_match.group(1).split())) # 提取密钥中的base64
# ——————————————payload 可以瞎改——————————————
ESC = b"\x1b"
clear_screen = (ESC + b"[1A" + ESC + b"[2K") * 14 # 为什么呢,因为如果直接调用gpg的话会出现gpg: 警告:没有提供命令。正在尝试猜测您的意图...
# 色(sai)
BROWN_BG = ESC + b"[43m"
TEXT_CYAN = ESC + b"[36m"
TEXT_GREEN = ESC + b"[32m"
TEXT_GOLD = ESC + b"[93m"
RESET = ESC + b"[0m"
payload = (
clear_screen + b"\r" +
BROWN_BG + b" ( ) " + RESET + b" " + TEXT_CYAN + b"eat" + b"\n" +
BROWN_BG + b" ( ) " + RESET + b" " + TEXT_GREEN + b"it!" + b"\n" +
BROWN_BG + b"(________)" + RESET + b" " + TEXT_GOLD + b"Oisi and Fresh!" + b"\n" +
RESET + b"run `!! --import`" + b"\n" + # 为什么是`!! --import`?开个终端试试就知道了 !!会变为你上一个执行的命令,--import就是导入密钥(我也是才知道)
b"\n"
)
# ——————————————————打包———————————————————
def build_packet(tag, data):
l = len(data)
header = bytes([0x80 | (tag << 2) | 1])
return header + l.to_bytes(2, 'big') + data
# 将我们的答辩放在最前面
comment_packet = build_packet(11, payload)
modified_raw = comment_packet + raw_key
# —————————————————生成—————————————————————
new_b64 = base64.b64encode(modified_raw).decode()
lines = [new_b64[i:i+64] for i in range(0, len(new_b64), 64)]
# 计算新的 CRC24
checksum = base64.b64encode(crc24(modified_raw).to_bytes(3, 'big')).decode()
final_asc = [ # 不想注释了后面都能看懂吧
"-----BEGIN PGP PUBLIC KEY BLOCK-----",
"",
]
final_asc.extend(lines)
final_asc.append("=" + checksum)
final_asc.append("-----END PGP PUBLIC KEY BLOCK-----")
with open(OUTPUT_ASC, "w") as f:
f.write("\n".join(final_asc))
print(f"finish:{OUTPUT_ASC}")