资讯动态

CTF古典密码实战:Base64+栅栏+凯撒叠加解密全流程

发布时间:2026/10/5 2:54:55 来源:尧图企业网站定制
第二届春秋欢乐赛的下午我点开classical这道题附件只有一个txt文件里面躺着一行36个字符的字符串。说实话看到题名叫classical我就大概知道方向了——古典密码。但真正动手之前我还是按老规矩先做信息收集因为古典密码题最怕的不是不会解而是解到一半发现自己认错了加密方式。classical这道题对刚接触CTF的选手来说是一道很好的编码识别 古典密码叠加入门题对老手它更像一道顺手刷分的开胃菜。这篇writeup我尽量按自己真实的解题思路来写包括中间走岔的路和最后总结的经验希望能给卡在类似题目上的朋友一些参考。1. 拿题先别急classical第一步是信息收集不是猜答案1.1 题目描述与附件形态打开题目网页上写着简短的题目描述大意是古典密码相关flag格式为flag{...}同时提供一个下载附件。附件下载下来是一个cipher.txt文件。用cat查看内容得到一行字符串aWR7bzVsX3VzMGxfeG9qZmQ1ZmZid192aXEA这里我想先强调一个习惯很多新手拿到这种字符串第一反应是直接扔进某个在线解密网站点自动检测结果出来一堆乱七八糟的东西或者压根没结果。我个人的习惯是先在本地做两件事看文件类型、看字符串形态。这两件事的成本不到一分钟但能筛掉至少一半的错误方向。1.2 file命令与形态观察$ file cipher.txt cipher.txt: ASCII text $ wc -c cipher.txt 36 cipher.txtfile命令告诉我们这是纯文本没有隐藏的二进制数据不是图片隐写、不是压缩包伪装。wc -c显示36字节也就是那行字符串本身的长度。这里有个小技巧如果一个文件大小正好等于你看到的字符串长度说明附件内容就是这行文本本身没有在末尾藏其他数据。接下来是关键——观察字符串形态。aWR7bzVsX3VzMGxfeG9qZmQ1ZmZid192aXEA这个串有非常明显的特征字符集只包含A-Z、a-z、0-9末尾带两个等号。这种字符组成几乎是Base64的身份证。1.3 为什么看起来像什么比它是什么更重要这里我多说一句。做古典密码题第一层往往是编码而不是加密。编码和加密的区别在于编码有标准的、确定的映射关系关键在于识别出是哪一种编码加密则通常有密钥或偏移关键在于还原变换规则。Base64、Base32、Hex、URL编码都属于编码凯撒、维吉尼亚、栅栏、仿射才属于古典密码。出题人把Base64放在第一层不是为了考你Base64本身而是为了给后面的古典密码套一个壳。所以在拿到题目时先判断字符集和长度规律比直接猜答案高效得多。如果字符串里出现、且长度是4的倍数那Base64是最高优先级怀疑对象如果全是十六进制字符且长度为偶数优先怀疑Hex如果字符集里出现大量2和3以外的数字组合可以考虑Base32。这些判断都不需要工具肉眼扫一眼就够了。2. Base64这层壳解码之前先搞懂它的规则2.1 Base64的字符集与padding规则密码题里最常用的编码就是Base64。它本质上是用64个可打印字符来表示任意二进制数据。64 2^6所以每6个比特变成一个字符每3个字节24比特变成4个Base64字符。如果原文长度不是3的倍数就用补足。因此完整Base64串的长度一定是4的倍数末尾可能有1到2个。拿我手上的串举例子aWR7bzVsX3VzMGxfeG9qZmQ1ZmZid192aXEA36个字符是4的倍数末尾2个说明原始数据长度除以3余1。这些信息在解码之前就能获得。2.2 命令行解码我习惯用Linux自带的base64命令或者Python一行搞定$ printf %s aWR7bzVsX3VzMGxfeG9qZmQ1ZmZid192aXEA | base64 -d id{o5l_us0l_xojfd5ffbw_viq注意这里我用printf而不是echo因为echo默认会在末尾加一个换行符base64 -d对多出的换行符有时会报错printf %s则不会有这个问题。这个细节我第一次实战时没注意卡了好一会儿后来才养成用printf的习惯。Python版本更直观import base64 s aWR7bzVsX3VzMGxfeG9qZmQ1ZmZid192aXEA print(base64.b64decode(s).decode())输出同样是那一行id{o5l_us0l_xojfd5ffbw_viq2.3 解码结果的第一印象看到这行字符串我第一反应是有{有}说明flag的外壳已经出现了但里面还是一坨乱码。字符串由字母、数字、下划线组成没有多余特殊符号一共26个字符。这种外壳可见、内容不可读的状态基本可以确定后面还有一层古典密码等着。这里要提醒新手Base64解完不等于题目做完。Base64在CTF里更多是打包工具的角色它的作用是让你看不见原文而不是给你制造真正的加密困难。真正的题目核心永远在解码之后的那个字符串里。那么id{o5l_us0l_xojfd5ffbw_viq到底用了什么古典密码我先试了凯撒爆破又试了栅栏还原。这个过程挺有意思下一节详细说。3. 凯撒爆破交卷失败问题出在字符的排列顺序上3.1 为什么我第一个想到凯撒凯撒密码是整个古典密码里最基础、最常考的一种几乎所有classical题都会带一点凯撒的影子。凯撒的特点是字母按字母表整体平移固定位数密文和原文的每个字符位置一一对应不会打乱顺序。对id{o5l_us0l_xojfd5ffbw_viq来说它保留了完整的flag{...}外壳特征左花括号在第三个字符的位置右花括号靠近末尾。既然花括号没动只是里面的字母乱了那第一直觉就是字母替换类密码而凯撒是这类密码里最容易验证的。3.2 爆破脚本与输出分析我写了一个爆破脚本把所有26个偏移量都打出来s id{o5l_us0l_xojfd5ffbw_viq for shift in range(26): out [] for ch in s: if ch.isalpha(): base ord(A) if ch.isupper() else ord(a) out.append(chr((ord(ch) - base - shift) % 26 base)) else: out.append(ch) print(fshift {shift:2d}: {.join(out)})这里有一个约定需要说明数字、花括号、下划线不参与凯撒位移只有字母发生偏移。这也是多数CTF古典密码题的默认规则因为如果数字也偏移合法的flag字符集会变得非常不可控。跑完脚本我把输出往下翻其中几个偏移量是这样的shift 0: id{o5l_us0l_xojfd5ffbw_viq shift 1: hc{n5k_tr5k_wniec5eeav_uhp shift 3: fal{li_rp0i_ulgca5ccyt_sfn shift 13: vq{b5y_hf5y_kbws5ssoj_ivd没有一行看起来像人话。凯撒这条路直接堵死。3.3 转向栅栏从前后两半交叉想到的既然字母替换不行我开始考虑置换类密码。置换类密码和替换类的最大区别是它不改变字符本身只改变字符的位置。最典型的就是栅栏密码Rail Fence。怎么判断是不是栅栏有一个很实用的观察方法看字符串能不能被对半拆开再交错拼回。我把id{o5l_us0l_xojfd5ffbw_viq从中间劈成两半前半id{o5l_us0l_x 后半ojfd5ffbw_viq然后尝试把两半逐字符交错合并i o d j { f o d 5 5 l f _ f u b s w 0 _ l v _ i x q拼出来是iodj{fod55lf_fubsw0_lv_ixq。注意看这个串的后半段_fubsw0_lv_ixq里的字母开始出现连续的英文感了iodj也恰好是某个单词往字母表前移3位的样子。这强烈暗示先做栅栏再做凯撒就是正确的解密顺序。这里有一个经验值得记下来替换类密码保留原始字符顺序所以明文中相邻的字母在密文里仍然相邻置换类密码会把相邻关系打散。如果你发现一个密文里常见字母组合完全对不上、但字符集干净那就要怀疑是置换类栅栏、列移位都属于这一类。4. 亲手还原栅栏凯撒偏移再补一刀4.1 栅栏2栏的解密原理2栏栅栏密码的加密过程可以这样理解把明文字符串按照交替写入两行的方式排成两行然后把两行按顺序拼接起来。以iodj{fod55lf_fubsw0_lv_ixq为例加密时一行取偶数位字符另一行取奇数位字符偶数位i d { o 5 l _ u s 0 l _ x 奇数位o j f d 5 f f b w _ v i q 拼接 id{o5l_us0l_xojfd5ffbw_viq所以解密就是把密文重新拆成两半再按照前一半取一个、后一半取一个的方式交错合并。用Python写就是s id{o5l_us0l_xojfd5ffbw_viq half len(s) // 2 a, b s[:half], s[half:] out [] for x, y in zip(a, b): out.extend([x, y]) print(.join(out))运行结果iodj{fod55lf_fubsw0_lv_ixq这个还原动作很机械但它是整道题的转折点。第一次拼出这个串的时候我基本已经确定题目快解完了因为它看起来就是一段凯撒加密后的英文句子。4.2 符号不参与位移解密时花括号、数字、下划线都不动拿到iodj{fod55lf_fubsw0_lv_ixq之后再跑一次凯撒爆破。同样的脚本只需要把输入换成这个串。所有26个候选里shift 3那一行值得注意shift 3: flag{cla55ic_crypt0_is_fun}逐字母验证一下i往前移3位是fo移3位是ld移3位是aj移3位是g{保持不动f移3位是co移3位是ld移3位是a数字5保持不动后面一路推下去_fubsw0_lv_ixq 对应 _crypt0_is_fun。整句话干净利落。这里有个重要的约定在做凯撒爆破时非字母字符一律保留。如果哪个脚本把数字、花括号、下划线也做了位移那结果一定不对。我在实战中见过有人用在线工具时没有取消数字也位移的选项结果解出来全是怪字符浪费了不少时间。4.3 最终flag的确认最终得到的flag是flag{cla55ic_crypt0_is_fun}注意中间的cla55ic不是拼写错误而是把s替换成了数字5这是CTF题目里很常见的leet风格。理解了这一层整个flag的含义就是classic crypto is fun和题名classical完全呼应。到这里核心解题链已经完整了Base64解码把外层壳剥掉栅栏2解密把被打乱的字符顺序还原凯撒偏移把每个字母往前移3位加密顺序是凯撒 → 栅栏 → Base64所以解密顺序必须反过来是Base64 → 栅栏 → 凯撒。这个顺序逻辑是叠加密码题最重要的通用解法。5. 两种解题姿势CyberChef流程化与Python脚本化5.1 一条Recipe快速出结果如果你不打算写脚本CyberChef可以一条流程解决。把初始密文粘贴进Input然后在Recipe里依次添加三个操作From Base64Rail Fence Cipher DecodeKey填2Caesar Brute ForceOutput区域会列出25个候选结果找到flag{...}那一条即可。这里有一个需要留意的坑CyberChef里的Rail Fence Cipher实现默认的偏移和变体定义跟部分教材不一样。有些版本的栅栏默认从第0行开始有些版本把加密时按行读取的方式定义得不同。如果按Key2解出来不对别急着怀疑题目先试试把密文直接对半拆开、手动交叉拼一下或者调整操作里的offset参数。我的经验是遇到工具结果和预期不符先用最简单的脚本验证核心逻辑再回头调工具参数。5.2 可复用的Python工具函数如果你希望以后遇到类似题目能直接复用我建议把加解密逻辑写成通用函数。这里给出一套完整代码支持任意栏数的栅栏解密和凯撒爆破import base64 def rail_fence_decrypt(s, rails): n len(s) lens [0] * rails cur, step 0, 1 for _ in range(n): lens[cur] 1 cur step if cur in (0, rails - 1): step -step rows, pos [], 0 for L in lens: rows.append(s[pos:pos L]) pos L res, idx [], [0] * rails cur, step 0, 1 for _ in range(n): res.append(rows[cur][idx[cur]]) idx[cur] 1 cur step if cur in (0, rails - 1): step -step return .join(res) def caesar_brute(s): for shift in range(26): out [] for ch in s: if ch.isalpha(): base ord(A) if ch.isupper() else ord(a) out.append(chr((ord(ch) - base - shift) % 26 base)) else: out.append(ch) print(fshift {shift:2d}: {.join(out)}) raw aWR7bzVsX3VzMGxfeG9qZmQ1ZmZid192aXEA step1 base64.b64decode(raw).decode() step2 rail_fence_decrypt(step1, 2) caesar_brute(step2)这个栅栏解密函数的核心思想是模拟走位先计算出每一行最终有多少个字符把密文按行宽切开再按照zigzag路径逐行取字符回填。rails2时它等价于对半拆开、交错合并rails3、4时也能直接通用。5.3 我的建议什么时候用工具什么时候写脚本实战里我的习惯是如果只是一道签到题CyberChef最快拉三个操作几秒钟出结果如果题目要反复试不同的参数或者你要在解题过程中验证自己的判断写脚本更踏实。刷CTF不是为了背工具链而是为了理解每一步在做什么。用CyberChef解出来之后我仍然会手写一遍脚本因为这个过程能帮你建立字符集识别 → 编码解码 → 变换识别 → 逆推还原的完整思维链。下次遇到更复杂的叠加题你就知道该在哪一步停下来观察而不是盲目套工具。6. 复盘classical这道题真正在考什么6.1 考点拆解这道题表面上是Base64 栅栏 凯撒的简单叠加但每个环节都埋了一个判断点Base64识别看字符集、看号、看长度是4的倍数栅栏识别发现字母替换不管用转而观察顺序是否被打散凯撒偏移确认非字母不参与位移爆破后靠肉眼识别英文叠加顺序加密顺序是凯撒→栅栏→Base64解密必须完全逆序最核心的考点其实是在不确定加密方式时如何一步步排除错误方向。这道题没有给你任何提示加密步骤一切要自己推断。6.2 古典密码叠加题的通用解题SOP经过这次实战我给自己整理了一套古典密码叠加题的解题流程先剥编码外壳识别Base64、Hex、URL编码、Base32等用最安全的方式解码检查输出是否为flag格式如果是直接交flag如果不是记录输出形态判断是替换类还是置换类替换类保留字符顺序置换类打乱字符顺序替换类优先试凯撒、ROT13、仿射等置换类优先试栅栏、列移位如果一层解完仍然不是明文继续重复第3步如果解到某一步变得更乱立刻回退很可能是解密顺序不对这套流程不复杂但能避免大多数瞎猜。6.3 实战失误与时间线复盘我这次解题的完整时间线大概是前5分钟下载附件用file命令确认文件类型用肉眼识别出Base64第5到15分钟解出Base64后直接尝试凯撒爆破发现所有输出都不像英文卡住了第15到20分钟意识到应该观察字符排列尝试对半拆分并交错合并得到中间结果第20到25分钟对中间结果做凯撒爆破拿到flag提交成功最大的失误在于前15分钟太依赖凯撒万能的想法没有第一时间注意到26个字符的串可以被对半拆分。如果我早一点从字符排列角度入手应该能省掉10分钟。另外还有一个教训在线工具虽然方便但某些工具对栅栏密码的变体定义不一样。遇到工具解不出时不要怀疑题目错了先自己写一个几行的脚本验证逻辑通常问题都出在工具参数上。最后再分享一个小技巧做这类题目时准备好一个包含Base64解码、栅栏解密、凯撒爆破的本地脚本模板能极大提升刷题效率。后续遇到类似的古典密码叠加题直接改输入、改参数几分钟就能出结果。classical这道题虽然不难但把整个流程走一遍你对编码、替换、置换这三类基本操作的区别会清晰很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑