CTF密码学实战:Python实现埃特巴什码的加密与解密
1. 从零认识埃特巴什码:CTF里最“懒”的古典密码
大家好,我是老陈,一个在CTF密码学和AI领域摸爬滚打了十多年的老兵。今天咱们来聊一个在CTF密码学题目里出场率极高,但原理又简单到让人会心一笑的古典密码——埃特巴什码。如果你刚接触CTF,或者对密码学有点兴趣但又被各种复杂算法吓到,那埃特巴什码绝对是你的完美起点。
埃特巴什码到底是什么? 用一句话概括:它就是字母表的“镜像对称”替换。想象一下,你把26个英文字母从A到Z写在一张纸条上,然后捏住纸条的两端,把它对折。A和Z贴在一起,B和Y贴在一起,C和X贴在一起……以此类推。加密和解密,就是按照这个“谁和谁是一对”的规则,把字母换成它的“镜像伴侣”。
我当年第一次在CTF比赛里遇到它,题目给了一段像乱码的字符串:"gsv jfrxp yildm ulc qfnkh levi gsv ozab wlt"。当时我试了凯撒密码、栅栏密码,折腾了半天没头绪。后来灵光一闪,把每个字母“倒过来”映射,瞬间就得到了那句著名的测试句:"the quick brown fox jumps over the lazy dog"。那种“原来如此”的顿悟感,至今记忆犹新。埃特巴什码的魅力就在于此,它不靠复杂的数学运算,纯粹是一种巧妙的对称思维。
为什么说它“懒”呢?因为它的加密和解密过程完全一样。对一段文本用埃特巴什规则处理一次,得到密文;再用同样的规则处理一次密文,就变回明文。这种特性在密码学里称为“自反性”或“对合性”。你不需要记两套规则,一套就够了。在CTF实战中,这个特性非常有用。当你怀疑一段密文可能是埃特巴什码时,根本不用纠结这是加密还是解密,直接拿同一段代码跑一遍,如果是,立马现原形。
那么,埃特巴什码在CTF里通常怎么出现呢?根据我这十年的“踩坑”经验,主要有三种姿势:
- 单独出现:作为一道独立的入门题,直接给一段密文让你还原。这种题就是送分,考察你是否知道这个密码的存在。
- 组合出现:作为“套娃”的一部分。比如题目先给一段Base64编码,解码后得到一串看起来还是乱码的字母,这时候再尝试埃特巴什码,才能拿到真正的Flag。这是CTF出题人最喜欢的套路之一。
- 变形或部分应用:只对字符串中的字母部分进行埃特巴什替换,而数字、符号、花括号
{}(Flag的常见格式)保持不变。这需要你在写代码时做好字符类型的判断。
接下来,我们就用Python这把“瑞士军刀”,亲手实现这个有趣的密码,并看看如何在CTF实战中运用它。
2. 手把手:用Python实现埃特巴什码的核心算法
理解了原理,动手实现就是水到渠成。我们用Python来写,不仅因为它是CTF脚本小子的最爱,更因为它语法简洁,能让我们专注于逻辑本身。我会给出两种不同思路的实现,从最直观的到最高效的,并解释其中的“坑”和技巧。
2.1 方法一:字典映射法(最直观)
这是最符合人类思维的方式:我先告诉电脑,A对应Z,B对应Y……建立一个“密码本”(字典),然后查表替换。
def atbash_cipher_dict(text):
"""
使用字典映射实现埃特巴什码加密/解密。
参数 text: 待处理的字符串
返回: 处理后的字符串
"""
# 构建映射字典
# 大写字母映射
upper_map = {}
# 小写字母映射
lower_map = {}
# 利用循环和ASCII码构建映射关系
for i in range(26):
plain_upper = chr(ord('A') + i) # 从'A'开始的大写字母
cipher_upper = chr(ord('Z') - i) # 对应的镜像大写字母
upper_map[plain_upper] = cipher_upper
plain_lower = chr(ord('a') + i) # 从'a'开始的小写字母
cipher_lower = chr(ord('z') - i) # 对应的镜像小写字母
lower_map[plain_lower] = cipher_lower
result = []
for char in text:
if char in upper_map:
# 如果是大写字母,查大写映射表
result.append(upper_map[char])
elif char in lower_map:
# 如果是小写字母,查小写映射表
result.append(lower_map[char])
else:
# 非字母字符(如数字、空格、标点、CTF flag中的花括号{})原样保留
result.append(char)
return ''.join(result)
# 测试一下
test_text = "Hello CTF{Atbash_Is_Fun}"
encrypted = atbash_cipher_dict(test_text)
print(f"原文: {test_text}")
print(f"密文: {encrypted}")
print(f"再次处理密文(应还原): {atbash_cipher_dict(encrypted)}")
运行上面的代码,你会看到输出:
原文: Hello CTF{Atbash_Is_Fun}
密文: Svool XGU{Zgyzhs_Rh_Ufm}
再次处理密文(应还原): Hello CTF{Atbash_Is_Fun}
代码解读与踩坑点:
- 大小写分离:这是第一个坑。‘A’的镜像是‘Z’,但‘a’的镜像是‘z’。我们必须区分大小写,建立两个映射字典,否则会得到错误结果。
- 非字母保留:这是CTF实战中的关键。Flag的格式通常是
flag{...}或CTF{...},那些花括号{和}绝对不能被加密,必须原样保留。我们的if-else逻辑确保了这一点。 - 使用列表拼接:在循环中,我们使用
result.append()而不是直接用字符串的+=运算符。对于较长的文本,前者的效率更高。 - 自反性验证:最后一行代码验证了“自反性”,对密文再次加密,果然变回了原文。
字典法的优点是极其清晰,一看就懂。但它的效率不是最高的,因为每个字符都要进行两次字典查找(char in map)。对于CTF题目那点长度完全够用,但如果我们追求极致,可以看看更数学化的方法。
2.2 方法二:数学公式法(最高效)
埃特巴什码的映射关系其实可以用一个简单的数学公式来描述:
- 对于一个偏移量为
base的字母(‘A’或‘a’),其镜像位置 =(base + 25) - (当前字母ASCII码 - base)=2 * base + 25 - 当前字母ASCII码。 - 观察一下:
ord('A') + ord('Z') = 155,ord('a') + ord('z') = 219。 - 所以,镜像字母的ASCII码 = 155 - 原大写字母ASCII码,或 219 - 原小写字母ASCII码。
def atbash_cipher_math(text):
"""
使用数学计算实现埃特巴什码加密/解密。更高效。
参数 text: 待处理的字符串
返回: 处理后的字符串
"""
result = []
for char in text:
ascii_val = ord(char)
if 'A' <= char <= 'Z':
# 大写字母:A(65) + Z(90) = 155
result.append(chr(155 - ascii_val))
elif 'a' <= char <= 'z':
# 小写字母:a(97) + z(122) = 219
result.append(chr(219 - ascii_val))
else:
# 非字母字符原样保留
result.append(char)
return ''.join(result)
# 测试,结果应该和字典法一模一样
print(atbash_cipher_math("Hello CTF{Atbash_Is_Fun}"))
代码解读与优势:
- 效率提升:避免了字典的创建和查找开销,直接进行计算。对于超长文本(虽然CTF不常见),这种方法更快。
- 逻辑紧凑:利用ASCII码的连续性(‘A’到‘Z’,‘a’到‘z’是连续的)和对称性,用减法一步完成映射。
- 边界清晰:
if 'A' <= char <= 'Z'这种判断方式非常Pythonic,清晰表达了字符范围。
在实际写CTF脚本时,我更喜欢用第二种方法,因为它写起来快,跑起来也快。你可以把它封装成一个函数,随时调用。
2.3 方法三:使用str.maketrans和str.translate(最Pythonic)
Python的字符串内置方法提供了终极简洁的方案:
def atbash_cipher_translate(text):
"""
使用str.maketrans和str.translate实现,代码最简洁。
"""
# 创建映射表
# 生成正序和倒序的字母表
original_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
reversed_upper = "ZYXWVUTSRQPONMLKJIHGFEDCBA"
original_lower = "abcdefghijklmnopqrstuvwxyz"
reversed_lower = "zyxwvutsrqponmlkjihgfedcba"
# 创建转换表
translation_table = str.maketrans(original_upper + original_lower,
reversed_upper + reversed_lower)
# 应用转换
return text.translate(translation_table)
# 测试
print(atbash_cipher_translate("The Quick Brown Fox jumps over 123!"))
一句话解释:str.maketrans生成一个翻译表,str.translate根据这个表批量替换字符串中的字符。这是处理这类简单替换密码的“王牌”方法,一行核心代码搞定,而且底层用C实现,速度极快。
三种方法怎么选?
- 学习理解:用字典法,一步步看清映射关系。
- 比赛实战:用数学公式法或translate法,写得快,跑得快。
- 代码优雅:毫无疑问是translate法。
3. CTF实战演练:识别与破解埃特巴什码
光会写代码不够,关键是要在题目里认出它来。埃特巴什码的密文有一些可识别的特征,结合Python脚本,我们可以快速定位。
3.1 密文特征与肉眼识别技巧
拿到一段疑似古典密码的密文,先问自己几个问题:
- 是否只包含字母? 埃特巴什码通常只转换字母,保留数字、标点和特殊符号(如
{}_!?)。如果一段“乱码”里混杂着明显的数字和符号,它很可能被某种编码(如Base64)处理过,或者埃特巴什只是其中一步。 - 有没有“镜像”单词? 这是最直接的线索。尝试念一下密文。埃特巴什码处理英文后,会产生很多以不常见字母(如Z, X, V, J)开头或结尾的“伪单词”。比如,
gsv(the的加密)、jfrxp(quick的加密)、yildm(brown的加密)。如果你发现密文单词里z,x,v,j等字母异常多,而e,t,a,o等常见元音很少,就要高度怀疑。 - 试试“首尾字母”猜测:在CTF的Flag中,开头经常是
flag或ctf。flag的埃特巴什码是oztz,ctf的埃特巴什码是xgu。如果你在密文开头看到oztz{或xgu{,那基本可以实锤了。
实战案例1:基础识别 假设题目给出一段密文:Dsviv ziv hlfixgzuv. Z XZKRGFWV ULI GSV YVZIGS.
- 观察:全是字母和空格、句点。单词
Z很显眼,它很可能是A的镜像(因为A<->Z)。GSV出现了两次,它很可能是THE的镜像(T<->G, H<->S, E<->V)。用我们的Python函数跑一下,立刻得到:Where are solutions. A COMPUTER FOR THE BETTER.这很可能就是提示信息。
3.2 编写自动化识别与破解脚本
在CTF比赛中,时间就是分数。我们不能只靠肉眼。我们可以写一个脚本,尝试多种古典密码,其中就包括埃特巴什码。
import string
def try_common_ciphers(ciphertext):
"""
尝试几种常见古典密码解密,包括埃特巴什。
返回一个可能的结果列表。
"""
results = []
# 1. 尝试埃特巴什码
atbash_result = atbash_cipher_math(ciphertext)
# 简单启发式判断:如果解密后包含常见英文单词片段,则可能性高
common_words = ['the', 'and', 'flag', 'ctf', 'is', 'in', 'to', 'of']
score = sum(1 for word in common_words if word in atbash_result.lower())
results.append(('Atbash', atbash_result, score))
# 2. 可以继续添加其他密码的尝试,例如凯撒密码爆破
# ... (此处省略凯撒密码的尝试代码)
# 按“得分”排序,得分高的(更像英文的)排前面
results.sort(key=lambda x: x[2], reverse=True)
return results
# 使用示例
cipher_from_ctf = "Gsv jfrxp yildm ulc qfnkh levi gsv ozab wlt. oztz{svool_dliow}"
possible_solutions = try_common_ciphers(cipher_from_ctf)
print("可能的解密结果(按可能性排序):")
for cipher_name, plaintext, score in possible_solutions[:3]: # 只看前三个
print(f"[{cipher_name}] -> {plaintext} (得分: {score})")
这个脚本会输出埃特巴什码的解密结果,并且因为解密文中包含“the”、“gsv”(原文的“the”被加密为“gsv”,解密后变回“the”)等大量英文单词,所以得分会很高,排在前面。这样我们就能快速锁定埃特巴什码就是正解。
3.3 处理混合编码与“套娃”题
CTF出题人不会让你轻松过关。埃特巴什码常常和其他编码方式组合,形成“套娃”。
经典解题步骤:
- 观察密文:看结尾是否有
=(Base64特征),是否全是十六进制字符(Hex特征),是否有%(URL编码特征)。 - 从外到内剥离:像剥洋葱一样,从最外层的编码开始解。
- 尝试埃特巴什:当解码出一段看起来是字母组合,但又不是正常单词的文本时,马上尝试埃特巴什码。
让我们模拟一个经典题型:
题目给出:
VU9aVFNaU1pYR1VSSE1SWFY=
解题脚本:
import base64
def solve_ctf_challenge(cipher):
print(f"原始密文: {cipher}")
# 第1步:观察有'=',尝试Base64解码
try:
step1 = base64.b64decode(cipher).decode('ascii')
print(f"Base64解码后: {step1}")
except:
step1 = cipher
print("不是标准Base64,或解码失败。")
# 第2步:Base64解码后得到纯英文大写字符串,尝试埃特巴什
if step1.isalpha() and step1.isupper():
step2 = atbash_cipher_math(step1)
print(f"应用Atbash后: {step2}")
# 通常flag会是小写,我们转为小写看看
flag_candidate = step2.lower()
print(f"Flag候选: {flag_candidate}")
# 检查是否符合常见flag格式
if flag_candidate.startswith('flag') or 'ctf' in flag_candidate:
print(f"✅ 发现疑似Flag: {flag_candidate}")
return flag_candidate
else:
print("解码后文本不符合埃特巴什码常见特征,需尝试其他方法。")
return None
# 运行解题
cipher_text = "VU9aVFNaU1pYR1VSSE1SWFY="
solve_ctf_challenge(cipher_text)
运行后,你会看到输出:
原始密文: VU9aVFNaU1pYR1VSSE1SWFY=
Base64解码后: UOZTSZSZXGURHMRXV
应用Atbash后: FLAGHAHACTFISNICE
Flag候选: flaghahactfisnice
✅ 发现疑似Flag: flaghahactfisnice
看,这就是一个标准的“Base64 + 埃特巴什”套娃题。我们的脚本自动化完成了识别和解码流程。
4. 深入原理与变种:不止于26个字母
掌握了基础玩法,我们来看看埃特巴什码的“七十二变”。理解这些变种,能帮助你在CTF中应对那些不按常理出牌的题目。
4.1 数学本质与自反性证明
埃特巴什码的数学核心是一个对合函数。什么叫对合?就是函数自己和自己复合后,得到恒等映射:f(f(x)) = x。
我们用数学公式来表达对于单个字母的映射: 设字母在字母表中的位置索引为 i (A=0, B=1, ..., Z=25)。 加密/解密函数 E(i) = 25 - i。 验证自反性:E(E(i)) = 25 - (25 - i) = i。完美!
在编程中,我们用的 155 - ord('A') 和 219 - ord('a') 正是这个公式在ASCII码体系下的体现。因为 ord('Z') = ord('A') + 25,所以 ord('A') + ord('Z') = 2*ord('A') + 25 = 155。
4.2 扩展与变种
真正的CTF题目不会总是用标准的26字母英文字母表。
1. 自定义字母表 比如,题目可能只用字母表的前N个字母,或者使用自定义的符号序列。
def custom_atbash(text, alphabet):
"""
对任意自定义字母表进行Atbash变换。
参数:
text: 待处理文本
alphabet: 字母表字符串,如"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"""
n = len(alphabet)
# 创建映射字典:首尾对称
mapping = {}
for i in range(n):
src = alphabet[i]
dst = alphabet[n-1-i]
mapping[src] = dst
# 同时建立反向映射,方便加解密同一函数
mapping[dst] = src
result = []
for char in text:
result.append(mapping.get(char, char)) # 找不到的字符原样保留
return ''.join(result)
# 示例:对数字0-9也进行Atbash
alphabet_full = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
test_str = "FLAG123"
encrypted = custom_atbash(test_str, alphabet_full)
print(f"自定义字母表加密 '{test_str}': {encrypted}")
print(f"解密: {custom_atbash(encrypted, alphabet_full)}")
输出可能是FLAG123 -> UOZT876(取决于字母表顺序)。这要求你从题目描述或上下文推断出正确的字母表。
2. 仅对部分字符应用 有时出题人只对单词中的元音或辅音进行埃特巴什替换。这就需要我们仔细分析替换规则。写脚本时,核心逻辑不变,只是修改字符判断条件。
3. 数字埃特巴什 完全可以将规则应用到数字上:0<->9, 1<->8, 2<->7, 3<->6, 4<->5。
def atbash_numbers(text):
"""对字符串中的数字进行Atbash变换(0<->9, 1<->8, ...)"""
digit_map = str.maketrans('0123456789', '9876543210')
return text.translate(digit_map)
print(atbash_numbers("Answer is 12345")) # 输出: Answer is 87654
4.3 安全性探讨:为什么它不安全?
埃特巴什码在现代密码学视角下,是完全不安全的。原因如下:
- 密钥空间极小:它的“密钥”就是那个固定的、唯一的映射关系。攻击者连试都不用试,只有一种可能。
- 保留统计特征:它没有改变原文字母的频率分布。英文中最常见的字母是
e,加密后对应的是v。那么在密文中,v就会是最常见的字母。攻击者通过频率分析,很容易猜出映射关系。 - 无扩散性:明文中的一个微小变化,只会导致密文中对应位置的一个变化,不会影响其他部分。
所以,埃特巴什码绝不能用于任何真正的信息安全目的。它的价值在于教学、趣味谜题,以及CTF中作为一道“开胃菜”或者复杂编码链中的一环。
5. 打造你的CTF密码工具箱:模块化与实战技巧
在真实的CTF比赛或日常练习中,效率至关重要。我们应该把常用的密码函数,包括埃特巴什码,封装成随时可调用的工具。
5.1 创建可复用的Python模块
我习惯创建一个名为crypto_utils.py的文件,里面存放各种密码函数:
# crypto_utils.py
"""
CTF密码学常用工具函数库
作者:你的名字
"""
def atbash(text):
"""埃特巴什码加密/解密。"""
result = []
for ch in text:
if 'A' <= ch <= 'Z':
result.append(chr(155 - ord(ch)))
elif 'a' <= ch <= 'z':
result.append(chr(219 - ord(ch)))
else:
result.append(ch)
return ''.join(result)
def rot13(text):
"""ROT13加密/解密。"""
# ... 实现代码
pass
def caesar_bruteforce(ciphertext):
"""凯撒密码爆破,返回所有25种可能。"""
# ... 实现代码
pass
def detect_simple_substitution(ciphertext):
"""尝试识别简单替换密码(包括埃特巴什)。"""
# 可以在这里集成我们之前写的try_common_ciphers逻辑
pass
# ... 其他函数,如base64解码、hex解码、摩斯电码等
然后在解题脚本中直接导入使用:
from crypto_utils import atbash, caesar_bruteforce
import base64
cipher = "VU9aVFNaU1pYR1VSSE1SWFY="
step1 = base64.b64decode(cipher).decode()
flag = atbash(step1)
print(f"Flag: {flag.lower()}")
5.2 集成到命令行工具
更进一步,我们可以做一个命令行工具,像使用grep或cat一样使用它:
# atbash_cli.py
import argparse
import sys
def main():
parser = argparse.ArgumentParser(description='埃特巴什码命令行工具 - CTF助手')
parser.add_argument('text', nargs='?', help='要处理的文本。如果未提供,则从标准输入读取。')
parser.add_argument('-f', '--file', help='从文件中读取文本')
parser.add_argument('-o', '--output', help='将结果输出到文件')
args = parser.parse_args()
# 获取输入文本
input_text = ""
if args.file:
try:
with open(args.file, 'r', encoding='utf-8') as f:
input_text = f.read()
except FileNotFoundError:
print(f"错误:文件 '{args.file}' 未找到。", file=sys.stderr)
sys.exit(1)
elif args.text:
input_text = args.text
else:
# 从标准输入读取
input_text = sys.stdin.read().strip()
if not input_text:
print("错误:未提供输入文本。", file=sys.stderr)
parser.print_help()
sys.exit(1)
# 处理(加密/解密相同)
result = atbash(input_text)
# 输出结果
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(result)
print(f"结果已写入文件: {args.output}")
else:
print("处理结果:")
print(result)
if __name__ == "__main__":
main()
这样,在终端里就可以这样用:
# 直接处理字符串
python atbash_cli.py "Gsv jfrxp yildm"
# 从文件读取
python atbash_cli.py -f cipher.txt
# 管道操作
echo "UOZTSZSZXGURHMRXV" | python atbash_cli.py
5.3 应对CTF比赛的实战策略
- 优先尝试:遇到一段由字母组成的密文,且长度适中,可以第一时间用埃特巴什码试一下。成本极低,一键运行。
- 组合思维:如果埃特巴什单独解不出有意义的内容,别放弃。想想是不是
Base64/Hex/URL编码 + 埃特巴什,或者埃特巴什 + 凯撒。写一个自动化的解码流水线脚本。 - 注意格式:CTF的Flag有固定格式,如
flag{...}、CTF{...}、SECCON{...}等。解密后,首先检查输出是否符合这些格式,这能帮你快速判断方向是否正确。 - 利用在线工具,但别依赖:有很多在线埃特巴什加解密网站。在紧急或验证思路时可以用,但比赛网络可能不稳定,或者题目环境是离线的。所以,自己本地有一个可靠的脚本库是硬道理。
埃特巴什码就像密码世界里的“Hello World”,它简单,但蕴含着对称和映射的核心思想。通过亲手实现它,你不仅掌握了一个CTF考点,更迈出了理解更复杂加密算法(如仿射密码、维吉尼亚密码)的第一步。下次在CTF赛场上看到一段奇怪的字母,不妨先试试把它“倒过来”看看,也许Flag就在眼前。
更多推荐


所有评论(0)