AI摘要

培根密码是弗朗西斯·培根提出的隐写术,利用两种状态(如大小写)编码字母,每个字母对应5位二进制。文章介绍了其原理、码表、Python实现及CTF中的识别与解题方法。

概述

培根密码(Baconian Cipher / Bacon's cipher)由英国哲学家弗朗西斯·培根(Francis Bacon)于 1605 年在《学术的进展》中提出。与凯撒、仿射等替换密码不同,它的本质是一种隐写术(steganography):秘密不在于字母被替换成了什么,而在于信息被藏进了载体文本的"外在形式"——两种字体、两种大小写、两种符号……只要能区分出两种状态,就能承载信息。

每个明文字母被编码成长度为 5 的 A/B 序列(因为 $2^5 = 32 \ge 26$,5 位足以覆盖 26 个字母)。在 CTF 中,培根密码常以"密文只含两种符号、且有效长度为 5 的倍数"这一特征出现。

编码:  字母 → 5 位 A/B 序列        (2^5 = 32 ≥ 26)
隐写:  A/B → 载体文本的两种"形态"  (小写/大写、正体/斜体、两种符号…)
解码:  载体形态 → A/B 序列 → 每 5 位一组 → 查表还原字母

两套码表:
  26 字母版   每个字母独立(现代 / CTF 常用,等价 5-bit 二进制)
  24 字母版   I=J、U=V 合并(培根原版,1605)

编码原理

隐写而非替换:为什么叫"双字母密码"

培根本人称之为 biliteral cipher(双字母密码)。"双字母"指整套系统只用两个基本符号(记作 ab)就能表达全部字母表——这正是二进制编码思想的雏形,比莱布尼茨系统化二进制(1703)早了近一个世纪。

它与普通替换密码的根本差异,不在算法强度,而在"秘密藏在哪里":

维度替换密码(凯撒 / 仿射)培根密码
秘密藏在内容:字母被换成别的字母形式:字母的字体 / 大小写 / 符号
密文外观一串明显的乱码字母一段"看似正常"的文本
安全依赖敌人不知道密钥敌人没意识到存在密文
学科归属密码学 Cryptography隐写术 Steganography

信息论视角:编码一个英文字母的理论下限是 $\log_2 26 \approx 4.70$ bit,而至少需要 $\lceil 4.70 \rceil = 5$ 位($2^4=16 < 26 \le 32 = 2^5$)才能一一区分,所以培根固定用 5 位。代价是每位只承载 1 bit——每个载体符号仅携带 1 bit,编码 1 个字母需要 5 个载体符号,载体文本因此膨胀 5 倍。这也是隐写容量的核心约束:要藏 $n$ 个字母,载体至少要有 $5n$ 个可区分单元。

培根密码的精妙不在替换本身(替换极其简单),而在于它把 0/1 悄悄"焊"进了正常文本的排版里。理解了这一点,就理解了从零宽字符到图片 LSB 的一整条现代隐写脉络。

编码对照表

26 字母版(现代 / CTF 常用)——本质就是"字母序号的 5 位二进制",a=0b=1

字母序号二进制Bacon 码字母序号二进制Bacon 码
A000000aaaaaN1301101abbab
B100001aaaabO1401110abbba
C200010aaabaP1501111abbbb
D300011aaabbQ1610000baaaa
E400100aabaaR1710001baaab
F500101aababS1810010baaba
G600110aabbaT1910011baabb
H700111aabbbU2010100babaa
I801000abaaaV2110101babab
J901001abaabW2210110babba
K1001010ababaX2310111babbb
L1101011ababbY2411000bbaaa
M1201100abbaaZ2511001bbaab

24 字母版(培根 1605 原版,I/J、U/V 各占同一码)

字母字母字母字母
AaaaaaGaabbaNabbaaTbaaba
BaaaabHaabbbOabbabU/Vbaabb
CaaabaI/JabaaaPabbbaWbabaa
DaaabbKabaabQabbbbXbabab
EaabaaLababaRbaaaaYbabba
FaababMababbSbaaabZbabbb
关键差异:24 版从 I/J 合并处起,后续每个字母的码值都比 26 版"错位"。例如 26 版 K=ababa,24 版 K=abaab。CTF 解题时若一版解出乱码,务必换另一版再试。

编码 / 解码示意

① 明文编码(26 版,把 BACON 转成 A/B 序列):

明文    B       A       C       O       N
序号    1       0       2       14      13
        ↓       ↓       ↓       ↓       ↓
二进制  00001   00000   00010   01110   01101
        ↓       ↓       ↓       ↓       ↓
Bacon   aaaab   aaaaa   aaaba   abbba   abbab

拼接:  aaaab aaaaa aaaba abbba abbab
     = aaaabaaaaaaaabaabbbaabbab      (25 位 = 5×5)

② 隐写解码(大小写载体,小写=a、大写=b)——这是培根密码的"杀手锏",密文伪装成一个普通单词:

载体    c  r  Y  P  T  o  G  r  a  m      ← "crYPToGram" 看着只是大小写怪异
大小写  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
        小 小 大 大 大 小 大 小 小 小
A/B     a  a  b  b  b  a  b  a  a  a
        └──── aabbb ────┘└──── abaaa ────┘
分组:        aabbb            abaaa
查表:          H                I
明文:                  "HI"

Python 完整实现

编码 / 解码类

同时支持 26 版与 24 版;解码时对无法识别的 5 位组返回 ?,便于定位噪声。

class BaconCipher:
    """培根密码编解码器,支持 24 / 26 两种码表"""

    # 24 字母版:I=J、U=V 合并(培根 1605 原版)
    TABLE_24 = {
        'A': 'aaaaa', 'B': 'aaaab', 'C': 'aaaba', 'D': 'aaabb', 'E': 'aabaa',
        'F': 'aabab', 'G': 'aabba', 'H': 'aabbb', 'I': 'abaaa', 'J': 'abaaa',
        'K': 'abaab', 'L': 'ababa', 'M': 'ababb', 'N': 'abbaa', 'O': 'abbab',
        'P': 'abbba', 'Q': 'abbbb', 'R': 'baaaa', 'S': 'baaab', 'T': 'baaba',
        'U': 'baabb', 'V': 'baabb', 'W': 'babaa', 'X': 'babab', 'Y': 'babba',
        'Z': 'babbb',
    }

    def __init__(self, version: int = 26):
        if version not in (24, 26):
            raise ValueError('version 只能是 24 或 26')
        self.version = version
        if version == 26:
            # 26 版 = 字母序号的 5 位二进制,0→a、1→b
            self.enc_table = {
                chr(ord('A') + i): format(i, '05b').replace('0', 'a').replace('1', 'b')
                for i in range(26)
            }
        else:
            self.enc_table = dict(self.TABLE_24)

        # 解码表:24 版中 abaaa/baabb 一码对应 I/J、U/V,取首个字母
        self.dec_table = {}
        for ch, code in self.enc_table.items():
            self.dec_table.setdefault(code, ch)

    def encode(self, text: str) -> str:
        """明文 → a/b 序列(忽略所有非字母字符)"""
        return ''.join(self.enc_table[ch] for ch in text.upper() if ch.isalpha())

    def decode(self, seq: str) -> str:
        """a/b 序列 → 明文(每 5 位一组,多余位丢弃)"""
        seq = seq.lower()
        groups = [seq[i:i + 5] for i in range(0, len(seq) - len(seq) % 5, 5)]
        return ''.join(self.dec_table.get(g, '?') for g in groups)


# 使用示例
b = BaconCipher(version=26)
s = b.encode('BACON')
print(s)            # aaaabaaaaaaaabaabbbaabbab
print(b.decode(s))  # BACON

隐写:把密文藏进大小写

培根的原意是用两种字型(正体/斜体)承载 a/b;在纯文本环境里,最常见的等价载体是大小写

def hide_in_case(secret: str, carrier: str, version: int = 26,
                 a_is_lower: bool = True) -> str:
    """
    把 secret 编码为 a/b 序列后,藏进 carrier 的大小写中。
    a_is_lower=True 表示 'a'→小写、'b'→大写。
    carrier 的字母数必须 ≥ 5 × secret 的有效字母数。
    """
    bits = BaconCipher(version).encode(secret)
    letters = [c for c in carrier if c.isalpha()]
    if len(letters) < len(bits):
        raise ValueError(f'载体字母不足:需 {len(bits)} 个,仅有 {len(letters)} 个')

    out, bi = [], 0
    for ch in carrier:
        if ch.isalpha() and bi < len(bits):
            want_lower = (bits[bi] == 'a') == a_is_lower
            out.append(ch.lower() if want_lower else ch.upper())
            bi += 1
        else:
            out.append(ch)
    return ''.join(out)


def reveal_from_case(stego: str, version: int = 26,
                     a_is_lower: bool = True) -> str:
    """从大小写隐写文本中提取 a/b 序列并解码"""
    bits = ''.join(
        'a' if (ch.islower() == a_is_lower) else 'b'
        for ch in stego if ch.isalpha()
    )
    return BaconCipher(version).decode(bits)


# 使用示例
stego = hide_in_case('HI', 'cryptogram')
print(stego)                   # crYPToGram
print(reveal_from_case(stego)) # HI

自动识别与变种爆破

CTF 里 a/b 未必是字母,可能是 01、大小写、两种标点、长短音……而且 a↔b 可能被反转、码表可能是 24 或 26。把"归一化"与"爆破"拆开处理:

def normalize(cipher: str) -> str:
    """把常见的两态载体统一成 a/b 序列"""
    chars = [c for c in cipher if not c.isspace()]
    kinds = set(chars)

    # 情况 1:全文只由两种符号构成(AB、01、./- 等)
    if len(kinds) == 2:
        lo = sorted(kinds)[0]
        return ''.join('a' if c == lo else 'b' for c in chars)

    # 情况 2:大小写混合的字母 → 小写=a、大写=b(隐写载体)
    letters = [c for c in cipher if c.isalpha()]
    if letters and any(c.islower() for c in letters) and any(c.isupper() for c in letters):
        return ''.join('a' if c.islower() else 'b' for c in letters)

    raise ValueError('无法自动识别两种状态,请手动指定映射')


def auto_decode(cipher: str, hint: str = 'flag') -> list:
    """对归一化序列做 正反映射 × 24/26 版本 的全爆破,命中 hint 者排前"""
    seq = normalize(cipher)
    flip = seq.translate(str.maketrans('ab', 'ba'))

    out = []
    for label, s in (('原映射', seq), ('翻转 a↔b', flip)):
        for ver in (26, 24):
            plain = BaconCipher(ver).decode(s)
            out.append((ver, label, plain))
    out.sort(key=lambda r: hint.lower() in r[2].lower(), reverse=True)
    return out


# 示例:一段大小写隐写,自动还原
for ver, label, plain in auto_decode('crYPToGram', hint='hi'):
    print(f'[{ver}版 / {label}] {plain}')
# [26版 / 原映射] HI      ← 命中,排在最前
# [24版 / 原映射] HI
# [26版 / 翻转 a↔b] YX
# [24版 / 翻转 a↔b] YW

CTF 实战

常见载体形式(题型)

培根密码的"变装"能力极强,识别关键始终是找出那两种状态

载体形式密文特征识别 / 解法
两种字母只含 A、B(或 a、b),长度 5 的倍数直接查表,试 A↔B 反转
二进制 01 串只含 0、1,长度 5 的倍数0=a 1=b(或反)按 5 位分组
大小写隐写一段"正常"英文,大小写异常小写=a、大写=b(或反)提取
字体 / 格式隐写正体 vs 斜体、粗体、下划线两种字型 → a/b(培根本意)
长短 / 高低两种长度的横线、两种音高…归一化为两种符号
套娃编码培根 → 摩斯 / Base 等叠加逐层按字符集判断

识别与解题流程

快速判定:密文是否同时满足——① 只有两种可区分状态;② 有效符号数是 5 的倍数(或去噪后接近)。两条都命中,优先怀疑培根密码。
步骤操作说明
1 识别统计字符 / 大小写 / 字型种类恰好两种状态是强信号
2 归一两种状态 →ab约定其一为 a,另一为 b
3 对齐按 5 位分组长度非 5 倍数 → 排查噪声字符
4 查表每组 a/b → 字母26 版、24 版各试一次
5 试反A↔B 反转再解一遍出题人常故意反转映射
6 验证是否出现 flag 头命中flag{ctf{ 即停

穷举空间极小:2(映射反转)× 2(24/26 版本)= 4 种组合,毫秒级即可全部试完。

与其他编码 / 隐写对比

方案基本符号本质典型识别特征
培根密码2 种状态隐写 + 二进制编码两态、有效长度为 5 的倍数
摩斯电码· 变长编码点划、有分隔、变长
二进制 / ASCII0 1定长编码长度为 8 的倍数
Base6464 字符编码出现+ / =,长度 4 的倍数
栅栏密码26 字母置换字母表不变,仅顺序被打乱
凯撒 / 仿射26 字母替换一串无意义字母
培根密码的"强度"几乎为零——一旦被识别,穷举只有 4 种组合。它的全部价值在于隐蔽而非强度:只要对手没发现文本里藏了东西,再弱的编码也是安全的。这正是隐写术与密码学的分野。
投币支持一下吧
END