ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CTF密码学入门:Base64、ROT13与Atbash组合解码实战指南

CTF密码学入门:Base64、ROT13与Atbash组合解码实战指南 开篇先聊点实在的。CTF里密码学这一块很多人一上来就抱着RSA、AES、ECC这类“硬核算法”啃结果越学越懵。实际上在入门和进阶阶段真正决定你能不能拿分的反而是一些看起来平平无奇、甚至谈不上“加密”的编码技巧——Base64、ROT13、Atbash就是其中出场率最高的三兄弟。它们单个拎出来都不难但一旦被出题人组合起来、再塞点流量分析或者图片隐写的壳就能让一批人卡到比赛结束。我见过不少新手拿到一个以结尾的字符串下意识就去解Base64解出来一坨乱码就不知道怎么办了。其实思路完全错了在CTF的密码题里Base64通常只是“最外层包装”真正的关键在解出来之后那串看起来像乱码、实际上是有规律替换的文本。这时候如果能想到ROT13或Atbash再套一层就能出flag。这背后的核心能力是“能认出编码/密码类型并且知道它们之间怎么嵌套”。这篇文章我就把这些组合玩的细节、识别特征、实操顺序和踩坑点一次讲透。1. 内容整体设计与思路拆解1.1 为什么是Base64、ROT13、Atbash这三兄弟先说个很多人忽略的事实Base64严格来说不是加密是编码ROT13和Atbash严格来说也不是现代加密属于古典替换密码。但在CTF题目里出题人根本不在乎这个学术边界。他们在乎的是这些变换足够常见、足够容易被自动化处理、又足够能迷惑人。Base64的最大特点是能把任意二进制数据变成一串可见字符这在数据传输和存储里太常用以至于题目里到处都是它的影子。一个图片、一段音频、一个压缩包经常以data:image/...;base64,开头混进流量包或网页源码里。你要是没见过这种格式走都不知道往哪走。ROT13则是凯撒密码的一个特例移位数为13。为什么是13因为英文字母一共26个移13位再移13位就回到原点加密和解密直接用同一个函数就能实现这在程序里写起来特别顺手。它保留大小写、空格、数字和标点只替换字母所以解出来的文本一眼能看出“好像有某种规律但读不通”。Atbash是另一种古典替换规则是字母表前后颠倒a变成zb变成y依次类推。它比ROT13更隐蔽一点因为不是平移而是镜像替换很多人第一反应看不出来。但它的特征也很明显空格和标点全部保留字母被彻底替换成另一套字母。1.2 CTF出题人最爱怎么组合它们这三兄弟在题目里的组合方式我总结下来基本是四种套路。第一种是“单层直出”比如给你一个Base64字符串解出来直接是flag这种是送分题。第二种是“Base64 ROT13/Atbash”双层嵌套解完Base64后还要再做一次字母替换这是今天文章的重点。第三种是多层编码轮询比如Base64解码后是ROT13ROT13后又是Base64来回套好几层有的题目甚至会故意用脚本生成十层八层的嵌套来恶心人。第四种是“藏在格式里”比如一个PNG图片的base64数据流解出图片后还要再用binwalk或strings继续挖最后才在某个角落找到经过Atbash处理的明文。理解出题人这种“嵌套”思维特别重要。因为单纯考某一种编码考察的只是“你知不知道这个公式”而嵌套组合考察的是“你能不能识别出每一层的特征并选择合适的解码顺序”。后者才是CTF真正想练出来的能力。1.3 为什么宁可先学识别、再学算法我见过很多新手疯狂背Base64对照表、背ROT13移位表结果遇到新题照样懵。原因很简单做密码题的第一步不是解密而是识别。拿到一串密文你应该先问三个问题这是什么字符集里面有没有特殊符号字母的分布和空格的保留情况什么样根据这些线索判断出“可能是什么编码”然后再选工具去解。这个过程有点像医生看病先看症状再开药而不是把全部药都吃一遍指望能好。所以这篇文章的实操部分我不会只丢给你几个命令而是会带着你把识别特征、解码顺序、工具链选择整个过一遍。学完之后你会发现那些看起来花里胡哨的组合题本质上都是同一套思维模型在不同场景下的重复应用。2. 核心细节解析与实操要点2.1 一眼认出Base64不只是看等号很多人判断Base64的唯一标准就是“末尾有没有等号”。这个标准太粗糙了。标准的Base64用A-Z、a-z、0-9、、/共64个字符来表示数据用来做填充。但CTF里还有URL-safe变体把和/换成了-和_这种就没有等号或者等号很少。还有MIME格式的Base64会按每76个字符插入一个换行符你直接把整段拿去解会带进换行符导致报错。更实用的判断方法是看字符集合。如果一段可见文本只包含上述64个字符加上而且长度是4的倍数或者接近4的倍数那基本就可以往Base64的方向试。如果里面有大量和/那更是铁板钉钉的事。注意只可能出现在末尾最多两个不可能出现在字符串中间。如果看到等号在中间肯定不是正常的Base64需要先处理一下。还有一个容易踩的坑有些题目里的Base64字符串是经过反向或字符替换的比如先把Base64字符串倒序再给你。这种时候你直接拿去解解码出来是乱码但如果你能看出来“这串字符的字符集很像Base64”就会想到先反转再解。我会在后面第4节的实战案例里再演示这种变体。2.2 ROT13的识别核心读一读就知道不对劲ROT13最有趣的识别方式是“读感识别法”。因为它只替换字母不动空格和标点所以解出来的文本看起来像是一段正常的英文句子被打乱了拼写。比如你看到Gur flag vf ...虽然读不通但能清楚地感觉到每个单词的长度没变、空格位置没变、开头字母大写的位置也没变。这种感觉很微妙但只要你亲手解过几次一下就记住了。ROT13还有一个特点它是自逆的。解密就是加密再移13位就回来了。所以在Python里实现极其简单一行str.translate()就能搞定。但不要因为这个简单就轻视它因为CTF里它经常不是单独出现而是和Base64嵌套或者被进一步的替换加密盖住。另一种变体是ROT5、ROT18之类。ROT5只作用于数字ROT18是数字ROT5加字母ROT13的组合。看到一串字母和数字混合、但字母似乎被替换过、数字似乎在变化时就要想到这些变体。注意如果数字没有变化那就大概率是纯ROT13而不是ROT18。2.3 Atbash的易混淆点它比ROT13更彻底Atbash的规则是字母表整体反转所以a变z、b变y、c变x用来加密一句话所有字母几乎全部变样但空格和标点同样保留。第一次见到Atbash的人往往会把它和ROT13搞混因为两者都是“字母被替换成另一个字母其他字符不动”。区分方法很简单ROT13是循环平移Atbash是镜像翻转。具体到字母上ROT13把a变nAtbash把a变z。你随便取一个字母试一试就知道是哪一种。另外Atbash没有“自逆需两次”的概念它本身就是对合运算做一次和做两次结果一样。这个性质在CTF里很有意思因为出题人如果只套Atbash那相当于根本没加密做一次就回来了。实际题目中Atbash经常和Base64甚至ROT13同时出现。比如先Base64解码得到Atbash文本再Atbash一次得到ROT13文本再ROT13一次才是flag。这种三层嵌套看着吓人但只要区分清楚每一层的特征解起来其实很机械。3. 实操过程与核心环节实现3.1 从零构造一个三层嵌套样本为了把这几种变换的组合逻辑讲明白我先手工构造一个三层嵌套的样本。假设原始flag是flag{base64_rot13_atbash}第一步做Atbash加密。先建立映射a↔zb↔yc↔x以此类推。小写字母处理完后得到uzot{ yzhv64 ilg13 zg yzhs}下划线、花括号、数字4、1、3这些非字母字符保持不变。当然这里的空格是我为了可读性加的实际字符串里不应该有空格。去掉后Atbash结果是uzot{yzhv64ilg13zgyzhs}第二步做ROT13。ROT13规则下u变hz变mo变bt变gy变lz变mh变uv变i4不变i变vl变yg变t3不变z变mg变ty变lz变mh变us变f。整理一下ROT13加密后的字符串是hmbg{ylui64vyl13tzlyzhf}第三步做Base64编码。把这串文本按UTF-8编码成字节再做Base64得到aG1iZ3t5bHVpNjR2eWwxM3R6bHl6aGZ9现在这串aG1iZ3t5bHVpNjR2eWwxM3R6bHl6aGZ9就是你会在一道CTF题目里看到的原始密文。我们要做的就是反过来从这个Base64字符串还原出flag。3.2 手工解码流程演示拿到aG1iZ3t5bHVpNjR2eWwxM3R6bHl6aGZ9先看特征字符集在A-Z、a-z、0-9范围内末尾没有等号长度52个字符能被4整除。基本可以确定是标准Base64虽然没等号但长度刚好够不需要填充。第一步Base64解码。用Python写也就是import base64 s aG1iZ3t5bHVpNjR2eWwxM3R6bHl6aGZ9 b base64.b64decode(s) print(b.decode())输出hmbg{ylui64vyl13tzlyzhf}看到这个结果第一反应不是懵而是观察花括号和数字保留字母全部变成了一堆看起来没什么规律的组合空格不存在。如果题目里有下划线这时候下划线也会保留。根据“字母被替换但非字母保留”的特征判断这可能是ROT13或Atbash。第二步试ROT13。用Python做个映射表import string def rot13(s): lower string.ascii_lowercase upper string.ascii_uppercase trans str.maketrans(lower upper, lower[13:] lower[:13] upper[13:] upper[:13]) return s.translate(trans) print(rot13(hmbg{ylui64vyl13tzlyzhf}))输出uzot{yzhv64ilg13zgyzhs}还是读不通。但好消息是我们已经确定ROT13不是最后一层。继续。第三步试Atbash。Atbash的映射就是a变z、b变y……写个通用的实现def atbash(s): lower string.ascii_lowercase upper string.ascii_uppercase trans str.maketrans(lower upper, lower[::-1] upper[::-1]) return s.translate(trans) print(atbash(uzot{yzhv64ilg13zgyzhs}))输出flag{base64_rot13_atbash}到了这一步flag就出来了。整个过程看起来没什么难度但真正的考点在于你能不能在一堆乱码里冷静地判断出该用什么变换、按什么顺序用。这也是为什么我在前面强调识别能力比工具熟练度更重要。3.3 用CyberChef搭建一条解码链手工操作适合理解原理但比赛时间紧张时我建议直接上CyberChef。这是英国GCHQ开源的一个在线工具最大的优势是可以把多个操作串成一条recipe一键执行。针对刚才这个例子Recipe只需要三步拖入From Base64再拖入ROT13最后拖入Atbash拖好后输入框粘贴原始密文输出框直接就是flag{base64_rot13_atbash}。如果你不确定顺序可以把这三个模块多排几个排列组合反正操作是即时刷新的试错成本几乎为零。我个人的习惯是用CyberChef做快速验证再用Python脚本做批量处理或复杂变体。因为CyberChef的可视化对“理解数据流”非常有帮助尤其当你有多个待解码的数据块时拖一个recipe出来一个个刷比在终端里反复敲命令快得多。3.4 用Python写一个通用解码器如果你经常刷CTF把下面这个多层解码思路做成一个通用脚本是很值得的。它的核心逻辑是不断尝试可逆的编码/密码变换直到输出里出现flag或ctf开头的字符串。import base64 import string import re def rot13(s): lower string.ascii_lowercase upper string.ascii_uppercase trans str.maketrans(lower upper, lower[13:] lower[:13] upper[13:] upper[:13]) return s.translate(trans) def atbash(s): lower string.ascii_lowercase upper string.ascii_uppercase trans str.maketrans(lower upper, lower[::-1] upper[::-1]) return s.translate(trans) def try_decode(s, depth0): if depth 8: return if re.search(rflag\{|ctf\{, s, re.I): print(f[FOUND] {s}) return try: decoded base64.b64decode(s).decode(utf-8, errorsignore) print(f[BASE64] {decoded}) try_decode(decoded, depth 1) except Exception: pass try_decode(rot13(s), depth 1) try_decode(atbash(s), depth 1) s aG1iZ3t5bHVpNjR2eWwxM3R6bHl6aGZ9 try_decode(s)这个脚本比较笨靠的是穷举变换路径但对付常见的嵌套绰绰有余。实际比赛里你也可以直接拿到一个密文就跑一遍能省下不少眼睛盯着屏幕发呆的时间。注意不要设置太深的深度否则分支爆炸会卡死。4. 常见问题与排查技巧实录4.1 Base64解码出来是乱码怎么办这是问得最多的一个问题。首先要冷静下来判断这个“乱码”到底是什么。一种情况是解出来是一堆不可见字符比如\x00\x01\xff这样这通常说明数据流本身是二进制内容比如图片、压缩包。这时不要继续做文本替换要把解码后的字节保存成文件再用binwalk、file、strings去看。另一种情况是解出来是可以打印的乱码文本比如%3Cscript%3E这种URL编码样式或者\xE4\xBD\xA0这种UTF-8编码被强行当Latin-1显示的样式。这时候要做的不是怀疑Base64解错了而是要识别下一层是不是URL编码、HTML实体编码、Hex或Unicode编码。还有一种情况解出来是一段看起来有结构、但字母被打乱的英文那就往ROT13、Atbash或者维吉尼亚密码方向想。今天的重点就是这种情况。记住一条铁律Base64解码后得到的结果一定不要只看一眼就丢掉。把解码结果当成新的密文继续走才是组合题的正解。4.2 分不清ROT13和Atbash怎么办搞混的根源在于只记住了“字母被替换”这一点没注意到具体映射方式。如果你不想背映射表教你一个土办法把密文里的第一个词比如uzot自己动手把u、z、o、t按ROT13翻译一下得到h、m、b、g。如果出来的hmbg像是个被Base64包裹后解码的样子那说明你方向对了。再拿同一个词做Atbash得到f、a、l、g这时候flag基本就呼之欲出了。实际判断中我更倾向于先跑一遍Atbash。因为如果原始字符串里有flag这个单词它Atbash加密后是uzot如果ROT13加密则是syns。两个结果都很有辨识度。你可以先随便找一个长度4的单词试着解看到flag就直接收工。如果非要用工具CyberChef里直接在Recipe里同时拖ROT13和Atbash两个模块输入相同密文看哪一个输出更接近正常英文即可。4.3 解码顺序怎么确定嵌套解码的顺序一般不是随便试而是由最外层的数据格式决定的。比如密文是显性的Base64字符串那第一步几乎必然是Base64解码密文是看起来像英文但读不通的句子第一步就可以尝试ROT13或Atbash密文是Hex数字串第一步就是from hex。“由最外层往内”这个思路听起来简单但在实战里能节省大量时间。很多人喜欢直接把整串丢进一个“万能解码工具”里等结果。工具确实能给出一些候选但它给不了你逻辑。比赛里如果遇到工具解不出来的变体你还是得靠手动判断。建议平时多练习“看字符集判断编码”的习惯比如看到一串十六进制字符0-9/a-f先去想ASCII转文本看到%开头想URL编码看到\u开头想Unicode转义看到连续多个等号或64字符表想Base64。这个能力刷多了自然就有。4.4 容易被忽略的URL-safe变体和自动换行Base64在实际传输中经常会被改写。CTF题里最常出现两种改写。一种是URL-safe变体把标准表里的和/分别换成-和_这种变体常常出现在JSON字段或URL参数中。另一种是带换行的MIME格式每76个字符加一个换行符直接复制粘贴会导致解码失败。遇到这两种情况处理方式分别是把-换回、_换回/再做标准解码或者把换行符全部删掉再进行解码。Python里可以这样处理import base64 s aG1iZ3t5bHVpNjR2eWwxM3R6bHl6aGZ9 # 假设这是URL-safe变体 s s.replace(-, ).replace(_, /) # 假设可能混入换行 s s.replace(\n, ).replace(\r, ) print(base64.b64decode(s))顺带一提很多在线工具会自己处理这些变体但如果数据量大建议直接脚本处理不容易出错。4.5 实战案例data:image/base64里的图片隐写再说一个真实比赛里频繁出现的场景题目源码或流量包里有一段以data:image/png;base64,开头的数据。很多新手看到这种就直接用在线工具转图片然后发现图片里除了几个字什么都没有就卡住了。正确思路是这样的先按Base64解码成图片文件然后用binwalk扫描这个图片看看尾部有没有附加文件再用strings搜索图片里的可打印字符串如果有LSB隐写迹象再上stegsolve或zsteg。如果图片里有一段英文文本莫名其妙地乱序那就可以把这一段单独拎出来做ROT13或Atbash。你会发现这本质上还是“先解Base64再做字母替换”的老套路只不过中间多了一层图片壳。4.6 字符集问题与大小写陷阱处理ROT13和Atbash时一个容易出错的地方是大小写。标准ROT13只作用于ASCII字母且大小写分别处理Atbash也一样。如果某个工具或脚本把字母先转成小写再处理那么密文里的高人就是另一回事了。我遇到过一道题题目给出的密文全是小写但真正的明文里有大写字母。结果用某在线工具直接做ROT13输出全小写我差点以为解错了。后来改用Python脚本按大小写分别映射才拿到正确flag。所以如果明文涉及flag格式里面的FLAG或Flag大小写是容易被干扰的点尽量自己写脚本处理或者检查工具是否区分大小写。4.7 多层脏数据混入时的清理有时候题目为了增加难度会在Base64字符串中间故意插入一些干扰行比如彩虹屁、乱数字、HTML标签。这种情况用肉眼处理很痛苦但正则一行就能清理import re dirty aG1iZ3t5bHVp\nNjR2eWwxM3R6bHl6aGZ9 !-- fake -- clean re.sub(r[^A-Za-z0-9/], , dirty) print(clean)清理之后再解码瞬间清爽。再强调一下在线工具对这类脏数据往往不够宽容脚本是你最可靠的后盾。5. 工具链推荐与效率提升5.1 从CyberChef到随波逐流CTF工具箱工欲善其事必先利其器。CTF密码学这一块我常用的工具分三类在线快速验证、本地批量脚本、全能工具箱。在线首选CyberChef原因不多说它的Recipe机制和即时刷新体验是所有同类工具里做得最舒服的。其次是各类Base64编解码网站但这类网站只能做单层变换遇到嵌套就得靠浏览器手动一层层点效率不高。本地端更推荐随波逐流CTF工具箱它整合了编码解码、古典密码、哈希、隐写等大量功能对初学者特别友好。比如你复制一段密文进去它能帮你猜测编码类型并在右侧列出一排可能的结果省去很多搜索时间。不过要提醒一句这类工具箱的本质是“自动化枚举”它能帮你快速找到答案但没办法帮你建立识别逻辑。所以我的建议是做题时可以用它验证思路但平时练习一定要先手动解想明白每一步的原理。5.2 Python脚本写好了能省一年时间CTF密码学里用得最多的是以下标准库和扩展库base64、codecs、binascii、string、re、hashlib、pycryptodome。对今天讨论的三兄弟来说只需要前五个就够。写脚本的方向不是去背API而是把“尝试-验证”的循环自动化。我自己的做法是维护一个ctf_decode_tools.py里面放常用的解码函数以及一个可以自定义顺序的链式解码函数。遇到新题时先手工分析再用脚本批量验证。这个脚本也不是一次写成的是几年比赛下来不断往里面添补丁的结果。建议你也从今天开始积累自己的工具库而不是每次都从零开始写。5.3 如何利用在线靶场和题库巩固手感理论看十遍不如动手做一遍。推荐两个类型的练习资源一类是CTF在线靶场里面有大量按难度分类的历史真题直接搜“Base64 ROT13”或“古典密码”关键字就能找到对应题目另一类是专门的编码转换挑战站会把Base64、ROT13、Atbash、摩斯电码、十六进制等编码混合成随机题目可以反复刷。我个人的刷题节奏是先做5道纯单层编码题热身再做5道双层嵌套题找感觉最后去找那些混合了图片隐写、流量分析的复杂题来综合实战。不用贪多每天保持手感比临时抱佛脚有用得多。5.4 比赛时要养成的三个习惯第一所有密文先复制保存不要直接在网页上反复编辑。因为在线工具往往会在你刷新之后清空输入丢失原始数据很耽误事。第二每一步解码后的中间结果都单独存一行。比如0_base64.txt、1_rot13.txt、2_atbash.txt。这样万一最后没解出来也能回溯到底哪一层出了问题。第三遇到多段密文时先尝试把相同特征的密文分好类。比如都以结尾的可能是相同方式编码的多个字段看起来像英文乱读的可能是同一种古典密码。分类后再统一处理比一段一段瞎试快得多。6. 写在最后的一点实操心得代码解法写多了我觉得最值钱的不是记住某条命令而是形成一个条件反射看到一个字符串先别慌着解先问自己它属于什么字符集、有没有填充符号、字母分布是否规律、空格结构是否保留。这个反射练出来之后像Base64嵌套ROT13和Atbash这种题基本就是几秒钟定位的事。另外我很建议大家在做题的时候顺手记录一下题目里的“异常点”。比如明明是一道密码题却给了个data:image/jpg;base64,开头的数据或者一段正常英文里频繁出现gur这种奇怪的单词。这些“异常点”其实就是出题人留下来引导你往特定方向走的线索。把这些线索和最终解法对应起来刷题效率会明显提升。最后再分享一个小技巧如果你卡在某一层一直解不出来试着把上一层的解码结果放到CyberChef里把所有可能的古典密码模块ROT13、Atbash、凯撒、维吉尼亚、培根、摩斯全部点一遍经常会有意外收获。毕竟题目是人出的出题人总是有偏好的猜中出题人喜欢用什么套路比猜中某种算法更重要。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进