Python实现古典密码:从凯撒到维吉尼亚的编程与密码学入门
1. 项目概述:为什么现在还要学古典密码?
最近看到不少朋友在讨论信息安全,尤其是家里有孩子报了相关专业的,总有人问“学这个后不后悔”。我的看法是,信息安全从来都不是一个“过时”的领域,它的核心是攻防对抗的思维,而这种思维最好的启蒙,恰恰是那些被我们称为“古典密码”的东西。用Python来实现它们,不是一个怀旧游戏,而是一次绝佳的思维训练。它能让你亲手触摸到密码学的“骨骼”——那些关于替换、移位、统计和密钥的基础概念,这些概念在现代的AES、RSA等算法中依然以更复杂的形式存在。
当你用几十行Python代码实现一个凯撒密码,并尝试去破解它时,你实际上就在经历一次完整的安全评估:理解算法、寻找弱点、实施攻击。这个过程,比直接调用一个cryptography库里的AES.encrypt()要有价值得多。后者让你成为一个“使用者”,而前者让你开始像一个“设计者”或“分析者”一样思考。这也是为什么在CTF(夺旗赛)中,古典密码的嵌套、与隐写术的结合(比如热词里提到的gfsj1127-【catcat】这类题目)一直是常客,它考察的正是这种基础的、灵活的密码学直觉。
所以,这个项目适合谁?如果你是信息安全的新手,正在为“网络与信息安全管理员”或“软考信息安全工程师”考试打基础,这里的数学原理和编程实践能帮你夯实“信息安全数学基础”。如果你是Python初学者,刚刚配好vscode python环境或者用anaconda配好了环境,正愁没地方练手,那么从古典密码入手,既能熟悉python基础语法、python collections等模块,又能做出有实际反馈(加解密文本)的小项目,成就感十足。甚至,当你能把脚本python打包成exe发给朋友玩时,学习的乐趣就又上了一个台阶。
2. 古典密码的核心思想与Python映射
古典密码虽然形式多样,但核心思想可以归结为两大流派:替换和置换(也叫移位)。理解这两点,就握住了打开古典密码大门的钥匙。
2.1 替换密码:字符的一对一映射
替换密码的核心是建立一个映射表,将明文中的每个字符替换为另一个字符。最著名的就是凯撒密码,它是一种特殊的替换密码——等距移位替换。
为什么是等距移位?在古罗马时代,这种规则简单到可以用心算或简单的物理工具(如密码棒)实现,易于加密和解密双方记忆和操作。它的安全性完全依赖于密钥(移位数字)的保密。在Python中,我们可以用字符的ASCII码或自定义字母表索引来实现这种映射。
注意:古典密码通常只考虑字母,忽略大小写和标点。这是为了简化模型,专注于密码算法本身。在实际编程中,我们需要先对输入文本进行预处理(如统一转为大写,过滤非字母字符)。
2.2 置换密码:字符位置的重新排列
置换密码不改变字符本身,而是打乱字符的顺序。比如栅栏密码,把明文按一定规则写成多行,再按列读取生成密文。
为什么改变位置也能加密?这破坏了明文语言的统计特性(如单词间隔、常见字母组合)和可读性。对于短文本,单纯的置换可能效果不明显,但结合替换或其他操作,就能显著增加复杂度。在Python中,这通常涉及到对字符串进行切片、重组和矩阵变换操作。
这两种思想是现代密码学的基石。例如,著名的AES算法中就包含了替换(S盒)和置换(行移位、列混淆)的多轮操作。我们从古典密码学起,就是在理解这些基本操作的原始形态。
3. 核心密码算法Python实现与详解
下面,我们将用Python逐一实现几个最具代表性的古典密码。我会提供可直接运行的代码,并详细解释每一行代码背后的意图和密码学原理。
3.1 凯撒密码:密码学的“Hello World”
凯撒密码是移位密码的代表。其加密过程是:将明文字母按字母表顺序向后移动一个固定数值(密钥),解密则反向移动。
核心Python实现:
def caesar_cipher(text, shift, mode='encrypt'): """ 实现凯撒密码的加密和解密。 参数: text: 待处理的字符串 shift: 移位值(密钥) mode: 'encrypt' 或 'decrypt' 返回: 处理后的字符串 """ result = [] # 预处理:只处理字母,统一转为大写以便于计算 processed_text = text.upper() for char in processed_text: if char.isalpha(): # 核心计算:找到字符在A-Z(0-25)中的位置 base = ord('A') char_index = ord(char) - base if mode == 'encrypt': new_index = (char_index + shift) % 26 # 加密:正向移位 else: # decrypt new_index = (char_index - shift) % 26 # 解密:反向移位 new_char = chr(base + new_index) result.append(new_char) else: # 非字母字符原样保留(可根据需求修改,如过滤掉) result.append(char) return ''.join(result) # 示例使用 plaintext = "HELLO SECURITY" key = 3 ciphertext = caesar_cipher(plaintext, key, 'encrypt') print(f"明文: {plaintext}") print(f"密文: {ciphertext}") print(f"解密: {caesar_cipher(ciphertext, key, 'decrypt')}")代码与原理拆解:
char.isalpha(): 这个判断至关重要。古典密码模型通常只针对字母表,忽略数字和标点。这既是历史习惯,也简化了我们的算法焦点。ord(char) - ord('A'): 这是将字符映射为0-25数字的核心。ord()函数获取字符的Unicode码点,A-Z是连续的(65-90),相减后正好得到字母表索引。(char_index ± shift) % 26: 这是算法的数学核心。取模运算% 26确保了移位是循环的,例如Z后移1位回到A。这体现了密码算法中的“封闭性”要求。- 为什么模数是26?这是由英文字母表的长度决定的。如果你要处理包含数字的更大字符集,模数就需要相应改变。
实操心得:
- 在测试时,尝试
shift值为26。你会发现密文和明文一样,因为移动一整圈回到了原点。这说明密钥空间其实只有0-25(共26种),非常脆弱。 - 对于包含空格和标点的句子,上述代码会保留它们。这在某些CTF题中可能是提示,但在严格的古典密码分析中,通常先会去除这些干扰项。你可以修改代码,在预处理步骤中用
filter函数只保留字母。
3.2 仿射密码:凯撒密码的升级版
仿射密码是凯撒密码的推广,它用了两个密钥a和b,加密公式为:E(x) = (a * x + b) mod 26。其中x是字母索引(0-25),a必须与26互质(即gcd(a, 26) = 1)。
为什么要求a与26互质?这是为了确保加密函数是一个“双射”,即每个明文字母唯一对应一个密文字母,并且可以解密。如果a和26不互质,比如a=2,那么加密E(0)=0, E(13)=0,两个不同的明文(A和N)都加密成了A,解密时就无法确定原文是什么了。
Python实现:
def gcd(a, b): """计算最大公约数,用于检查密钥a是否合法""" while b != 0: a, b = b, a % b return a def mod_inverse(a, m): """ 求模逆元。即寻找一个整数 a_inv,使得 (a * a_inv) % m == 1。 这是解密所必需的。 """ for x in range(1, m): if (a * x) % m == 1: return x return None # 如果不存在逆元 def affine_cipher(text, a, b, mode='encrypt'): """ 实现仿射密码。 参数: a: 乘法密钥,必须与26互质 b: 加法密钥 """ # 1. 密钥有效性检查 if gcd(a, 26) != 1: raise ValueError(f"密钥a={a}无效,必须与26互质。") result = [] processed_text = text.upper() for char in processed_text: if char.isalpha(): base = ord('A') x = ord(char) - base if mode == 'encrypt': # 加密: E(x) = (a*x + b) mod 26 y = (a * x + b) % 26 else: # 解密: D(y) = a_inv * (y - b) mod 26 a_inv = mod_inverse(a, 26) if a_inv is None: raise ValueError(f"密钥a={a}的模26逆元不存在,无法解密。") y = (a_inv * (x - b)) % 26 result.append(chr(base + y)) else: result.append(char) return ''.join(result) # 示例:使用a=5, b=8 (因为gcd(5,26)=1) plaintext = "AFFINE CIPHER" a, b = 5, 8 ciphertext = affine_cipher(plaintext, a, b, 'encrypt') print(f"仿射加密 ({a},{b}): {ciphertext}") print(f"仿射解密: {affine_cipher(ciphertext, a, b, 'decrypt')}")算法深度解析:
- 密钥空间扩大:凯撒密码只有26种可能(b=0-25,a固定为1)。仿射密码中,a有φ(26)=12种选择(与26互质的数),b有26种,所以总密钥数为12*26=312种。虽然依然很小,但已是凯撒的12倍。
- 模逆元计算:解密的关键是找到
a在模26下的逆元a_inv。我们用了最简单的遍历法(从1到25尝试)。对于小模数这没问题,但对于大质数模数(如RSA中),就需要用扩展欧几里得算法来高效计算。这里手动实现一次,对理解公钥密码学的底层原理非常有帮助。 - 错误处理:代码中加入了密钥检查,当用户输入无效的
a时(如a=2),会立即抛出错误。这在构建健壮的密码工具时是必要的。
3.3 维吉尼亚密码:多表替换的里程碑
维吉尼亚密码通过使用一个关键词,实现多个凯撒密码的轮换使用,极大增强了抗频率分析的能力。关键词KEY会重复使用,每个字母对应一个移位量(A=0, B=1, ..., Z=25)。
Python实现:
def vigenere_cipher(text, keyword, mode='encrypt'): """ 实现维吉尼亚密码。 参数: keyword: 密钥词,仅包含字母。 """ result = [] # 预处理:统一大写,并过滤非字母字符(但保留其位置用于还原?这里简单处理,仅加密字母) processed_text = text.upper() keyword = keyword.upper().replace(' ', '') key_length = len(keyword) key_indices = [ord(k) - ord('A') for k in keyword] # 预计算密钥字母的移位值 non_alpha_count = 0 # 用于记录非字母字符,以便密钥索引对齐 for i, char in enumerate(processed_text): if char.isalpha(): # 计算当前使用的密钥字母索引 key_index = (i - non_alpha_count) % key_length shift = key_indices[key_index] base = ord('A') char_index = ord(char) - base if mode == 'encrypt': new_index = (char_index + shift) % 26 else: new_index = (char_index - shift) % 26 result.append(chr(base + new_index)) else: # 非字母字符原样输出,并且需要记录,以免打乱密钥流同步 result.append(char) non_alpha_count += 1 return ''.join(result) # 示例 plaintext = "ATTACK AT DAWN" keyword = "LEMON" ciphertext = vigenere_cipher(plaintext, keyword, 'encrypt') print(f"维吉尼亚加密 (密钥:{keyword}): {ciphertext}") print(f"维吉尼亚解密: {vigenere_cipher(ciphertext, keyword, 'decrypt')}")为什么维吉尼亚更安全?凯撒密码中,一个字母永远被加密成同一个字母(单表替换)。在英文中,E的出现频率最高,那么密文里出现频率最高的字母很可能就对应E。维吉尼亚密码中,明文的同一个字母,如果对应的密钥字母不同,会被加密成不同的密文字母。例如,用密钥KEY加密HELLO,第一个L对应密钥E(移位4),第二个L对应密钥Y(移位24),结果不同。这破坏了单字母频率统计,破解难度直线上升。
实操中的关键细节:
- 密钥流同步:代码中
(i - non_alpha_count) % key_length是精髓。它确保了密钥字母只对明文字母进行移位,跳过空格、标点等非字母字符。如果简单按字符位置i % key_length取密钥,遇到空格时密钥流就会“错位”,导致解密失败。这是很多初学者容易忽略的坑。 - 密钥管理:维吉尼亚的安全性很大程度上依赖于密钥的长度和随机性。一个短且常见的关键词(如“KEY”、“SECRET”)很容易被猜出或通过卡西斯基试验破解。
3.4 栅栏密码:典型的置换密码
栅栏密码将明文按“之”字形排列,然后按行读取形成密文。例如,深度为3的栅栏密码,将明文HELLOWORLD写成:
H O L E L W R D L O按行读取:HOLELWRDLO。
Python实现:
def rail_fence_cipher(text, rails, mode='encrypt'): """ 实现栅栏密码。 参数: rails: 栅栏的层数(深度) """ if rails < 2: return text if mode == 'encrypt': # 创建rails个空字符串,模拟栅栏 fence = [''] * rails rail = 0 direction = 1 # 1表示向下,-1表示向上 for char in text: fence[rail] += char # 到达顶部或底部时转向 if rail == 0: direction = 1 elif rail == rails - 1: direction = -1 rail += direction return ''.join(fence) else: # 解密稍复杂,需要先重建栅栏的“形状” length = len(text) # 创建一个二维列表模拟空栅栏 fence = [['' for _ in range(length)] for __ in range(rails)] # 标记出栅栏的路径位置(放置*号占位) rail = 0 direction = 1 for i in range(length): fence[rail][i] = '*' if rail == 0: direction = 1 elif rail == rails - 1: direction = -1 rail += direction # 将密文字母按行填充到*号位置 index = 0 for r in range(rails): for c in range(length): if fence[r][c] == '*' and index < length: fence[r][c] = text[index] index += 1 # 按照加密时的路径读取明文 result = [] rail = 0 direction = 1 for i in range(length): result.append(fence[rail][i]) if rail == 0: direction = 1 elif rail == rails - 1: direction = -1 rail += direction return ''.join(result) # 示例 plaintext = "HELLOWORLD" rails = 3 ciphertext = rail_fence_cipher(plaintext, rails, 'encrypt') print(f"栅栏加密 (深度{rails}): {ciphertext}") print(f"栅栏解密: {rail_fence_cipher(ciphertext, rails, 'decrypt')}")置换密码的特点与局限:
- 不改变字符:密文和明文由完全相同的字母组成,只是顺序变了。这对于非常短的文本可能加密效果不明显。
- 密钥是栅栏深度:密钥空间很小(通常从2到文本长度的一半)。可以通过暴力枚举所有可能的深度来破解。
- 常与其他密码结合:正因为其弱点明显,在CTF或实际古典密码应用中,栅栏密码很少单独使用。它常作为一层“混淆”手段,与替换密码(如凯撒、维吉尼亚)嵌套使用,增加分析难度。这就是热词中提到的“嵌套”玩法。
4. 从实现到破解:古典密码分析实战
只会加密不算真正理解。密码学的另一半是密码分析,即如何在不知道密钥的情况下破解密文。我们用Python来实现两种经典的攻击方法。
4.1 凯撒密码的暴力破解与频率分析
由于凯撒密码只有26种可能的密钥,最直接的方法就是暴力枚举所有偏移量,然后人工观察哪个结果是有意义的英文。但我们可以用频率分析来让程序自动做出最佳猜测。
英文单字母频率统计(近似):
E: 12.7%, T: 9.1%, A: 8.2%, O: 7.5%, I: 7.0%, N: 6.7%, ...Python实现智能破解:
def frequency_analysis(text): """统计文本中字母频率,返回一个字典""" text = text.upper() letter_count = {chr(i): 0 for i in range(ord('A'), ord('Z')+1)} total_letters = 0 for char in text: if char in letter_count: letter_count[char] += 1 total_letters += 1 if total_letters == 0: return {} # 计算频率百分比 frequency = {k: (v / total_letters) * 100 for k, v in letter_count.items()} return frequency def caesar_break(ciphertext): """ 尝试破解凯撒密码。 步骤:1. 暴力枚举所有26种移位。2. 对每个结果计算频率并与英文字母标准频率做相关性比较。 """ # 英文字母标准频率(按E,T,A,O,I,N,S...顺序的百分比) english_freq = [ 12.70, 9.06, 8.17, 7.51, 6.97, 6.75, 6.33, 6.09, 5.99, 4.25, 4.03, 2.78, 2.76, 2.41, 2.36, 2.23, 2.02, 1.97, 1.93, 1.49, 1.39, 0.98, 0.77, 0.15, 0.15, 0.10 ] # 对应A-Z best_shift = 0 best_correlation = -1 possible_plaintexts = [] for shift in range(26): # 用当前shift尝试解密 decrypted = caesar_cipher(ciphertext, shift, 'decrypt') # 计算解密文本的字母频率 freq = frequency_analysis(decrypted) if not freq: continue # 将频率字典按字母表顺序转为列表 freq_list = [freq.get(chr(ord('A')+i), 0) for i in range(26)] # 计算与标准频率的相关系数(皮尔逊积矩相关系数的简化版) # 这里用点积作为相关性的简单度量 correlation = sum(f * e for f, e in zip(freq_list, english_freq)) possible_plaintexts.append((decrypted, shift, correlation)) if correlation > best_correlation: best_correlation = correlation best_shift = shift # 按相关性排序,输出最有可能的前3个结果 possible_plaintexts.sort(key=lambda x: x[2], reverse=True) print("最有可能的破解结果(按频率匹配度排序):") for i, (text, shift, corr) in enumerate(possible_plaintexts[:3]): print(f" 尝试 {i+1}: Shift={shift:2d}, 匹配度={corr:.2f}, 明文预览: {text[:50]}...") return caesar_cipher(ciphertext, best_shift, 'decrypt') # 测试破解 ciphertext = caesar_cipher("The quick brown fox jumps over the lazy dog", 17, 'encrypt') print(f"待破解密文: {ciphertext}") plaintext_guessed = caesar_break(ciphertext) print(f"\n程序推荐的最可能明文: {plaintext_guessed}")破解原理与注意事项:
- 频率分析的有效性:这种方法依赖于密文足够长,以体现英文的统计特征。如果密文只有几个单词,频率可能失真,程序可能会给出错误的最佳猜测。这时就需要结合暴力枚举的结果人工判断。
- 相关系数简化:我们用了简单的点积来代替复杂的相关系数计算,因为目的是比较相对大小,而非绝对值。在实际更严谨的工具中,会使用卡方检验或更精确的相关系数。
- 非字母处理:我们的
frequency_analysis函数只统计字母,忽略了空格和标点。这在实际中是合理的,因为古典密码分析通常先处理纯字母文本。
4.2 维吉尼亚密码的卡西斯基试验思路
完全破解维吉尼亚密码要复杂得多,但其中一个关键步骤是确定密钥长度,卡西斯基试验是经典方法。其核心思想是:在密文中寻找重复出现的片段,这些片段可能对应明文中相同的单词(如“THE”、“AND”),而它们之间的距离很可能是密钥长度的倍数。
Python实现密钥长度推测:
def kasiski_examination(ciphertext, seq_len=3): """ 卡西斯基试验:寻找重复序列,计算其间距的公约数以推测密钥长度。 参数: seq_len: 要寻找的重复序列的最小长度(通常为3) """ ciphertext = ''.join(filter(str.isalpha, ciphertext.upper())) # 取纯字母密文 n = len(ciphertext) # 步骤1:找出所有重复序列及其位置 seq_positions = {} for i in range(n - seq_len + 1): seq = ciphertext[i:i+seq_len] if seq not in seq_positions: seq_positions[seq] = [] seq_positions[seq].append(i) # 只保留出现超过1次的序列 repeating_seqs = {seq: pos for seq, pos in seq_positions.items() if len(pos) > 1} if not repeating_seqs: print("未找到足够长的重复序列,卡西斯基试验可能不适用。") return [] print(f"找到的重复序列(长度{seq_len})及位置:") for seq, pos in repeating_seqs.items(): print(f" '{seq}': {pos}") # 步骤2:计算重复序列间距 distances = [] for seq, positions in repeating_seqs.items(): for i in range(len(positions)): for j in range(i+1, len(positions)): dist = positions[j] - positions[i] distances.append(dist) print(f" 序列 '{seq}' 间距: {dist}") # 步骤3:对所有间距求最大公约数(或公约数),推测密钥长度 from math import gcd from functools import reduce if distances: # 计算所有间距的最大公约数 overall_gcd = reduce(gcd, distances) print(f"\n所有间距的最大公约数 (GCD): {overall_gcd}") # 密钥长度可能是GCD的因子 possible_lengths = [] for l in range(2, min(overall_gcd, 20) + 1): # 假设密钥长度不超过20 if overall_gcd % l == 0: possible_lengths.append(l) print(f"推测的可能密钥长度 (GCD的因子): {possible_lengths}") return possible_lengths else: return [] # 测试(使用一个较长的、有重复单词的明文加密后测试) plain_test = "THIS IS A SECRET MESSAGE THAT CONTAINS THE WORD SECRET TWICE FOR TESTING SECRET" key_test = "KEY" cipher_test = vigenere_cipher(plain_test, key_test, 'encrypt').replace(' ', '') print(f"测试密文: {cipher_test[:50]}...") possible_lengths = kasiski_examination(cipher_test, 3)卡西斯基试验的局限性:
- 需要足够长的密文:密文太短,重复序列可能只是随机巧合,或者根本不存在。
- 明文需有重复词汇:如果明文本身用词高度随机,没有像“THE”、“AND”、“SECRET”这样的常见词重复,这个方法就失效了。
- 结果是指示性的:它给出的是密钥长度的可能值(因子),还需要结合弗里德曼试验(利用重合指数)来进一步确认最可能的长度。
一旦确定了密钥长度L,维吉尼亚密码就被分解成了L个独立的凯撒密码。你可以将密文按每第L个字母分组,对每个分组单独进行频率分析,破解出密钥的每一个字母,最终拼出完整密钥。这个过程虽然繁琐,但用Python自动化是完全可行的,这留给你作为扩展练习。
5. 工程化与扩展:打造你的古典密码工具箱
当我们把几个密码算法都实现后,就可以考虑把它们“工程化”,做成一个更实用、更健壮的工具,甚至图形界面。
5.1 构建一个命令行工具
我们可以用Python的argparse库创建一个命令行程序,支持多种密码算法。
import argparse def main(): parser = argparse.ArgumentParser(description='古典密码加密/解密工具箱') parser.add_argument('mode', choices=['encrypt', 'decrypt', 'break'], help='操作模式') parser.add_argument('cipher', choices=['caesar', 'affine', 'vigenere', 'railfence'], help='密码类型') parser.add_argument('-t', '--text', help='直接输入的文本') parser.add_argument('-f', '--file', help='从文件读取文本') parser.add_argument('-k', '--key', help='密钥(对于凯撒是数字,仿射是"a,b",维吉尼亚是单词,栅栏是深度)') parser.add_argument('-o', '--output', help='输出到文件') args = parser.parse_args() # 获取输入文本 if args.text: input_text = args.text elif args.file: with open(args.file, 'r', encoding='utf-8') as f: input_text = f.read() else: print("错误:请通过 -t 或 -f 提供输入文本。") return # 根据密码类型和模式调用相应的函数 result = "" if args.cipher == 'caesar': if args.mode in ['encrypt', 'decrypt']: if not args.key: print("错误:凯撒密码需要 -k KEY(整数)") return shift = int(args.key) result = caesar_cipher(input_text, shift, args.mode) elif args.mode == 'break': result = caesar_break(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 cipher_tool.py encrypt caesar -t "HELLO" -k 5。
5.2 密码嵌套与CTF实战技巧
在CTF比赛中,古典密码常常不是单独出现的。热词中提到的gfsj1127-【catcat】 简单隐写加古典密码嵌套就是一个典型例子。解题思路通常是:
- 观察与识别:首先看题目给的文件或字符串。可能是二进制文件、图片(隐写)、或者一段看起来像Base64但解码后乱码的文本。
- 尝试常见编码:如果密文由A-Za-z0-9+/=组成,先尝试Base64解码。如果由0-9a-f组成,可能是十六进制。如果只有字母,可能是凯撒、仿射、维吉尼亚或Atbash(字母表反转)等。
- 统计特征分析:对纯字母密文,计算字母频率。如果频率分布非常不均匀(像英文),可能是单表替换(如凯撒、仿射)。如果频率分布非常平坦(接近平均),可能是多表替换(如维吉尼亚)或已进行过置换。
- 工具暴力尝试:对于凯撒、栅栏等密钥空间小的,直接写脚本或在线工具暴力枚举所有可能。
- 嵌套破解:如果一层解密后得到的结果还是乱码,但看起来有规律(比如变成了另一种编码格式),就要考虑嵌套。例如:“Base64解码 → 得到十六进制字符串 → 十六进制转文本 → 得到维吉尼亚密文 → 破解维吉尼亚”。
- 结合上下文:题目描述、文件名、标签(如
gfsj1127)有时就是密钥或提示。
一个模拟嵌套的例子:
import base64 # 模拟一个嵌套加密流程 plain_secret = "FLAG{Classical_Cipher_Is_Fun}" # 第一层:栅栏密码 (深度4) layer1 = rail_fence_cipher(plain_secret, 4, 'encrypt') # 第二层:凯撒密码 (移位7) layer2 = caesar_cipher(layer1, 7, 'encrypt') # 第三层:Base64编码(使其看起来像一堆乱码) final_cipher = base64.b64encode(layer2.encode()).decode() print(f"最终密文 (Base64): {final_cipher}") # 破解思路(逆向): # 1. Base64解码 decoded = base64.b64decode(final_cipher).decode() print(f"Base64解码后: {decoded}") # 2. 凯撒暴力破解(已知是古典密码,可尝试) for shift in range(26): attempt = caesar_cipher(decoded, shift, 'decrypt') # 通过肉眼或程序判断是否有“FLAG{”字样 if "FLAG{" in attempt: print(f"找到可能的凯撒偏移: {shift}, 文本: {attempt}") # 3. 对结果尝试不同深度的栅栏解密 for depth in range(2, 10): final_attempt = rail_fence_cipher(attempt, depth, 'decrypt') if "FLAG{" in final_attempt: print(f" 成功!栅栏深度: {depth}, 最终明文: {final_attempt}") break5.3 避免的坑与最佳实践
在实现和使用这些密码时,有一些坑需要避开:
- 字符集问题:我们的示例默认处理A-Z。如果文本包含小写字母、数字、中文或特殊符号,需要明确处理策略。是忽略、保留还是扩展算法?在CTF中,出题人有时会利用这一点设置陷阱。
- 密钥输入验证:对于仿射密码,必须检查
a是否与26互质。对于维吉尼亚密码,密钥词不应为空,且最好只包含字母。在图形界面或Web应用中,前端和后端都要做验证。 - 性能考虑:对于极长的文本(如整本书),我们的字符串拼接(
result += char)方式效率较低。更高效的做法是使用列表append,最后''.join(list),正如我在代码中所做。 - 代码可读性:密码学代码涉及数学,务必写清楚注释,尤其是模运算和索引计算部分。变量名使用
plaintext、ciphertext、shift、key等有意义的名称。 - 不要用于真实加密:务必记住,所有这些古典密码都绝对不安全,不能用于保护任何真实的敏感信息。它们只有教学、历史研究和CTF娱乐价值。真正的系统应该使用经过严格认证的现代密码库,如Python的
cryptography模块。
最后,学习古典密码的Python实现,就像学习武术的套路。套路本身不能直接用于实战,但它训练了你的基本功、协调性和对力量的理解。当你未来学习AES的S盒、RSA的模幂运算、椭圆曲线的点加时,你会感谢今天亲手实现凯撒移位和模逆元的经历。它让你对“加密”和“解密”这一对互逆操作,有了最直观和本质的把握。