Python实现古典密码:从凯撒到仿射的算法原理与代码实战

📅 2026/7/11 19:42:23
Python实现古典密码:从凯撒到仿射的算法原理与代码实战
1. 项目概述从“背公式”到“玩转”古典密码每次看到密码学相关的教程开头总是“凯撒密码是一种替换密码通过将字母按字母表顺序移动固定位来实现加密”然后就是一堆公式和表格。说实话这种教法除了应付考试对真正理解密码学的“手感”帮助不大。我们学编程尤其是用Python这种工具最大的优势不就是能把抽象概念“可视化”、“可操作化”吗今天我们就换个玩法不背定义不记公式直接上手写代码在敲键盘的过程中把凯撒密码和它的升级版——仿射变换——的原理、弱点、应用场景摸个门儿清。这个项目适合谁如果你是刚学完Python基础语法变量、循环、条件判断、函数想找个有趣又有深度的练手项目那它再合适不过了。它能帮你巩固字符串处理、列表索引、模运算这些核心知识点更重要的是让你体验从“知道”一个概念到“实现”一个功能的完整过程。对于已经入门的开发者这也是一个绝佳的回顾古典密码思想、理解现代加密算法基石的机会。我们将从最基础的凯撒密码开始一步步推导到更通用的仿射密码并附上可以直接运行、甚至能扩展的完整代码。我们的目标不是成为密码学家而是通过动手真正“玩转”这两个经典的加密工具。2. 核心思路拆解为什么是凯撒和仿射在动手写代码之前我们先得理清思路为什么选择这两个密码它们之间有什么联系我们的代码架构应该如何设计才能清晰且易于扩展2.1 凯撒密码一切的原点凯撒密码的核心思想简单到令人发指移位。把字母表中的每个字母都向后或向前移动固定的几位。比如移动3位那么A就变成DB变成E以此类推Z绕回变成C。这个“绕回”的操作在数学上就是模运算Modulo。字母表有26个字母我们可以把A看作0B看作1...Z看作25。那么移动k位的加密操作就可以用公式表示E(x) (x k) mod 26解密则是反向移动D(x) (x - k) mod 26这里的x是字母的序号0-25。你看根本不用死记硬背“A变成D”一个公式就概括了所有情况。我们写代码就是要实现这个公式对一段文本中每个字母的转换。注意古典密码通常只处理字母忽略大小写和空格、标点。在我们的实现中为了简化会先将文本统一转为大写并过滤掉非字母字符。这是一种常见的处理方式但你要知道在实际的、稍微复杂一点的场景中保留格式和标点也是加密的一部分。2.2 仿射变换凯撒的“升级补丁”凯撒密码最大的问题是什么太容易被破解。总共只有25种可能的移位移动0位等于没加密计算机一眨眼就能全部试出来这叫“暴力破解”。仿射密码就是为了增加破解难度而设计的“升级版”。它不再只是简单的加法移位而是引入了一次函数的形式E(x) (a * x b) mod 26这里多了个系数a。a和b都是密钥其中a必须与26互质即最大公约数gcd(a, 26) 1。为什么因为如果a和26不互质那么不同的x可能会映射到同一个E(x)导致解密时出现歧义无法得到唯一的原文字母。例如如果a2那么E(0)0A-AE(13)0N-AA和N都加密成了A这还怎么解密所以仿射密码的密钥空间所有可能的密钥对(a, b)比凯撒密码大得多。满足条件的a有12个1,3,5,7,9,11,15,17,19,21,23,25b有26种选择所以总共有12 * 26 312种密钥。虽然对现代计算机来说依然很小但比起凯撒的25种已经是一个进步了。更重要的是它引入了乘法运算使得加密过程不再是简单的位移破坏了字母的统计特性虽然不完全密码学思想从这里开始萌芽。从代码角度看凯撒密码是仿射密码在a1时的一个特例。因此我们的代码设计可以非常优雅先实现一个通用的仿射密码加解密函数当传入参数a1时它就退化成了凯撒密码。这样写代码复用率高逻辑也清晰。2.3 我们的代码设计蓝图基于以上分析我们的程序应该包含以下核心模块工具函数包括检查a是否与26互质的函数、求模逆元用于解密的函数、字符与数字相互转换的函数。核心加解密函数一个通用的affine_cipher(text, a, b, modeencrypt)函数通过mode参数控制加密或解密。交互与演示提供命令行接口或简单的输入输出让用户可以方便地体验加密解密过程。暴力破解演示可选针对凯撒密码写一个简单的函数尝试所有可能的移位量展示其脆弱性。我们将采用自顶向下的方式先搭建框架再填充细节。过程中我会分享我在处理边界条件、字符编码和算法效率上的思考。3. 核心工具函数实现打好地基任何扎实的项目都从基础工具开始。在实现主加解密逻辑前我们需要几个可靠的“螺丝刀”和“扳手”。3.1 字符与数字的转换器密码学运算是在数字上进行的而我们处理的是文本。所以第一要务是建立字母和数字0-25之间的映射关系。def char_to_num(char): 将大写字母转换为0-25的数字。 # ord(A) 返回65减去65后A-0, B-1, ..., Z-25 return ord(char.upper()) - ord(A) def num_to_char(num): 将0-25的数字转换为大写字母。 # 确保数字在0-25范围内利用模运算处理负数或超范围的情况 return chr((num % 26) ord(A))这两个函数是后续所有操作的基石。注意char_to_num中我们直接调用了.upper()这意味着我们的函数默认只处理大写字母。在主体函数中我们会提前将输入文本统一大写化。实操心得这里我选择统一转大写是为了简化。但在一个更完整的项目中你可能会想保留原始大小写。一个常见的做法是加密时记录每个字符是否为大写解密后再恢复。这虽然增加了复杂度但提升了实用性。作为初版我们先从简单的来。3.2 互质判断与模逆元计算这是仿射密码的数学核心。我们需要两个函数gcd(a, b)计算最大公约数用于判断a与26是否互质。mod_inverse(a, m)计算a在模m下的逆元即找到一个数a_inv使得(a * a_inv) % m 1。这在解密时至关重要因为解密公式是D(y) a_inv * (y - b) mod 26。Python的math库自带了gcd函数我们可以直接使用。而模逆元的计算我们可以使用扩展欧几里得算法或者因为模数26很小直接暴力遍历1到25也未尝不可。为了代码清晰和教学目的我们实现一个简单的遍历查找import math def is_coprime(a, b): 判断a和b是否互质最大公约数为1。 return math.gcd(a, b) 1 def mod_inverse(a, m26): 计算a在模m下的逆元。如果不存在则返回None。 if not is_coprime(a, m): return None # a与m不互质逆元不存在 for x in range(1, m): if (a * x) % m 1: return x return None # 理论上不会走到这里如果互质则逆元必存在对于仿射密码m固定为26。mod_inverse函数在解密时会被调用。如果传入的a不合法与26不互质函数会返回None我们在主函数中需要处理这个错误情况。注意事项mod_inverse的暴力搜索法在模数很大比如RSA算法中使用的几百位的大数时效率极低必须使用扩展欧几里得算法。但在我们这个小场景中遍历26个数完全不是问题。这里的选择体现了“合适的就是最好的”这一编程哲学。4. 主加解密函数实现核心引擎有了工具函数我们就可以组装核心的加解密引擎了。我们将遵循“一个函数两种模式”的设计让代码更紧凑。4.1 函数设计与参数解析def affine_cipher(text, a, b, modeencrypt): 使用仿射密码加密或解密文本。 参数: text (str): 待加密/解密的文本。 a (int): 密钥a必须与26互质。 b (int): 密钥b0-25之间的整数。 mode (str): encrypt 或 decrypt。 返回: str: 加密或解密后的文本。 # 参数校验 if not isinstance(a, int) or not isinstance(b, int): raise ValueError(密钥a和b必须是整数。) if not is_coprime(a, 26): raise ValueError(f密钥a{a}必须与26互质。) if not (0 b 26): raise ValueError(f密钥b{b}必须在0到25之间。) # 统一处理文本转大写只保留字母 processed_text .join(filter(str.isalpha, text.upper())) if not processed_text: return # 如果过滤后为空字符串直接返回 result_chars [] # 根据模式选择解密所需的a的逆元 if mode decrypt: a_inv mod_inverse(a, 26) if a_inv is None: raise ValueError(f无法找到密钥a{a}在模26下的逆元无法解密。) # 遍历处理每个字符 for char in processed_text: x char_to_num(char) # 字母转数字 if mode encrypt: # 加密公式: E(x) (a*x b) mod 26 y (a * x b) % 26 else: # mode decrypt # 解密公式: D(y) a_inv * (y - b) mod 26 y (a_inv * (x - b)) % 26 result_chars.append(num_to_char(y)) # 数字转字母 return .join(result_chars)这个函数是项目的心脏。我们来拆解几个关键点健壮性检查在函数开头对a和b进行类型和有效性校验。这是专业代码的好习惯能避免很多因非法输入导致的诡异错误。文本预处理‘’.join(filter(str.isalpha, text.upper()))这一行代码做了三件事text.upper()转大写filter(str.isalpha, ...)过滤出所有字母字符‘’.join(...)将过滤后的字符列表重新连接成字符串。这确保了核心算法只处理纯字母。模式切换通过mode参数用同一个函数处理加密和解密避免了代码重复。解密时需要提前计算好a的模逆元a_inv。算法核心加密和解密的公式被直接翻译成代码。注意解密公式(a_inv * (x - b)) % 26中的(x - b)可能产生负数但Python的%运算符对负数的处理方式-1 % 26 25正好符合我们的模运算需求非常方便。4.2 凯撒密码仿射的特例现在实现凯撒密码就轻而易举了。既然凯撒密码是a1的仿射密码我们可以直接封装一个更易用的函数def caesar_cipher(text, shift, modeencrypt): 使用凯撒密码加密或解密文本。 参数: text (str): 待加密/解密的文本。 shift (int): 移位数通常为1-25。 mode (str): encrypt 或 decrypt。 返回: str: 加密或解密后的文本。 # 凯撒密码是a1的仿射密码 a 1 b shift % 26 # 确保b在0-25范围内 return affine_cipher(text, a, b, mode)看多么简洁caesar_cipher只是affine_cipher的一个特例调用。这里我们对shift做了% 26处理这样用户传入27就等同于传入1更加友好。5. 让程序跑起来交互与演示代码写好了我们需要一个方式来运行和测试它。我们可以写一个简单的命令行交互程序也可以直接写几个演示用例。5.1 基础功能演示我们先写一个main函数来展示基本功能def main(): print( 古典密码加解密演示 \n) # 示例1: 凯撒密码加密 plaintext HELLO WORLD shift 3 ciphertext caesar_cipher(plaintext, shift, encrypt) print(f凯撒加密 (移位{shift}):) print(f 明文: {plaintext}) print(f 密文: {ciphertext}) # 解密 decrypted caesar_cipher(ciphertext, shift, decrypt) print(f 解密: {decrypted}) print() # 示例2: 仿射密码加密 plaintext2 SECRET MESSAGE a, b 5, 8 # a5与26互质是合法密钥 ciphertext2 affine_cipher(plaintext2, a, b, encrypt) print(f仿射加密 (a{a}, b{b}):) print(f 明文: {plaintext2}) print(f 密文: {ciphertext2}) # 解密 decrypted2 affine_cipher(ciphertext2, a, b, decrypt) print(f 解密: {decrypted2}) print() # 示例3: 处理带空格和标点的句子 sentence Hello, Python! This is a test. shift 7 encrypted_sentence caesar_cipher(sentence, shift, encrypt) print(f处理复杂文本凯撒加密 (移位{shift}):) print(f 原文: {sentence}) print(f 密文(仅字母): {encrypted_sentence}) # 注意解密回来的文本只有字母没有原标点和空格 decrypted_sentence caesar_cipher(encrypted_sentence, shift, decrypt) print(f 解密后: {decrypted_sentence}) print( (注意标点和空格在预处理时被过滤了)\n) # 示例4: 测试非法密钥 print(测试非法密钥a (a2, 与26不互质):) try: affine_cipher(TEST, 2, 3, encrypt) except ValueError as e: print(f 预期错误: {e}) if __name__ __main__: main()运行这个main函数你会看到清晰的输入输出对比直观地理解加密解密过程以及程序如何处理边界情况。5.2 实现一个简单的交互式命令行为了让体验更好我们可以做一个循环让用户随时输入文本和密钥进行加解密def interactive_mode(): 交互式加解密模式。 print(欢迎进入古典密码交互模式) print(支持命令) print( c - 凯撒密码加解密) print( a - 仿射密码加解密) print( q - 退出) while True: command input(\n请输入命令 (c/a/q): ).strip().lower() if command q: print(再见) break elif command c: text input(请输入文本: ) try: shift int(input(请输入移位数 (整数): )) mode input(加密(e)还是解密(d)? ).strip().lower() mode encrypt if mode e else decrypt result caesar_cipher(text, shift, mode) print(f结果: {result}) except ValueError as e: print(f输入错误: {e}) elif command a: text input(请输入文本: ) try: a int(input(请输入密钥a (必须与26互质如1,3,5,7,9,11,15,17,19,21,23,25): )) b int(input(请输入密钥b (0-25): )) mode input(加密(e)还是解密(d)? ).strip().lower() mode encrypt if mode e else decrypt result affine_cipher(text, a, b, mode) print(f结果: {result}) except ValueError as e: print(f输入错误: {e}) else: print(未知命令请重新输入。)这个交互模式虽然简单但具备了完整的功能。你可以把它加到main函数里作为一个选项。6. 进阶玩法暴力破解与密码分析只实现加密解密还不够“玩转”。密码学的另一面是“破译”。我们来给凯撒密码写一个“破解器”直观感受其脆弱性。6.1 凯撒密码的暴力破解原理很简单凯撒密码只有25种可能的密钥移位1-25。我们只需要遍历所有可能性将密文全部解密一遍然后通过一些启发式规则比如判断解密结果中是否包含常见的英文单词来找出最可能的明文。def brute_force_caesar(ciphertext): 暴力破解凯撒密码。 参数: ciphertext (str): 密文。 返回: list: 一个列表包含所有25种可能的解密结果及其移位量。 possibilities [] for shift in range(1, 26): # 尝试移位1到25 decrypted caesar_cipher(ciphertext, shift, decrypt) possibilities.append((shift, decrypted)) return possibilities def smart_break_caesar(ciphertext, common_wordsNone): 稍微聪明一点的破解尝试所有可能并尝试匹配常见单词。 参数: ciphertext (str): 密文。 common_words (set): 一个常见英语单词的集合。 返回: tuple: (最可能的移位量, 最可能的明文, 匹配的单词列表) if common_words is None: # 提供一个非常小的常见单词集作为示例 common_words {THE, AND, FOR, ARE, BUT, NOT, YOU, ALL, CAN, HAS} best_shift 0 best_text best_matches [] max_matches 0 possibilities brute_force_caesar(ciphertext) for shift, text in possibilities: # 将解密文本按空格分割成“单词”这里我们的文本没有空格所以需要按长度分割这是一个简化 # 更严谨的做法是使用字典进行单词匹配这里我们简单检查常见单词是否在文本中 words_in_text text.split() # 如果文本有空格 # 由于我们过滤了空格这里用一个简单检查看常见单词是否作为子串出现 matches [word for word in common_words if word in text] if len(matches) max_matches: max_matches len(matches) best_shift shift best_text text best_matches matches return best_shift, best_text, best_matchesbrute_force_caesar函数展示了暴力破解的“蛮力”过程。smart_break_caesar则加入了一点简单的频率分析思想匹配常见单词。在实际中更专业的工具会分析字母频率英文中E、T、A出现频率最高但对我们理解原理来说这个简单版本已经足够。6.2 演示破解过程让我们写一段代码来演示def demo_break(): print(\n 凯撒密码暴力破解演示 \n) # 加密一段已知的文本 original THIS IS A SECRET MESSAGE shift 19 cipher caesar_cipher(original, shift, encrypt) print(f原始明文: {original}) print(f使用移位 {shift} 加密后: {cipher}) print(\n开始暴力破解尝试所有25种移位...) all_possibilities brute_force_caesar(cipher) # 打印前5种可能结果 print(\n前5种可能的解密结果) for i in range(5): s, t all_possibilities[i] print(f 移位{s:2d}: {t}) print(\n使用简单词典匹配进行智能猜测...) likely_shift, likely_text, matches smart_break_caesar(cipher) print(f 最可能的移位: {likely_shift}) print(f 对应的明文: {likely_text}) print(f 匹配到的常见词: {matches}) if likely_shift shift: print( ✅ 破解成功) else: print( ❌ 破解失败可能是文本太短或常见词库不匹配。)运行这个演示你会看到计算机如何不费吹灰之力地“试”出凯撒密码的密钥。这能让你深刻理解为什么在现代密码学中密钥空间所有可能密钥的数量必须极其巨大才能抵御暴力破解。7. 常见问题与实战排坑指南在实际编写和运行这类代码时你肯定会遇到一些“坑”。下面是我总结的几个典型问题及其解决方案。7.1 字符编码与范围问题问题输入文本包含中文、数字或特殊符号时程序可能出错或得到奇怪结果。根因我们的char_to_num函数假设输入是A-Z的字母。ord(‘中’)的值远大于ord(‘Z’)减去ord(‘A’)后会产生非常大的负数后续模运算结果无意义。解决方案在核心处理前进行严格的过滤。正如我们在affine_cipher函数中所做使用filter(str.isalpha, ...)。这是最安全的方法。如果你需要加密数字和标点则需要定义更广泛的字符集比如ASCII可打印字符并相应调整模数比如95。7.2 模运算中的负数处理问题在解密公式(a_inv * (x - b)) % 26中(x - b)可能为负担心计算结果错误。放心Python的取模运算符%对负数的定义是(a % m)的结果与m同号且满足a (a // m) * m (a % m)。对于-1 % 26结果是25。这完美符合密码学模运算的期望。所以我们不需要对(x-b)的结果做额外处理。7.3 密钥a的有效性验证问题用户可能传入一个与26不互质的a如2、4、13等导致加密映射不是一一对应无法解密。解决方案必须在加密和解密函数开始处进行验证。我们使用is_coprime(a, 26)函数。如果a无效应立即抛出清晰的错误信息而不是继续执行产生错误结果。7.4 加解密结果“不可读”问题加密后的密文是一长串没有空格的大写字母解密后也同样如此丢失了原始格式。说明这是古典密码教学实现的常见做法专注于算法核心。这不是Bug而是Feature在本次教学中。在实际应用中加密通常会处理整个字节流保留所有格式。如果你想挑战自己可以尝试升级代码在加密前记录每个字符是否为字母、大小写、空格或标点加密时只转换字母部分解密后再恢复原始格式。这会大大增加代码复杂度但更实用。7.5 效率问题问题对于超长文本我们的循环处理会慢吗分析对于纯Python循环处理几万、几十万字符的文本也是瞬间完成的。真正的瓶颈可能在于filter和join操作。如果遇到性能瓶颈可以考虑使用列表推导式或str.translate()配合maketrans方法后者在Python中处理字符替换效率极高。例如可以预先计算好加密映射表字典然后用str.translate()批量替换这比遍历每个字符快得多。这里提供一个使用str.translate()优化凯撒密码的思路def caesar_cipher_fast(text, shift, modeencrypt): 使用str.translate()实现更快的凯撒密码。 shift shift % 26 if mode decrypt: shift -shift # 创建映射表 import string alphabet string.ascii_uppercase shifted_alphabet alphabet[shift:] alphabet[:shift] # 创建转换表大写字母到大写字母 trans_table str.maketrans(alphabet, shifted_alphabet) # 处理文本转大写非字母字符原样保留这是一个不同的设计选择 processed_text text.upper() return processed_text.translate(trans_table)这个版本没有过滤非字母字符而是将它们原样保留并且只转换大写字母。这展示了另一种设计选择并且速度更快。你可以思考如何将这种思路应用到仿射密码上。8. 项目扩展与思考完成基础版本后你可以从这个项目出发探索更多有趣的方向支持完整字符集扩展代码使其能加密解密大小写字母、数字、标点符号。你需要定义一个包含所有可用字符的“字母表”并相应调整模运算的基数比如对于52个大小写字母模数就是52。实现维吉尼亚密码这是凯撒密码的“系列升级”使用一个关键词作为密钥实现多表替换大大增强了安全性。你可以尝试实现它这会是理解古典密码进化的好练习。频率分析攻击写一个更强大的破解工具对密文进行字母频率统计并与英文字母标准频率分布比较自动推测最可能的密钥。这能让你亲身体验密码分析学的魅力。图形化界面使用tkinter或PyQt库为你的加解密工具做一个简单的桌面窗口让用户可以通过按钮和输入框交互而不是命令行。文件加解密修改程序使其能够读取一个文本文件加密后写入新文件或者读取密文文件进行解密。这会让工具更具实用性。这个项目就像一把钥匙打开了一扇名为“密码学”的大门。通过亲手实现凯撒和仿射密码你不仅学会了两个经典算法更重要的是理解了模运算在密码学中的核心地位体会了从设计、实现到测试、优化的完整编程流程。下次当你听到“加密”、“密钥”、“暴力破解”这些词时你的脑海里浮现的将不再是枯燥的定义而是一行行自己写过的、能够运行的代码。这才是“玩转”的真正含义。