ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从凯撒到RSA:九种加解密算法原理与自动破解实现

从凯撒到RSA:九种加解密算法原理与自动破解实现 简介这是一款基于Python开发的CTF Crypto图形化工具面向CTF参赛者与信息安全入门学习者覆盖凯撒密码、维吉尼亚密码、栅栏密码、摩斯密码、Base64/ASCII编码、AES、DES、RSA、RC4等常见算法的加密与解密并支持维吉尼亚密钥自动破解。资源包共70个文件压缩后仅126KB包含Python源码、pyc编译文件、JavaScript脚本、PEM密钥对、UI界面文件及JSON配置等既可阅读算法实现也能直接运行体验完整的加解密流程。配套的UI文件与spec打包配置便于二次开发或发布独立程序node_modules目录内置crypto-js等前端加密库适合在Web环境中复用。已有2346人学习下载适合需要快速搭建Crypto实验环境、研究经典密码实现或刷题验证思路的学习者。1. 一个把凯撒、维吉尼亚、AES 放同一工具里的现实场景把凯撒密码和 AES、RSA 放进同一个加解密工具第一反应往往是“这有什么可比性”但真正在写这类工具时你会发现它要解决的是两件完全不同的事一部分是 CTF、古典密文分析里常见的低熵编码与替换另一部分是生产环境里真正在传输链路上跑的对称与非对称算法。凯撒、栅栏、摩斯、Base64 这类对象破解的核心是枚举和统计而 AES、DES、RSA、RC4 的核心是参数怎么选、模式怎么配、密钥怎么管。维吉尼亚恰好卡在中间——它比凯撒难在一个“密钥长度未知”但又远没有到现代密码的强度级别因此值得单独给它一套基于重合指数和卡方拟合的自动破解流程。这篇文章就按这个思路把这九种算法拆开讲透并给出一套可以直接落地为命令行工具的实现路径。适合需要写密文分析脚本、做 CTF 工具集或者想理清古典密码与现代密码边界的人。2. 凯撒与栅栏密码的穷举破解路径摩斯表、Base64 与可枚举边界2.1 凯撒密码位移穷举与频率卡方判断凯撒密码是所有替换密码里最朴素的一种明文每个字母向后或向前移动固定位数得到密文。破解它的关键不是“知道位移量”而是“怎么从 26 个候选中自动挑出可读的那一个”。常见做法是暴力枚举全部 26 个位移再用英文频率分布做卡方检验筛选。import string FREQ [0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015, 0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749, 0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758, 0.00978, 0.02360, 0.00150, 0.01974, 0.00074] def caesar_break(ciphertext: str): candidates [] for shift in range(26): text [] for ch in ciphertext.lower(): if ch in string.ascii_lowercase: idx string.ascii_lowercase.index(ch) text.append(string.ascii_lowercase[(idx - shift) % 26]) else: text.append(ch) plain .join(text) counts [0.0] * 26 letters [c for c in plain if c.isalpha()] for c in letters: counts[ord(c) - ord(a)] 1 n len(letters) if n 0: continue chi2 sum((counts[i] / n - FREQ[i]) ** 2 / FREQ[i] for i in range(26)) candidates.append((chi2, shift, plain)) candidates.sort() return candidates[:5]这段代码的筛选逻辑不是看“像不像单词”而是看整体字母分布与英文自然分布的拟合程度卡方值越小说明频率分布越接近英文候选排名越靠前。之所以用卡方而不用单词匹配是因为凯撒密文往往来自短文本或经过大小写、数字混合处理单词表匹配在这种场景下召回率很低。把FREQ换成中文拼音或德语频率表同样一套算法就能跨语言复用。2.2 栅栏密码轨道数枚举加元音比例粗筛栅栏密码Rail Fence Cipher是按之字形把明文写到若干“轨道”上再按行读出。破解它的难点与凯撒不同位移量变成了轨道数且轨道数未知。好在这个密钥空间极小一般 2 到 10 轨足够覆盖绝大多数场景所以枚举轨道数并做启发式评分比做频率分析更直接。def decrypt_rail_fence(cipher: str, rails: int) - str: n len(cipher) positions [[] for _ in range(rails)] row, step 0, 1 for i in range(n): positions[row].append(i) if row 0: step 1 elif row rails - 1: step -1 row step it iter(cipher) filled {pos: next(it) for pos in sum(positions, [])} return .join(filled[i] for i in range(n)) def vowel_score(text: str) - float: letters [c for c in text.lower() if c.isalpha()] vowels sum(1 for c in letters if c in aeiou) return vowels / max(len(letters), 1) def break_rail_fence(cipher: str, max_rails: int 10): results [] for rails in range(2, max_rails 1): plain decrypt_rail_fence(cipher, rails) score abs(vowel_score(plain) - 0.38) results.append((score, rails, plain)) return sorted(results)[:5]这里用元音比例做粗筛英文的元音占比通常在 0.36 到 0.42 之间与 0.38 的偏差越小越像是正常文本。这个启发式并不严格但足以把 10 个候选压缩到前 5 名最后靠人工扫一眼即可确认。注意解密函数里的positions必须记录每个密文字符在原文中的真实下标而不是简单地按行均分否则遇到明文长度与轨道数不成倍数时就会错位。2.3 摩斯密码与 Base64可逆编码的直接映射摩斯密码和 Base64 严格说都不是加密而是编码。摩斯的本质是变长符号映射字母对应点划组合数字对应五单位组合Base64 则是把二进制数据按 6 位一组映射到 64 个可打印字符。它们不需要“破解”只需要“识别”和“解码”。MORSE { A: .-, B: -..., C: -.-., 0: -----, 1: .----, : / } def decode_morse(morse: str) - str: reverse {v: k for k, v in MORSE.items()} words morse.strip().split( / ) return .join( .join(reverse.get(sym, ?) for sym in word.split()) for word in words )在工具实现里摩斯码最常见的坑是分隔符不统一有人用空格分隔字母、用/分隔单词有人把空格与/混用。解码前先做一次正则归一化把所有连续空格压缩为单个空格比在解码函数里处理各种边界情况省事得多。Base64 的识别特征是末尾可能出现或填充以及字符串经常以data:image/png;base64,这类前缀出现在 Web 场景中。Python 里直接base64.b64decode(s)即可但要注意传入前先检查字符串长度的合法性长度不是 4 的倍数时先补。3. 维吉尼亚密码自动破解Kasiski 测定密钥长度 重合指数精排3.1 为什么维吉尼亚不能直接穷举维吉尼亚密码把凯撒的固定位移扩展成周期性的位移序列密钥长度是L明文中相隔L的字母使用同一个位移。直接穷举密钥需要尝试26^L种组合密钥长到 8 位时已经不可能暴力完成。但有一个结构性弱点密钥虽然是周期的但每个字母的位移在周期内固定所以只要把密文按密钥长度分列每一列本质上就是一个凯撒密文。破解维吉尼亚因此被拆成两个子问题先测定密钥长度再对每一列做凯撒频率分析。3.2 用重合指数IC测定密钥长度重合指数描述一段文本中随机抽取两个字母恰好相同的概率。英文自然文本的 IC 约为 0.065随机等概率字母序列的 IC 约为 0.038。对维吉尼亚密文按某个候选长度L分列后如果L就是真实密钥长度那么每一列内字母分布会接近英文自然分布平均 IC 会显著高于 0.038如果L不对各列相当于随机乱序IC 会贴到 0.038 附近。这就是自动测定密钥长度的依据。from collections import Counter def index_of_coincidence(text: str) - float: letters [c for c in text.lower() if c.isalpha()] n len(letters) if n 2: return 0.0 counts Counter(letters) return sum(v * (v - 1) for v in counts.values()) / (n * (n - 1)) def estimate_key_length(cipher: str, max_len: int 20) - int: best_len, best_ic 1, 0.0 for L in range(1, max_len 1): columns [cipher[i::L] for i in range(L)] avg_ic sum(index_of_coincidence(col) for col in columns) / L if avg_ic best_ic: best_ic, best_len avg_ic, L return best_len实际使用时不要只返回 IC 最高的那个长度。我一般会把候选长度按 IC 从高到低排成前 5 名因为短文本的统计波动很大真实密钥长度经常排在第 2 或第 3 位。判断技巧是看 IC 曲线是否在某个长度处出现“尖峰群”——真实长度的倍数如 5、10、15也会出现小幅抬升所以直接取第一个显著峰值比取最大值更可靠。3.3 按列做卡方拟合还原密钥与明文密钥长度确定后把密文按cipher[i % L]分到 L 列每一列单独跑凯撒卡方分析卡方最小的位移就是该列密钥字母。将 L 个位移拼成完整密钥再对全量密文做逆向移位即可还原明文。import string def vigenere_key_from_shift(cipher: str, key_length: int) - str: key for col in range(key_length): column cipher[col::key_length] best_shift, best_score 0, float(inf) for shift in range(26): letters [c for c in column.lower() if c.isalpha()] counts Counter(letters) n len(letters) if n 0: continue score 0.0 for i, ch in enumerate(string.ascii_lowercase): observed counts.get(ch, 0) / n expected FREQ[(i - shift) % 26] score (observed - expected) ** 2 / expected if score best_score: best_score, best_shift score, shift key string.ascii_lowercase[best_shift] return key def vigenere_decrypt(cipher: str, key: str) - str: plain [] key_idx 0 for ch in cipher: if ch.isalpha(): base ord(A) if ch.isupper() else ord(a) shift ord(key[key_idx % len(key)].lower()) - ord(a) plain.append(chr(base (ord(ch) - base - shift) % 26)) key_idx 1 else: plain.append(ch) return .join(plain)这段实现里有两个值得注意的细节。第一vigenere_decrypt只对字母做移位数字、空格、标点全部原样保留同时密钥索引只在字母处前进否则非字母字符会导致密钥错位。第二卡方拟合时使用FREQ[(i - shift) % 26]而不是FREQ[i]是因为密文字母频率由明文字母频率平移shift得到拟合时需要尝试所有可能的平移方向。4. AES、DES、RC4 与 RSA对称分组、流式与公钥体系的手写实现要点4.1 AES分组、IV、填充与 GCM 模式的选择AES 是分组密码分组长度固定 128 位。工具实现里常见的坑不是算法本身而是三层包裹填充模式决定分组不满时补什么工作模式决定分组之间怎么关联IV 或 Nonce 决定同一密钥下密文是否可重复。from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import os key os.urandom(32) iv os.urandom(16) cipher AES.new(key, AES.MODE_CBC, iv) ct cipher.encrypt(pad(bplaintext data, AES.block_size)) print(iv.hex(), ct.hex())CBC 模式要求 IV 随机且每次加密都不同否则相同明文会得到相同密文这也是“AES 什么模式每次加密结果都不一样”这个问题的答案所在ECB 模式下同样的明文块永远得到同样的密文块。实现里必须把 IV 与密文一起存储或传输解密时先取前 16 字节作为 IV。生产工具我更推荐 GCM 而不是 CBC因为 GCM 同时提供机密性和完整性校验能直接发现密文被篡改CBC 搭配 PKCS#7 填充时如果解密端对填充错误处理不当还可能引入 Padding Oracle 攻击面。4.2 DES 与 RC4块密码退化与流式密码实现DES 在今天已经属于被淘汰的对称算法密钥只有 56 位有效长度硬件几分钟就能暴力破解。但很多遗留系统的协议里还在跑 3DES工具里保留它是为了兼容老数据。RC4 则是流密码密钥调度算法生成 256 字节状态向量再逐字节异或生成密钥流。RC4 的问题在于密钥调度存在偏差前 256 字节密钥流有明显统计特征现代协议已经全面弃用。def rc4(key: bytes, data: bytes) - bytes: S list(range(256)) j 0 for i in range(256): j (j S[i] key[i % len(key)]) % 256 S[i], S[j] S[j], S[i] i j 0 out bytearray() for b in data: i (i 1) % 256 j (j S[i]) % 256 S[i], S[j] S[j], S[i] out.append(b ^ S[(S[i] S[j]) % 256]) return bytes(out)RC4 加解密是同一个函数因为异或操作是对称的。这里要注意每次调用都重新执行 KSA如果对多段数据复用一个状态就破坏了流密码的一次一密前提。实际工具中如果必须兼容 RC4建议至少跳过前 768 字节的密钥流输出降低已知偏差被利用的风险。4.3 RSA从密钥参数到签名验签的常见坑RSA 的强度建立在两个大素数乘积难以分解上。工具实现里常见的需求是生成密钥对、按参数组合公钥、签名验签。最容易出问题的不是数学部分而是密钥格式与参数含义。from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 from Crypto.Hash import SHA256 key RSA.generate(3072) priv_pem key.export_key().decode() pub_pem key.publickey().export_key().decode() print(n 位数:, key.n.bit_length()) print(e:, key.e) print(p 位数:, key.p.bit_length(), q 位数:, key.q.bit_length()) h SHA256.new(bimportant message) sig pkcs1_15.sign(key, h) pkcs1_15.verify(key.publickey(), h, sig) print(verify ok)n p * q公钥是(n, e)私钥必须持有dp和q不该泄露。生成密钥时若内存不足或熵源受限RSA.generate会报错这时可以把e稍微调大如 65537 已是常用值或改用系统熵源。常见报错rsa public key not find多数是公钥字符串传成了 PEM 头缺失的裸 Base64 文本或者把私钥对象当公钥去验签。签名验签和加密解密不要搞混RSA 加密用公钥、解密用私钥而签名用私钥、验签用公钥。若工具同时暴露加解密与签名两组接口必须把这两个流程分开设计否则用户很容易拿着公钥去做“解密”然后得到乱码。5. 维吉尼亚密钥破解的验证技巧用已知明文反推密钥与工具化落地5.1 用已知明文片段反推密钥频率分析只能给出统计意义上的密钥遇到短密文或非英文文本时经常整段跑偏。这时如果手里有一段已知明文哪怕是几个常见单词如the、and就可以直接反推密钥片段key_char (cipher_char - plain_char) mod 26把已知明文对齐到密文的对应位置逐位算出密钥字符再按周期性规律延展到整个密钥。def recover_key_fragment(cipher: str, known_plain: str, start: int) - str: key_frag [] for i, ch in enumerate(known_plain): if not ch.isalpha(): continue c cipher[start i].lower() p ch.lower() shift (ord(c) - ord(p)) % 26 key_frag.append(string.ascii_lowercase[shift]) return .join(key_frag)这个推法对凯撒同样适用已知一个明文字母就能定出位移。对维吉尼亚密钥破解来说反推出来的密钥片段还能反过来验证前面 IC 测定的长度是否正确——如果按长度 L 分列后已知明文对应的密钥片段在每个周期内重复出现说明 L 大概率正确如果不重复说明密钥长度估错了。5.2 破解结果的验证与批处理自动化自动破解维吉尼亚之后不要直接信任输出标准验证方法是解出来的明文再算一次 IC应该明显回落向英文自然值同时统计明文里高频词命中数。我一般会把两者合成一个可解释性得分输出到同一行表格里方便批量跑多个密文时快速排查。在实际工具落地时我建议把每一类算法封装成{name, encrypt, decrypt, crack}四个槽位的插件式注册表命令行入口统一为ciphertool.py --algo vigenere --crack --input cipher.txt。这样新增算法只需要注册新模块不需要改主流程。批量测试时用一层薄薄的脚本循环调用即可python ciphertool.py --algo vigenere --crack --input cipher.txt python ciphertool.py --algo caesar --crack --input cipher.txt python ciphertool.py --algo railfence --crack --input cipher.txt把输出的明文统一经过grep -iE \b(the|and|that|this)\b做一轮高频词过滤能在几十个预期结果里快速锁定可读文本。验证通过的再进入下一步把破解出来的维吉尼亚密钥合并成key:plaintext的键值对供其他分析脚本直接消费。这样整个工具链从识别、破解到验证就形成了一个不需要人工逐条盯屏的闭环。本文还有配套的精品资源点击获取
返回列表