← Gray Password 论文库 · GP-24

24字符外星密码体系(v ↔ U)——从手写防呆到8字节无损压缩的全栈设计

Jingsong Deng & Gemini 联合架构组 · 发表日期:2000-01-24

摘要

16 字符体系与机器同构,但对人类手写并不友好。本文提出 24 字符外星密码体系:剔除所有易混淆字形(0/O、1/I/L、2/Z、5/S、6/G 之一、8/B 之一、V),并引入关键约定 v ↔ U:手写的 v、V、u 在输入时统一归一为 U。我们比较两种 8 字节无损压缩方案:每符号 5 bit 的定长方案可存 12 个符号;24 进制整数打包方案利用 \(24^{13}<2^{64}<24^{14}\) 可存 13 个符号。最后给出打分器、极坐标分级与 Python 全栈示例。

Abstract

We propose a 24-symbol alphabet that removes visually ambiguous glyphs and normalizes handwritten v to U. Fixed 5-bit coding stores 12 symbols in a uint64, while radix-24 packing stores 13, since \(24^{13}<2^{64}<24^{14}\). A scorer, polar grading and Python code are given.

第一部分:字母表表格

\(\Sigma_{24}=\) 3479ABCDEFGHJKMNPQRTUWXY,按 ASCII 顺序赋值:

值符号值符号值符号值符号
036C12J18R
147D13K19T
278E14M20U(← v/V/u)
399F15N21W
4A10G16P22X
5B11H17Q23Y
被剔除原因输入时处理
0, O互相混淆拒绝
1, I, L竖线类字形拒绝
2, Z手写相近拒绝
5, S手写相近拒绝
6(保留 G)6/G/b 相近拒绝
8(保留 B)8/B 相近拒绝
V手写 v 与 U/u 难辨归一为 U

第二部分:v ↔ U 设计说明(手写防呆)

手写时 v 的底部常被写圆,u 的顶部常不闭合,二者在纸面上几乎不可区分。与其让用户猜测,不如在设计上消除歧义:字母表中只保留 U,并定义归一化映射

$$\nu:\ \{v,V,u,U\}\mapsto U,\qquad \nu(c)=\operatorname{upper}(c)\ \text{(其他符号)}$$

于是无论用户把 U 读成 “v” 还是 “u”,输入结果都相同——这就是 “v ↔ U” 的含义:写作两种、存储一种。由于 V 本就不在字母表中,归一化不会造成碰撞,密码空间大小不变。这一“防呆”(poka-yoke)原则同样体现在大小写不敏感上:所有符号统一为大写存储。

第三部分:5-bit 方案与 24 进制打包公式

单符号信息量 \(\log_2 24\approx 4.585\) bit,不是整数,因此存在两种 uint64 打包思路:

3.1 定长 5-bit 方案

$$W_5(s)=\bigvee_{i=0}^{n-1}\big(v(c_i)\ll 5(n-1-i)\big),\qquad 5n\le 64\Rightarrow n_{\max}=12$$

12 × 5 = 60 bit,余 4 bit 可作长度或版本标记;每个符号浪费 \(5-4.585=0.415\) bit(32 个码位只用 24 个)。13 × 5 = 65 bit 已超出 64 位。

3.2 24 进制整数打包

$$W_{24}(s)=\sum_{i=0}^{n-1} v(c_i)\cdot 24^{\,n-1-i}\quad(\text{Horner: } W\leftarrow 24W+v(c_i))$$

精确计算:

$$24^{13}=876\,488\,338\,465\,357\,824\ <\ 2^{64}=18\,446\,744\,073\,709\,551\,616\ <\ 24^{14}=21\,035\,720\,123\,168\,587\,776$$

因此 13 个符号可无损放入一个 uint64,占用 \(13\log_2 24\approx 59.60\) bit;\(2^{64}/24^{13}\approx 21.05\),剩余容量足以再附带一个 0–20 的标签值(如长度或校验)。

方案最大符号数有效熵编码代价
ASCII836.7 bit无
5-bit 定长1255.0 bit移位/掩码
24 进制1359.6 bit乘法/除法

第四部分:打分器

$$S(s)=n\log_2 24-P_{\text{rep}}-P_{\text{seq}}-P_{\text{dict}},\qquad S\in[0,\,13\log_2 24]$$
扣分项判定扣分
重复相邻符号相同3 bit/处
顺序相邻值差 ±1(如 ABC、DCB)2 bit/处
词典含常见词片段(如 MAX、KEY、CAT)6 bit/个
长度不足\(n<10\)分级上限为“白”

第五部分:极坐标分级

将归一化分数映射为半径,将“数字/字母”与“前半表/后半表”的偏好映射为角度:

$$r=\left\lfloor\frac{S}{13\log_2 24}\cdot 255\right\rfloor,\quad \theta=\left\lfloor\frac{\arctan2(y,x)+\pi}{2\pi}\cdot 255\right\rfloor,\quad \text{Level}_{24}=(r\ll 8)\,|\,\theta$$

其中 \(x=\) 数字占比 \(-0.5\),\(y=\) 值 \(\ge 12\) 的符号占比 \(-0.5\)。

\(r\) 区间等级建议
0–95白仅用于公开/低价值场景
96–191灰日常账户
192–255黑主密钥、离线备份

第六部分:Python 代码示例

import math

ALPHABET = "3479ABCDEFGHJKMNPQRTUWXY"
assert len(ALPHABET) == 24
IDX = {c: i for i, c in enumerate(ALPHABET)}
MAX_LEN = 13                      # 24**13 < 2**64 < 24**14
H = math.log2(24)

def normalize(s: str) -> str:
    s = s.strip().upper().replace("V", "U")   # v <-> U 防呆
    bad = [c for c in s if c not in IDX]
    if bad:
        raise ValueError(f"非法符号: {bad}")
    return s

def pack24(s: str) -> int:
    s = normalize(s)
    assert 1 <= len(s) <= MAX_LEN
    w = 0
    for c in s:
        w = w * 24 + IDX[c]
    return w * 21 + len(s)            # 余量存长度:24**13 * 21 < 2**64

def unpack24(w: int) -> str:
    w, n = divmod(w, 21)
    out = []
    for _ in range(n):
        w, d = divmod(w, 24)
        out.append(ALPHABET[d])
    return "".join(reversed(out))

def pack5(s: str) -> int:
    s = normalize(s)
    assert len(s) <= 12
    w = 0
    for c in s:
        w = (w << 5) | IDX[c]
    return (w << 4) | len(s)          # 60 bit 数据 + 4 bit 长度

def score(s: str) -> float:
    s = normalize(s)
    v = [IDX[c] for c in s]
    pen = sum(3 if a == b else 2 if abs(a - b) == 1 else 0
              for a, b in zip(v, v[1:]))
    pen += 6 * sum(w in s for w in ("MAX", "KEY", "CAT"))
    return max(0.0, len(s) * H - pen)

def level24(s: str) -> int:
    s = normalize(s)
    r = int(min(score(s) / (MAX_LEN * H), 1) * 255)
    x = sum(c.isdigit() for c in s) / len(s) - 0.5
    y = sum(IDX[c] >= 12 for c in s) / len(s) - 0.5
    t = int((math.atan2(y, x) + math.pi) / (2 * math.pi) * 255)
    return (r << 8) | t

if __name__ == "__main__":
    p = "k7vMQ3xa9HdTe"                # 手写输入,v 自动变 U
    w = pack24(p)
    assert w < 2**64 and unpack24(w) == normalize(p)
    print(normalize(p), w, round(score(p), 2), hex(level24(p)))

结论

24 字符体系以放弃移位对齐(需乘除法打包)为代价,在单符号熵(4.585 bit,高于 16 字符体系的 4 bit)上反而更优,同时换来人类手写与口述的鲁棒性;v ↔ U 归一化消除了最常见的手写歧义;24 进制打包让 8 字节容纳 13 个符号并附带长度,实现无损、定长、可索引的全栈设计。

参考文献(示意)

  1. Gray Password GP-000101 / GP-000102 / GP-16(本站前文)。
  2. Crockford, D. Base32 Encoding(易混淆字符剔除思想)。
  3. Shingo, S. Poka-yoke 防呆原则。