ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CTFshow CRYPTO1-3入门精讲:编码、古典密码与异或实战

CTFshow CRYPTO1-3入门精讲:编码、古典密码与异或实战 1. 从三道入门密码题说起为什么CRYPTO1、2、3值得反复琢磨CTFshow的CRYPTO系列前三题在圈子里基本算是密码学方向的“新手村三件套”。很多人第一次接触CTF的Crypto模块就是从这三道题开始的。它们看起来简单甚至有人扫一眼就过了但我带过不少新人之后发现真正能把这三道题吃透的人并不多。大部分人只是把flag提交上去就完事了至于题目背后考的是什么、为什么这么设计、下次遇到变种能不能自己推出来基本没想过。这篇文章面向的是刚入门CTF密码学方向、或者刷到CTFshow CRYPTO1、2、3但没完全搞明白的读者。我会把这三道题从题目形式、考察点、解题思路到代码实现完整拆一遍同时补充一些原题里没写、但实际比赛中经常遇到的变种和坑。读完你至少能做到两件事第一遇到同类编码或简单加密能自己动手解第二知道密码学入门阶段该补哪些基础知识而不是盲目刷题。需要提前说明的是CTFshow的CRYPTO1、2、3在不同时期题目内容可能略有调整但核心考察方向基本稳定CRYPTO1通常是编码转换类CRYPTO2涉及简单的古典密码或字符移位CRYPTO3则往往引入基础的数学运算或异或操作。我下面会按照“通用题型具体解法”的方式来写即使你看到的题目细节和我描述的有出入思路也是通用的。2. 三道题的整体设计逻辑与考察意图2.1 为什么入门题都爱用编码和古典密码CTF密码学方向有一个很明显的梯度设计入门题几乎不会一上来就让你做RSA或椭圆曲线而是先用编码转换和古典密码把你“骗进来”。原因很简单编码和古典密码的门槛低不需要太多数学基础但又能培养几个关键习惯观察字符集、识别特征、动手写脚本。CRYPTO1、2、3就是典型的梯度设计。第一题让你意识到“看到一串奇怪字符先想编码”第二题让你意识到“字母可以被替换或移位”第三题让你意识到“异或和简单数学运算也能藏信息”。这三步走完你才算真正摸到了密码学的门。从出题人的角度看这三道题还有一个隐藏目的逼你装环境、逼你写代码。很多人一开始想用在线工具糊弄过去但稍微复杂一点的题在线工具就不好使了你必须学会用Python或者CyberChef。这个习惯越早养成越好。2.2 三道题之间的能力递进关系我把这三道题的能力要求拆成三个层次题目核心考察点能力层次常见工具CRYPTO1编码识别与转换观察与工具使用CyberChef、PythonCRYPTO2古典密码/字符移位模式识别与暴力尝试Python脚本CRYPTO3异或/基础数学运算逻辑推理与代码实现Python、手动计算这个递进关系很重要。如果你CRYPTO1做得磕磕绊绊说明你对编码体系不熟如果CRYPTO2卡住了说明你还没养成写脚本遍历的习惯如果CRYPTO3做不出来说明你对位运算和进制转换还不够敏感。每一道题都在暴露一个具体的短板这比单纯拿flag有价值得多。2.3 解题时最容易走偏的两个方向我带新人的时候发现两个高频误区。第一个是“过度依赖在线工具”看到Base64就丢到在线解码网站看到一串十六进制就找在线转换结果遇到多层编码或者需要微调的地方就懵了。第二个是“不写注释不保存脚本”解完一题脚本就丢了下次遇到类似题目又从头写。正确的做法是从CRYPTO1开始就建一个自己的crypto_tools.py把常用的编码解码、进制转换、异或函数都写进去。后面每做一题就往里面加函数。这样刷到CRYPTO10的时候你手里已经有一套顺手的工具库了。3. CRYPTO1编码识别与转换的实战拆解3.1 题目常见形式与第一眼观察CRYPTO1最常见的形态是给你一串看起来“像某种编码”的字符串。可能是Base64那种带等号和大小写字母的也可能是纯十六进制的还可能是URL编码那种带百分号的。题目通常不会告诉你这是什么编码就是要你自己判断。第一眼观察的时候我一般按这个顺序看字符集范围只有0-9和a-f大概率是十六进制。有没有等号结尾有等号且包含大小写字母和数字先试Base64。有没有百分号有%xx形式URL编码。全是0和1二进制先转ASCII。看起来像乱码但长度是4的倍数可能是Base64变种或多次编码。这个观察顺序不是绝对的但能覆盖大部分入门题。关键是不要一上来就瞎试先看字符集缩小范围。3.2 Base64编码的手动解码原理很多人用Base64解码就是丢给工具但如果你不懂它的原理遇到变种或者需要手动处理的时候就抓瞎。Base64的核心逻辑其实很简单把每3个字节24位重新分成4组每组6位然后查表换成可打印字符。举个例子字符串“Man”的ASCII是77、97、110二进制是01001101、01100001、01101110拼起来是010011010110000101101110。按6位切分010011、010110、000101、101110对应十进制19、22、5、46查Base64表得到T、W、F、u所以“Man”编码后是“TWFu”。理解了这个过程你就能明白为什么Base64编码后的长度总是4的倍数为什么有时候末尾有等号。等号是填充用的因为原始数据长度不一定是3的倍数。注意CTF里经常出现“Base64变种”比如把标准表换掉或者把和/换成-和_。遇到解不出来的时候先检查字符集是不是标准Base64的字符集。3.3 用Python实现通用编码识别脚本与其每次手动判断不如写一个简单的识别脚本。下面这个脚本可以帮你快速判断一串字符可能是什么编码import base64 import binascii import urllib.parse def detect_encoding(s): results [] # 检查是否是十六进制 try: if all(c in 0123456789abcdefABCDEF for c in s) and len(s) % 2 0: decoded bytes.fromhex(s) results.append((hex, decoded)) except: pass # 检查是否是Base64 try: decoded base64.b64decode(s * (-len(s) % 4)) results.append((base64, decoded)) except: pass # 检查是否是URL编码 if % in s: decoded urllib.parse.unquote(s) results.append((url, decoded.encode())) # 检查是否是二进制 if all(c in 01 for c in s) and len(s) % 8 0: decoded bytes(int(s[i:i8], 2) for i in range(0, len(s), 8)) results.append((binary, decoded)) return results # 使用示例 test_str flag{test} for name, result in detect_encoding(test_str): print(f{name}: {result})这个脚本不是万能的但它能帮你快速排除掉不可能的编码。实际做题的时候你可以把题目给的字符串丢进去看哪个结果像可读文本。3.4 多层编码的剥洋葱策略CRYPTO1有时候会套两层甚至三层编码。比如先Base64再十六进制再URL编码。这时候你需要一层一层剥。我的习惯是每剥一层就看看结果像什么如果还是乱码就继续剥。剥洋葱的时候有几个信号要注意剥完一层后出现“flag{”或者“ctfshow{”字样说明快到了。剥完一层后全是可打印字符但读不懂可能是古典密码不是编码。剥完一层后出现大量百分号说明还有URL编码。剥完一层后长度变成原来的一半左右可能是十六进制转字节。我一般会在脚本里写一个循环自动尝试常见的编码组合但手动剥的时候更有感觉因为你能看到每一步的变化。3.5 实操心得别小看编码题的基本功很多人觉得编码题太简单不屑于练。但我实际打比赛的经验是编码题的手速直接决定你前期能不能快速拿分。一场比赛里编码题往往是最先被秒掉的如果你能在别人还在找在线工具的时候就用脚本解出来就能省下时间去做后面的难题。我自己的做法是把常用的编码解码函数都封装好放在一个文件里比赛的时候直接import。下面是我常用的几个函数def b64_decode(s): return base64.b64decode(s * (-len(s) % 4)) def hex_decode(s): return bytes.fromhex(s) def url_decode(s): return urllib.parse.unquote(s).encode() def binary_decode(s): return bytes(int(s[i:i8], 2) for i in range(0, len(s), 8)) def auto_decode(s, max_layers5): for _ in range(max_layers): for func in [b64_decode, hex_decode, url_decode, binary_decode]: try: result func(s) if result ! s.encode() if isinstance(s, str) else result ! s: s result break except: continue else: break return s这个auto_decode不一定每次都对但能帮你快速试出大部分多层编码。4. CRYPTO2古典密码与字符移位的破解思路4.1 从凯撒密码到维吉尼亚识别古典密码的特征CRYPTO2通常考的是古典密码最常见的是凯撒密码和它的变种。凯撒密码就是把每个字母往后移固定的位数比如移3位a变成db变成e。识别凯撒密码的特征是字母频率分布和正常英文很像但单词读不懂。如果移位不固定而是按照某个关键词来移那就是维吉尼亚密码。维吉尼亚的识别特征是字母频率被“抹平”了因为不同位置的移位量不同。入门题里维吉尼亚出现得少但凯撒的变种很多比如移位数不固定、只移元音、大小写分别处理等等。还有一种常见的是“栅栏密码”把明文按固定行数写成几行然后按行读出。栅栏密码的特征是字母顺序被打乱但字母本身没变。如果你看到一串字母字母组成和正常英文一样但顺序不对先试栅栏。4.2 凯撒密码的暴力破解与频率分析凯撒密码只有25种可能的移位移0位不算所以最直接的解法就是暴力遍历。下面这个脚本可以列出所有26种移位结果def caesar_bruteforce(ciphertext): for shift in range(26): result for c in ciphertext: if c.isalpha(): base ord(A) if c.isupper() else ord(a) result chr((ord(c) - base - shift) % 26 base) else: result c print(fshift {shift}: {result}) # 使用示例 caesar_bruteforce(Wklv lv d whvw)跑出来之后你一眼就能看出哪个是可读的英文。如果题目里的明文不是英文而是flag格式那就找哪个结果包含“flag”或“ctfshow”。频率分析是更高级的方法适合处理移位不固定的情况。英文里字母e出现频率最高大约12.7%其次是t、a、o、i、n。如果你统计密文里出现最多的字母它很可能对应e这样就能推算出移位量。4.3 栅栏密码的手动还原与脚本实现栅栏密码的解法取决于加密时的行数。假设明文是“flagishere”行数是2加密时写成两行f a g s e e l g i h r然后按行读出“fagsee”“lgihr”“fagseelgihr”。解密的时候你知道密文长度是10行数是2所以前5个字符是第一行后5个是第二行然后交替读取。脚本实现如下def rail_fence_decode(ciphertext, rails): n len(ciphertext) # 计算每行有多少字符 pattern [] rail 0 direction 1 for i in range(n): pattern.append(rail) rail direction if rail 0 or rail rails - 1: direction * -1 # 统计每行字符数 rail_counts [pattern.count(r) for r in range(rails)] # 按行切分密文 rail_chars [] idx 0 for count in rail_counts: rail_chars.append(list(ciphertext[idx:idxcount])) idx count # 按pattern还原 result [] rail_idx [0] * rails for r in pattern: result.append(rail_chars[r][rail_idx[r]]) rail_idx[r] 1 return .join(result) # 使用示例 print(rail_fence_decode(fagseelgihr, 2))这个脚本的关键是先算出每个位置属于哪一行然后按行切分密文最后按原顺序还原。4.4 实操心得古典密码题的手工与脚本平衡古典密码题有一个特点手工能做但容易出错脚本快但写脚本需要时间。我的经验是简单的凯撒直接手工试几个移位就行复杂的栅栏或者维吉尼亚一定要写脚本。另外古典密码题经常和编码题混在一起考。比如先凯撒移位再Base64编码。这时候你需要先剥编码再解古典密码。顺序反了就会卡住。提示如果解出来是一串看起来像Base64的字符但解码后还是乱码试试先解古典密码再解码。这个顺序问题在CTF里很常见。5. CRYPTO3异或运算与基础数学的代码实现5.1 异或运算的本质与CTF中的常见用法异或XOR是CTF密码学里出现频率最高的位运算。它的规则很简单相同为0不同为1。用符号^表示。异或有一个很重要的性质a ^ b ^ b a。也就是说如果你用同一个密钥异或两次就能还原原始数据。CTF里常见的异或题有三种第一种是单字节异或整个明文用一个字节异或。解法就是遍历0-255看哪个结果像flag。第二种是多字节异或密钥是一个短字符串循环使用。解法需要先猜密钥长度再逐字节破解。第三种是异或后转成其他编码比如异或后再Base64。这种需要先解码再异或。5.2 单字节异或的暴力破解脚本单字节异或的破解脚本很直接def xor_bruteforce(ciphertext): for key in range(256): result bytes(c ^ key for c in ciphertext) # 检查结果是否大部分是可打印字符 printable sum(1 for c in result if 32 c 127) if printable / len(result) 0.9: print(fkey {key}: {result}) # 使用示例 cipher bytes.fromhex(1c1d1e1f) xor_bruteforce(cipher)这个脚本会打印出所有可打印字符比例超过90%的结果。实际做题的时候你还可以加上“包含flag字样”的过滤条件。5.3 多字节异或的密钥长度推断与逐字节破解多字节异或稍微复杂一点。假设密钥是“key”长度3那么明文第0、3、6...字节都用k异或第1、4、7...字节都用e异或以此类推。破解步骤是猜密钥长度。可以用汉明距离法也可以直接试1到20的长度。对每个位置把所有用同一个密钥字节异或的密文字节收集起来做单字节异或暴力破解。组合所有位置的密钥字节得到完整密钥。汉明距离法的原理是正常英文文本的相邻字节汉明距离平均值大约是2.5到3.5如果按某个长度分组后组内平均汉明距离最小那个长度就可能是密钥长度。def hamming_distance(a, b): return sum(bin(x ^ y).count(1) for x, y in zip(a, b)) def guess_key_length(ciphertext, max_len20): best_lengths [] for length in range(1, max_len 1): chunks [ciphertext[i:ilength] for i in range(0, len(ciphertext), length)] chunks [c for c in chunks if len(c) length] if len(chunks) 2: continue distances [] for i in range(len(chunks) - 1): distances.append(hamming_distance(chunks[i], chunks[i1]) / length) avg_dist sum(distances) / len(distances) best_lengths.append((avg_dist, length)) best_lengths.sort() return best_lengths[:5] # 使用示例 cipher bytes.fromhex(...) print(guess_key_length(cipher))得到候选长度后对每个长度做逐字节破解看哪个长度能解出可读文本。5.4 实操心得异或题的常见坑与调试技巧异或题最大的坑是“密钥长度猜错”。如果长度猜错了后面逐字节破解出来的结果就是乱的。我的经验是不要只信汉明距离法多试几个候选长度手动看看哪个结果像英文。另一个坑是“异或后还有编码”。比如异或完得到的是Base64字符串你需要再解码一次。所以解异或题的时候每解一步都要看看结果像什么。还有一个技巧如果明文是flag格式你可以利用已知明文攻击。比如你知道明文开头是“flag{”那么密钥的前5个字节就是密文前5个字节异或“flag{”。这个方法在多字节异或里特别好用。known_plaintext bflag{ cipher bytes.fromhex(...) key_part bytes(c ^ p for c, p in zip(cipher[:len(known_plaintext)], known_plaintext)) print(fkey starts with: {key_part})5.5 从CRYPTO3延伸进制转换与数学运算题CRYPTO3有时候也会考进制转换比如给你一个十进制大数让你转成十六进制再转ASCII。或者给你一个数学表达式让你计算结果再转字符。这类题的解法就是老老实实写代码# 十进制转ASCII num 123456789 hex_str hex(num)[2:] if len(hex_str) % 2: hex_str 0 hex_str print(bytes.fromhex(hex_str)) # 或者直接 print(num.to_bytes((num.bit_length() 7) // 8, big))数学运算题常见的有给一个公式让你算结果或者给一串数字让你找规律。这类题没有固定解法关键是耐心和细心。6. 常见问题与排查技巧实录6.1 解出来是乱码怎么办乱码是CTF密码学里最常见的问题。遇到乱码按这个顺序排查现象可能原因解决方法全是不可打印字符编码方式判断错误换一种编码试试部分可打印部分乱码密钥错误或长度错误重新检查密钥可打印但读不懂还有一层古典密码试凯撒、栅栏等出现大量重复字符可能解错了方向回到题目重新观察长度不对填充或截断问题检查等号和长度我自己的习惯是每解一步就把结果保存下来用不同的文件名区分。这样如果后面发现方向错了可以快速回到某一步重新试。6.2 脚本跑不通的排查清单脚本跑不通通常有几个原因编码问题Python3里bytes和str不能直接混用该decode就decode该encode就encode。长度问题Base64解码要求长度是4的倍数不够要补等号。异常处理有些解码函数遇到非法字符会抛异常要用try-except包起来。密钥长度异或题密钥长度不对后面全错。我一般会在脚本里加很多print把中间结果打出来看。这样虽然看起来笨但调试效率最高。6.3 独家避坑技巧建立自己的解题模板刷CTFshow CRYPTO系列的时候我建议你从一开始就建一个模板文件把每道题的解题脚本都保存下来加上注释。比如# crypto1_base64.py # 题目CTFshow CRYPTO1 # 考点Base64多层编码 # 解法先Base64解码再十六进制转字节 import base64 cipher ... step1 base64.b64decode(cipher) step2 bytes.fromhex(step1.decode()) print(step2)这样积累下来到后面做难题的时候你可以直接翻之前的脚本改一改就能用。这比每次从头写快得多。6.4 从这三道题看密码学入门的学习路径做完CRYPTO1、2、3之后你应该已经掌握了编码识别、古典密码破解、异或运算。接下来可以按这个路径继续先把Python的bytes和str操作练熟这是基础中的基础。学一点数论基础为后面的RSA做准备。了解常见的哈希算法和它们的特征。开始接触简单的RSA题从已知p、q求d开始。CTFshow的CRYPTO系列题目梯度设计得很好前三题是打基础后面会逐渐引入RSA、AES、椭圆曲线等内容。不要急着往后刷先把前三题涉及的每个知识点都动手实现一遍。我个人在实际操作中的体会是密码学题最忌讳“看答案会了自己写就废”。每道题都要自己从头写脚本哪怕写得丑、写得慢也比复制粘贴强。我见过太多人刷了几十道题遇到新题还是不会就是因为一直在“看”而不是在“写”。最后分享一个小技巧如果你在某道题上卡了很久不妨把题目放一放去把前面做过的题重新用不同的方法解一遍。很多时候卡住你的不是新题太难而是旧题里的某个知识点你没真正掌握。
返回列表