古典最后辉煌 — WriteUp
题目信息
分类:Crypto / 古典密码
靶机:
http://xxxFlag:
flag{540cdedc43cdc4e19be016e2e8ba5f70}
解题思路
第一步:观察页面,识别密码类型
打开靶机页面,主体是10 段用大写字母书写的密文,仔细观察会发现一个关键特征:
所有密文只由A、D、F、G、V、X这 6 个字母组成。
这是密码学史上非常著名的指纹 —— 一战德军使用的ADFGVX 密码(结合题目名"古典最后辉煌",指的就是它号称"无法破译的最后辉煌")。
ADFGVX 是一种两步加密的古典密码:
替换:明文字符在 6×6 方阵中查表,替换为行标+列标的字母对(如
A→AA);列转置:把替换后的中间密文按密钥长度分行写入网格,再按密钥字母序读列输出。
解密就是逆序操作:先反列转置,再反查方阵。
第二步:找到密钥
题目提示"页面就有小 key"。页面背景文案讲述 Bletchley Park 的破译历史,其中反复出现核心词汇BOMBE(图灵设计的破译机"Bombe")—— 这就是列转置的密钥。
方阵采用最常见的标准布局:A-Z+0-9按行填入 6×6 网格。
第三步:编写解密脚本
import re
# 抓取页面并提取 10 段密文
html = open('page.html', encoding='utf-8').read()
cts = [re.sub(r'\s+', '', c)
for c in re.findall(r'data-text="([^"]+)"', html)]
ALPHA = 'ADFGVX'
idx = {a: i for i, a in enumerate(ALPHA)}
def col_transpose_decrypt(ct, key):
"""反列转置:按密钥字母序填列,再按行读出"""
n, L = len(key), len(ct)
rows, rem = L // n, L % n
order = sorted(range(n), key=lambda i: (key[i], i)) # 密钥字母序
cl = [rows + (1 if i < rem else 0) for i in range(n)] # 各列长度
cols, pos = {}, 0
for i in order:
cols[i] = ct[pos:pos + cl[i]]
pos += cl[i]
return ''.join(cols[i][r]
for r in range(rows + (1 if rem else 0))
for i in range(n) if r < len(cols[i]))
# 标准 6×6 方阵
sq = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
for k, c in enumerate(cts, 1):
inter = col_transpose_decrypt(c, 'BOMBE') # ① 反列转置
pairs = [inter[i:i+2] for i in range(0, len(inter), 2)]
h = ''.join(sq[idx[p[0]] * 6 + idx[p[1]]] for p in pairs) # ② 反查方阵
try:
print(k, bytes.fromhex(h).decode('utf-8')) # ③ hex -> UTF-8
except Exception:
print(k, h)第四步:分析解密结果
运行后 10 段密文依次解开:
第 1~7、9、10 段:二战密码战的中文战报(1940 年破译 PURPLE、1939 年 Bletchley Park 参战、1944 年 Colossus 破译 Tunny、图灵与丘吉尔、Hut 8、SIGABA 等),属于背景故事和干扰信息;
第 8 段:hex 解码后直接得到 flag 格式的字符串。
第 8 段共 76 个字母对 = 38 字节,与flag{+ 32 位十六进制 +}的长度完全吻合。
Flag
flag{540cdedc43cdc4e19be016e2e8ba5f70}总结与坑点
识别特征是关键:密文字母表只有
ADFGVX六个字母时,直接锁定 ADFGVX 密码,不要浪费时间尝试凯撒、维吉尼亚等其他古典密码;两步缺一不可:ADFGVX = 列转置 + 方阵替换。只做方阵替换会得到一串"看起来像 hex 但 decode 是乱码"的假阳性;
列转置细节:按密钥字母序读列时,相同字母按从左到右的稳定顺序处理;密文不能整除时,前
余数列多一个字符;噪音处理:个别字母对可能落在方阵边缘产生非法字符,核对
flag{...}的已知前缀即可校正,不影响最终结果。