1. 从一道“看起来很简单”的题说起如果你刚开始接触CTFCapture The Flag竞赛密码学方向大概率是你最先碰到、也最容易卡住的一类题目。它不像Web题那样有直观的页面交互也不像逆向题那样能直接丢进调试器里跑密码学题目往往只给你一段密文、一个加密脚本或者一个看似简单的数学问题然后要求你找出明文或者密钥。很多人第一次遇到这类题的时候反应基本是“这什么东西”然后就开始漫无目的地搜索最后要么放弃要么抄了个脚本但完全不知道自己在干什么。我写这篇东西的目的很直接把CTF密码学题目从“看到就懵”变成“有一套可复用的分析流程”。不管你是刚打完第一场校内赛的新手还是已经做过几道基础题但总觉得不成体系的进阶玩家这里的内容都能帮你把零散的知识点串成一条线。核心关键词就几个编码识别、古典密码、现代密码基础、工具链、解题思维。我会从最基础的编码与古典密码讲起一路延伸到RSA、AES这些现代密码在CTF里的常见考法每一步都配上我实际做题时用的思路和踩过的坑。需要提前说明的是CTF密码学题目和真实的密码学工程之间有相当大的差距。竞赛题目为了可解性往往会刻意削弱安全性比如用很小的素数、固定的IV、可预测的随机数种子。所以你在CTF里学到的“攻击方法”放到真实生产环境里大概率是不成立的。但这不影响它的价值——通过解题你能真正理解加密算法内部的运作机制这种理解在后续做安全开发或者代码审计时非常有用。2. 密码学题目的整体分类与识别思路2.1 先搞清楚你面对的是哪一类题CTF密码学题目大致可以分成这么几个层次从易到难排列编码类严格来说不算加密但经常被归到密码学方向。Base64、Hex、URL编码、Unicode转义、摩斯电码这些都属于这一类。特征是给你一串看起来“有规律但读不懂”的字符。古典密码类凯撒、维吉尼亚、栅栏、培根、仿射、Playfair等。特征是加密脚本很短或者题目描述里暗示了“移位”“替换”之类的词。现代密码基础类RSA、AES、DES、流密码。特征是给你公钥、密文、加密脚本要求你恢复明文或私钥。数学/数论类离散对数、椭圆曲线、格密码。这类题目通常需要一定的数学基础但CTF里出现的往往是简化版本。识别题目类型是第一步也是最关键的一步。我的习惯是拿到题目先看三个东西题目描述、附件文件类型、密文长度。如果附件是一个Python脚本先通读一遍看它调用了哪些库、用了什么算法、有没有明显的漏洞比如随机数种子固定、素数太小。如果附件只有一段文本先尝试常见的编码解码再根据字符集特征判断是不是古典密码。2.2 一个实用的判断流程我一般会按下面的顺序快速过一遍看密文字符集。只有大小写字母和数字可能是Base64或Base32只有0-9和a-f大概率是Hex包含大量等号和加号斜杠基本就是Base64。看长度。Base64编码后的长度通常是4的倍数Hex长度是偶数古典密码的密文长度往往和明文长度一致或成倍数关系。看题目给的提示。如果题目名叫“Caesar is my friend”那不用想了直接凯撒暴力枚举。如果叫“RSA beginner”那就去看公钥和密文。跑一遍自动化识别工具。我常用的是CyberChef的Magic功能或者本地的ciphey工具它能自动尝试多种编码和古典密码。但注意工具只是辅助最终还是要你自己理解每一步在做什么。提示不要一上来就丢给自动化工具。先手动判断一遍哪怕判断错了这个过程本身就在训练你的直觉。工具跑出来的结果如果看不懂那说明你还没真正解出来。2.3 为什么分类能力比具体算法更重要我见过很多新手背了一堆RSA的攻击方法但拿到题目连“这是RSA”都认不出来。原因就是缺乏分类意识。CTF密码学题目的变化非常多但万变不离其宗——它一定是在某个环节上做了削弱。你要做的就是找到那个削弱的环节。举个例子同样是RSA题目有的给你很小的e和很大的明文低加密指数攻击有的给你两个模数共享一个素数共模攻击有的给你多组密文对应同一个明文广播攻击。如果你不知道这些攻击的存在你可能会对着一个大整数发呆但如果你知道分类你就能快速定位到“这题考的是哪种攻击”。3. 编码与古典密码的实操拆解3.1 编码类题目的快速处理编码类题目在CTF里通常出现在签到题或者热身题的位置。它们的解法非常固定我整理了一个速查表编码类型特征解码方式Base64大小写字母数字/长度4的倍数base64 -d或Pythonbase64.b64decodeBase32大写字母数字2-7末尾多个Pythonbase64.b32decodeHex只有0-9和a-fxxd -r -p或bytes.fromhexURL编码大量%后跟两位十六进制urllib.parse.unquoteUnicode转义\u开头后跟四位十六进制直接print或unicode_escape摩斯电码点和划用空格或斜杠分隔查表替换实际操作中最麻烦的不是单一编码而是多层嵌套编码。比如先Base64再Hex再URL编码。我的做法是写一个小脚本循环尝试解码直到输出看起来像正常文本为止。但要注意有些编码解码后可能还是乱码这时候需要人工判断是不是还需要进一步处理。import base64 import binascii from urllib.parse import unquote def auto_decode(s): for _ in range(10): try: # 尝试Base64 if len(s) % 4 0 and all(c in ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/ for c in s): s base64.b64decode(s).decode(utf-8, errorsignore) continue # 尝试Hex if all(c in 0123456789abcdefABCDEF for c in s) and len(s) % 2 0: s bytes.fromhex(s).decode(utf-8, errorsignore) continue # 尝试URL编码 if % in s: s unquote(s) continue except: break return s这个脚本很粗糙但应付大部分签到题足够了。实际比赛中我建议直接开CyberChef它的Magic功能能自动识别多层编码省去很多手动尝试的时间。3.2 古典密码的手工与脚本解法古典密码是CTF密码学里最有“解谜感”的部分。我挑几个最常考的说说。凯撒密码是最简单的移位密码暴力枚举26个偏移量就行。但有时候题目会做变种比如只对字母移位、大小写分别处理、或者结合其他编码。我的习惯是先写一个暴力脚本把所有偏移的结果打印出来肉眼找可读的文本。def caesar_bruteforce(cipher): for shift in range(26): plain for c in cipher: if c.isalpha(): base ord(A) if c.isupper() else ord(a) plain chr((ord(c) - base - shift) % 26 base) else: plain c print(fshift{shift}: {plain})维吉尼亚密码是多表替换密码需要知道密钥才能解。CTF里常见的考法是给出足够长的密文让你通过频率分析或者已知明文攻击来恢复密钥。如果题目给了密钥长度提示可以用卡西斯基测试或者弗里德曼测试来估计。但说实话CTF里的维吉尼亚题目往往不会让你做完整的频率分析而是会给出一些额外提示比如“密钥是一个英文单词”“密钥和题目名有关”之类的。栅栏密码是换位密码把明文按固定字数分组后按列读出。解法就是尝试不同的栏数看哪个输出可读。我一般写个循环栏数从2到密文长度的一半逐个尝试。培根密码用A和B两种字符表示二进制每5个一组对应一个字母。CTF里经常把A和B替换成其他字符对比如大小写、奇偶、元音辅音。识别方法是看密文是否只有两种字符且长度是5的倍数。注意古典密码题目有时候会故意用错误的英文拼写或者夹杂数字来干扰你。这时候不要慌先把能识别的部分解出来剩下的可能是填充或者提示。3.3 古典密码的实战心得我做古典密码题有一个原则先假设它是最简单的变种再逐步增加复杂度。比如看到一串字母先试凯撒不行再试维吉尼亚再不行考虑是不是仿射或者Playfair。不要一上来就想着“这题肯定很难”很多时候答案就藏在最基础的解法里。另外古典密码题目经常和编码结合。比如先凯撒移位再Base64编码。这时候你需要反向操作先Base64解码再凯撒暴力。我建议养成习惯每解一层就把中间结果保存下来方便回溯。还有一个坑是大小写敏感。有些题目在凯撒移位时只移大写字母小写字母保持不变有些则统一处理。如果你暴力枚举的结果看起来“差一点”检查一下是不是大小写处理方式不对。4. 现代密码题目的核心攻击面4.1 RSA题目的常见漏洞与利用RSA是CTF密码学里出现频率最高的现代密码算法。它的安全性基于大整数分解的困难性但CTF题目为了可解往往会在参数上做手脚。我总结了几种最常见的攻击类型攻击类型触发条件利用方法小素数分解n很小比如小于512位用factordb或yafu分解n低加密指数e很小如e3且明文短直接对密文开e次方共模攻击同一n不同e加密同一明文扩展欧几里得求明文广播攻击同一明文不同n但相同e中国剩余定理因数共享多个n之间有公因数两两求gcd私钥泄露给了d或p、q之一直接解密小素数分解是最基础的。如果n只有一两百位直接丢到factordb.com或者用sage的factor()函数就能分解。我遇到过一些题目n甚至只有几十位手算都能算出来。低加密指数攻击的典型场景是e3明文很短导致m^3 n这时候密文c m^3直接开三次方就行。如果m^3略大于n可以尝试c k*n再开方k从0开始枚举。import gmpy2 def low_exponent_attack(c, e, n): for k in range(100000): m, exact gmpy2.iroot(c k * n, e) if exact: return int(m) return None共模攻击是指同一明文用两个不同的e加密但n相同。这时候可以用扩展欧几里得算法找到s1和s2使得e1s1 e2s2 1然后m c1^s1 * c2^s2 mod n。def common_modulus_attack(c1, e1, c2, e2, n): g, s1, s2 gmpy2.gcdext(e1, e2) if s1 0: c1 gmpy2.invert(c1, n) s1 -s1 if s2 0: c2 gmpy2.invert(c2, n) s2 -s2 m (pow(c1, s1, n) * pow(c2, s2, n)) % n return m广播攻击是同一明文用相同的e但不同的n加密多组可以用中国剩余定理合并。条件是e组密文且e个n两两互素。这些攻击的共同点是它们都利用了RSA参数选择不当导致的漏洞。在实际CTF中你需要先读题目的加密脚本看它生成了哪些参数然后判断哪个参数“不正常”。4.2 AES与流密码的考法AES在CTF里通常以两种形式出现ECB模式和CBC模式。ECB模式的问题是相同的明文块会产生相同的密文块。如果题目允许你控制部分明文你可以通过构造特定的输入来逐字节恢复未知的明文。这种攻击叫ECB字节翻转攻击或ECB逐字节恢复。核心思路是把未知明文放在你控制的明文后面通过观察密文块的变化来推断未知字节。CBC模式的问题主要出在IV初始化向量上。如果IV可控或者可预测可以实施CBC字节翻转攻击通过修改前一个密文块来影响下一个明文块的解密结果。还有一种叫Padding Oracle攻击利用服务器对填充错误的反馈来逐字节解密。这种攻击在CTF里出现得不多因为需要服务器有交互接口。流密码在CTF里最常见的是异或加密。如果密钥重复使用可以通过已知明文攻击或者频率分析来恢复密钥。我遇到过一道题密钥是一个短字符串重复异或加密了一段英文文本。解法就是利用英文空格的高频特性先恢复出密钥的一部分再逐步扩展。def xor_attack(cipher, known_plain): key bytes([c ^ p for c, p in zip(cipher, known_plain)]) return key4.3 现代密码题目的解题节奏我做现代密码题目的节奏是这样的读脚本先看加密脚本理解它做了什么。重点关注随机数生成、参数选择、模式选择。找异常哪个参数特别小哪个参数重复使用了哪个地方用了不安全的随机数选攻击根据异常选择对应的攻击方法。写脚本用Python的gmpy2、pycryptodome、sage等库实现攻击。验证解出来的明文通常是有意义的字符串或者flag格式如果不是检查步骤。提示gmpy2是处理大整数运算的利器sage在数论和椭圆曲线方面更强。如果本地没有sage可以用在线的SageMathCell。5. 工具链与自动化脚本的搭建5.1 我常用的工具清单CTF密码学题目不需要太复杂的工具链但有几个工具能极大提升效率CyberChef浏览器端的“编码瑞士军刀”支持几乎所有常见编码和古典密码Magic功能能自动识别多层编码。ciphey命令行的自动解码工具适合快速处理签到题。gmpy2Python的大整数运算库RSA题目必备。pycryptodomePython的密码学库支持AES、DES、RSA等。SageMath数学计算环境适合处理椭圆曲线、格密码等高级题目。factordb在线整数分解数据库小n直接查。yafu本地整数分解工具比factordb更灵活。5.2 搭建一个可复用的解题脚本框架我建议每个打CTF的人都维护一个自己的密码学解题脚本库。不需要多复杂但要把常用的函数封装好。比如# crypto_utils.py import gmpy2 from Crypto.Util.number import long_to_bytes, bytes_to_long def rsa_decrypt(c, d, n): m pow(c, d, n) return long_to_bytes(m) def rsa_encrypt(m, e, n): return pow(bytes_to_long(m), e, n) def gcd(a, b): return gmpy2.gcd(a, b) def egcd(a, b): return gmpy2.gcdext(a, b) def crt(remainders, moduli): # 中国剩余定理 pass这个框架的好处是下次遇到类似的题目直接调用函数就行不用重新写一遍。我自己的脚本库里还包含了常见的RSA攻击函数、异或分析函数、频率分析函数等。5.3 自动化与手工的平衡虽然工具很强大但我强烈建议不要完全依赖自动化。原因有两个第一CTF题目经常有变种自动化工具不一定能识别第二手工分析的过程能帮你真正理解算法的弱点。我的做法是签到题和编码题直接用工具秒掉节省时间古典密码和现代密码题目先手工分析一遍确定攻击思路后再写脚本实现。这样既能保证速度又能保证学到东西。6. 常见问题与排查技巧实录6.1 解题卡住了怎么办卡住是CTF的常态。我遇到卡住的时候会按下面的顺序排查重新读题是不是漏看了题目描述里的提示是不是附件里还有隐藏文件检查中间结果把每一步的输出打印出来看看哪一步开始变得“不对劲”。换工具用CyberChef再跑一遍看看有没有遗漏的编码层。搜索特征把密文的前几个字符或者题目的关键参数拿去搜索看看有没有类似的题目。放一放如果卡了超过半小时先去做其他题回头再看往往会有新思路。6.2 常见报错与解决方法问题原因解决方法ValueError: invalid literal for int()输入字符串包含非数字字符检查是否有多余空格或换行gmpy2.invert报错模数不互素检查n和e是否互素或者是否选错了参数解密结果乱码解密方式错误或编码问题尝试不同的解码方式检查是否需要用long_to_bytes脚本运行超时暴力枚举范围太大优化算法或者检查是否有更简单的攻击方法factordb查不到n太大或太特殊尝试yafu或sage的factor()6.3 几个容易踩的坑坑一忽略大小写和空格。古典密码题目经常在大小写和空格上做文章。比如凯撒移位只移大写或者空格被替换成其他字符。解出来的结果如果“差一点”检查这些细节。坑二RSA的e和phi不互素。正常情况下e和phi(n)是互素的但有些题目会故意让它们不互素这时候需要特殊处理不能直接求d。坑三Base64解码后是二进制。有些题目Base64解码后得到的是二进制数据不是文本。这时候需要判断是不是图片、压缩包或者其他文件格式。我一般用file命令或者看文件头。坑四异或密钥长度判断错误。异或加密的密钥长度如果不确定可以尝试用汉明距离或者卡西斯基测试来估计。不要盲目假设密钥长度。提示CTF密码学题目做多了之后你会形成一种“直觉”——看到某种参数组合就知道大概考什么。这种直觉只能通过大量练习获得没有捷径。7. 从CTF到真实世界的思维迁移CTF密码学题目和真实密码学工程之间有一条明显的鸿沟但这条鸿沟并不是不可跨越的。我在实际工作中发现CTF训练出来的几种能力非常有价值第一是参数敏感度。做过大量RSA题目后你会对“小素数”“低指数”“重复模数”这些危险信号非常敏感。在代码审计时你能快速发现不安全的加密实现。第二是编码识别能力。CTF里经常需要处理各种编码嵌套这种能力在分析日志、处理数据时非常有用。第三是脚本化思维。CTF题目往往需要你快速写脚本验证想法这种“想到就写”的习惯能极大提升工作效率。当然也要清醒地认识到CTF的局限性。CTF题目为了可解性往往会简化问题忽略真实环境中的很多因素比如侧信道攻击、实现漏洞、协议交互。所以不要把CTF里的攻击方法直接套用到真实系统上而是要把它们当作理解密码学原理的练习。我个人在实际操作中的体会是CTF密码学题目最大的价值不在于“解出flag”而在于“理解为什么这个flag能被解出”。每做完一道题花几分钟想想“如果我是出题人我会怎么防止这种攻击”这种反向思考能帮你真正掌握密码学的核心逻辑。下次遇到类似的题目你就不需要从头分析了直接就能定位到关键点。