青梅竹马:一道融合密码学与文化的CTF逆向题深度解析

admin 2025-12-22 04:46:46 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 这篇文章详细解析了一道名为青梅竹马的CTF逆向题,该题融合了密码学与中国文化元素。文章从题目名称的文化隐喻入手,识别出这是一道基于RSA算法的破解题,目标是通过求解幂模方程M^e≡2(modN)找到正确的序列号。作者系统介绍了RSA算法原理、欧拉函数、模逆元等密码学基础,并提供了完整的解题流程,包括生成素数列表、构造模数N、计算欧拉函数、求解私钥d等步骤。文章还详细解释了自定义Base64编码的实现过程,并提供了完整的验证代码。最终得出序列号为PEDIyV9102dVreadyu,并通过数学验证确认其正确性。文章不仅展示了CTF解题技巧,还深入分析了RSA安全性的数学基础,对密码学学习者有很高的参考价值。 综合评分: 92 文章分类: CTF,逆向分析,密码学,漏洞分析,实战经验


cover_image

青梅竹马:一道融合密码学与文化的CTF逆向题深度解析

原创

破镜安全

破镜安全

2025年12月13日 08:02 四川

青梅竹马:一道融合密码学与文化的CTF逆向题深度解析

前言

在CTF(Capture The Flag)竞赛中,crypto类题目往往结合了数学、密码学和编程技巧。今天要介绍的”青梅竹马”是一道设计精巧的题目,它不仅考察密码学知识,还巧妙地融入了中国传统文化元素。

本文将从零开始,带领大家完整地分析和解决这道题目,让即使是密码学新手也能理解其中的原理和技巧。

一、题目初探

1.1 题目信息

拿到题目后,我们首先了解基本信息:

  • 题目名称:青梅竹马
  • 题目文件crackme_readyu2019_FIX1.exe
  • 题目类型:Crypto + Reverse
  • 目标:找到正确的序列号(Serial Number)
  • 验证要求:序列号只能包含大小写字母和数字
  • 成功提示:输入正确序列号后显示 “yes, correct sn!”

1.2 文件信息

先查看文件的基本信息:

$ file crackme_readyu2019_FIX1.exe
crackme_readyu2019_FIX1.exe: PE32 executable (GUI) Intel 80386, for MS Windows

这是一个32位的Windows可执行文件。

1.3 题目名称的玄机

在开始技术分析之前,我们先思考一个问题:为什么题目叫”青梅竹马”?

“青梅竹马”源自李白的诗句”郎骑竹马来,绕床弄青梅”,形容从小一起长大的男女。这个成语让我们联想到另一个相关的成语——“两小无猜”

这里的关键词是”两小”,在数学语境中,它暗示着:

  • “两” = 数字 2
  • “小” = 素数

这个文化隐喻实际上在提示我们:

  1. 最终答案可能与数字2有关
  2. 题目可能涉及小素数的使用

这是出题者留下的第一个重要线索!

二、密码学基础知识

在深入分析题目之前,我们需要掌握一些必要的密码学知识。

2.1 什么是RSA算法?

RSA是一种非对称加密算法,由Ron Rivest、Adi Shamir和Leonard Adleman在1977年提出。它的安全性基于大整数分解的困难性。

RSA的基本流程:

1. 密钥生成

① 选择两个大素数 p 和 q
② 计算 N = p × q(N称为模数)
③ 计算 φ(N) = (p-1) × (q-1)(欧拉函数)
④ 选择公钥指数 e,满足 1 < e < φ(N) 且 gcd(e, φ(N)) = 1
⑤ 计算私钥指数 d,满足 e × d ≡ 1 (mod φ(N))

2. 加密

密文 C = M^e mod N

3. 解密

明文 M = C^d mod N

2.2 欧拉函数 φ(n)

欧拉函数 φ(n) 表示小于等于n的正整数中与n互质的数的个数。

重要性质:

  1. 素数的欧拉函数
  • 对于素数 p:φ(p) = p – 1
  • 原因:除了p本身,所有小于p的正整数都与p互质
  1. 积性性质
  • 如果 gcd(m, n) = 1,则 φ(m × n) = φ(m) × φ(n)
  1. 素数乘积的欧拉函数
  • φ(p₁ × p₂ × … × pₖ) = (p₁-1) × (p₂-1) × … × (pₖ-1)

举例说明:

# 计算 φ(15)
# 15 = 3 × 5
φ(15) = φ(3&nbsp;×&nbsp;5) = φ(3) × φ(5) = (3-1) × (5-1) =&nbsp;2&nbsp;×&nbsp;4&nbsp;=&nbsp;8

# 验证:小于15且与15互质的数:1, 2, 4, 7, 8, 11, 13, 14,共8个

2.3 模逆元

定义:对于整数 a 和模数 m,如果存在整数 x 使得:

a × x ≡ 1 (mod m)

则称 x 为 a 模 m 的逆元,记作 a⁻¹ 或 x ≡ a⁻¹ (mod m)。

求解方法:使用扩展欧几里得算法(Extended Euclidean Algorithm)

算法原理

  • 普通欧几里得算法求 gcd(a, b)
  • 扩展版本还能找到 x 和 y,使得 a×x + b×y = gcd(a, b)
  • 当 gcd(a, b) = 1 时,两边对 b 取模:a×x ≡ 1 (mod b)
  • 此时 x 就是 a 的模逆元

代码实现

def&nbsp;extended_gcd(a, b):
&nbsp; &nbsp;&nbsp;"""扩展欧几里得算法"""
&nbsp; &nbsp;&nbsp;if&nbsp;a ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;b,&nbsp;0,&nbsp;1
&nbsp; &nbsp; gcd, x1, y1 = extended_gcd(b % a, a)
&nbsp; &nbsp; x = y1 - (b // a) * x1
&nbsp; &nbsp; y = x1
&nbsp; &nbsp;&nbsp;return&nbsp;gcd, x, y

def&nbsp;mod_inverse(e, phi):
&nbsp; &nbsp;&nbsp;"""求模逆元"""
&nbsp; &nbsp; gcd, x, y = extended_gcd(e, phi)
&nbsp; &nbsp;&nbsp;if&nbsp;gcd !=&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;Exception("模逆元不存在")
&nbsp; &nbsp;&nbsp;return&nbsp;(x % phi + phi) % phi

2.4 欧拉定理

定理内容:如果 gcd(a, n) = 1,则:

a^φ(n) ≡ 1 (mod n)

这个定理是RSA算法正确性的数学基础。

RSA为什么能正确解密?

已知:e × d ≡ 1 (mod φ(N))
即:e × d = k × φ(N) + 1(k为某个整数)

解密过程:
C^d = (M^e)^d = M^(e×d) = M^(k×φ(N)+1) = M × (M^φ(N))^k

根据欧拉定理:M^φ(N) ≡ 1 (mod N)
所以:M × (M^φ(N))^k ≡ M × 1^k ≡ M (mod N)

因此解密能正确恢复明文 M

三、题目分析与建模

现在我们有了必要的理论基础,开始分析这道题目。

3.1 题目的核心机制

通过分析题目设计思路,我们发现这道题的核心是求解一个幂模方程

M^e ≡ 2 (mod N)

其中:

  • M:我们要求的未知数(最终会编码成序列号)
  • e:加密指数(需要确定)
  • N:一个特殊构造的合数(需要分析)
  • 2:方程右侧(对应”两小无猜”的提示)

3.2 为什么这个方程可解?

在标准RSA中,给定密文C和公钥(e, N),在不知道私钥d的情况下,解密是计算困难的(这是RSA安全性的基础)。

但这道题设计了一个特殊场景:

关键点1:N是小素数的乘积

  • N = p₁ × p₂ × … × pₖ(多个小素数)
  • 这意味着我们可以轻松”分解”N

关键点2:可以直接计算 φ(N)

  • 因为知道所有素因子,可以直接计算欧拉函数
  • φ(N) = (p₁-1) × (p₂-1) × … × (pₖ-1)

关键点3:可以求出私钥 d

  • 通过 d = e⁻¹ mod φ(N),我们能计算出私钥

关键点4:反向求解 M

  • 有了私钥d,就能通过 M = 2^d mod N 求出M

这实际上是一个**”已知公钥和密文,通过分解N来破解RSA”**的教学案例。

3.3 解题思路总览

第一步:生成100以内的素数列表
&nbsp; &nbsp;↓
第二步:构造模数N(排除2,从3到73)
&nbsp; &nbsp;↓
第三步:计算欧拉函数 φ(N)
&nbsp; &nbsp;↓
第四步:确定加密指数e(选择83)
&nbsp; &nbsp;↓
第五步:求私钥 d = e⁻¹ mod φ(N)
&nbsp; &nbsp;↓
第六步:计算 M = 2^d mod N
&nbsp; &nbsp;↓
第七步:将M转为16进制
&nbsp; &nbsp;↓
第八步:使用自定义Base64编码
&nbsp; &nbsp;↓
第九步:添加分隔符得到最终序列号

四、完整解题过程

4.1 生成素数列表

首先,我们需要生成100以内的所有素数。

素数判断算法(试除法)

def&nbsp;is_prime(n):
&nbsp; &nbsp;&nbsp;"""判断一个数是否为素数"""
&nbsp; &nbsp;&nbsp;if&nbsp;n <&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;if&nbsp;n ==&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;True
&nbsp; &nbsp;&nbsp;if&nbsp;n %&nbsp;2&nbsp;==&nbsp;0: &nbsp;# 排除偶数
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;# 只需检查到√n
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(3, int(n**0.5) +&nbsp;1,&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;n % i ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;return&nbsp;True

def&nbsp;generate_primes_under(limit):
&nbsp; &nbsp;&nbsp;"""生成小于limit的所有素数"""
&nbsp; &nbsp; primes = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(2, limit):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;is_prime(i):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; primes.append(i)
&nbsp; &nbsp;&nbsp;return&nbsp;primes

# 生成100以内的素数
primes = generate_primes_under(100)
print(primes)

输出结果

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

共有 25个素数

4.2 构造模数N

接下来需要构造N,但这里有几个关键问题需要理解。

问题1:为什么排除素数2?

原因分析

  1. 题目提示:”两小无猜” → 2应该在方程右侧,而不是N的因子
  2. 数学要求:方程是 M^e ≡ 2 (mod N)
  • 如果2是N的因子,那么N是偶数
  • 这会导致方程性质改变,可能无唯一解
  1. 保证互质:M和N需要互质,而右侧是2,所以N不能包含2

问题2:为什么在79处截止?

分析

  • 79的十六进制:0x4F
  • ASCII码:0x4F = ‘O’(大写字母O)
  • 这是出题者设置的一个技巧性边界

N的计算

N =&nbsp;1
factors = []

for&nbsp;p&nbsp;in&nbsp;primes:
&nbsp; &nbsp;&nbsp;if&nbsp;p ==&nbsp;2: &nbsp;# 排除2
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp;&nbsp;if&nbsp;p >=&nbsp;79: &nbsp;# 在79处截止
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; N *= p
&nbsp; &nbsp; factors.append(p)

print(f"N的素因子:&nbsp;{factors}")
print(f"N =&nbsp;{N}")

计算结果

N的素因子: [3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73]
N = 20364840299624512075310661735

N是一个 93位(bit)的大整数,由20个素数相乘得到。

4.3 计算欧拉函数 φ(N)

根据欧拉函数的积性性质:

phi_N =&nbsp;1
phi_factors = []

for&nbsp;p&nbsp;in&nbsp;factors:
&nbsp; &nbsp; phi_N *= (p -&nbsp;1)
&nbsp; &nbsp; phi_factors.append(p -&nbsp;1)

print(f"φ(N)的因子:&nbsp;{phi_factors}")
print(f"φ(N) =&nbsp;{phi_N}")

计算过程

φ(N) = φ(3) × φ(5) × φ(7) × ... × φ(73)
&nbsp; &nbsp; &nbsp;= (3-1) × (5-1) × (7-1) × ... × (73-1)
&nbsp; &nbsp; &nbsp;= 2 × 4 × 6 × 10 × 12 × 16 × 18 × 22 × 28 × 30 × 36 × 40 × 42 × 46 × 52 × 58 × 60 × 66 × 70 × 72
&nbsp; &nbsp; &nbsp;= 5133855159158901099724800000

结果

φ(N) = 5133855159158901099724800000

4.4 选择加密指数e

题目选择 e = 83 作为加密指数。

为什么选择83?

必要条件:e 必须满足 gcd(e, φ(N)) = 1

验证83的合理性

  1. 83是素数:素数只与1互质或与自身相等
  2. 83 > 73:83大于N的最大素因子,所以不是N的因子
  3. φ(N)的因子分析
  • φ(N)的所有因子项:2, 4, 6, …, 72
  • 都小于83
  • 所以83不可能整除φ(N)
e =&nbsp;83

# 验证互质性
gcd, _, _ = extended_gcd(e, phi_N)
print(f"gcd({e}, φ(N)) =&nbsp;{gcd}")
if&nbsp;gcd ==&nbsp;1:
&nbsp; &nbsp; print("✓ e和φ(N)互质,模逆元存在")

输出

gcd(83, φ(N)) = 1
✓ e和φ(N)互质,模逆元存在

4.5 求解私钥d

现在求解私钥d,使得:

e × d ≡ 1 (mod φ(N))

即求83在模φ(N)下的逆元。

d = mod_inverse(e, phi_N)
print(f"私钥 d =&nbsp;{d}")

计算结果

d = 1855610298491169072189686747

验证

# 验证 e × d ≡ 1 (mod φ(N))
result = (e * d) % phi_N
print(f"e × d mod φ(N) =&nbsp;{result}") &nbsp;# 应该等于1

4.6 计算M

现在我们可以计算M了!

数学推导

原方程:M^e ≡ 2 (mod N)
两边取d次方:(M^e)^d ≡ 2^d (mod N)
化简左边:M^(e×d) ≡ 2^d (mod N)

因为 e×d ≡ 1 (mod φ(N)),设 e×d = k×φ(N) + 1

所以:M^(k×φ(N)+1) ≡ 2^d (mod N)
&nbsp; &nbsp; &nbsp;M × (M^φ(N))^k ≡ 2^d (mod N)

根据欧拉定理:M^φ(N) ≡ 1 (mod N)(假设gcd(M,N)=1)

所以:M × 1^k ≡ 2^d (mod N)
&nbsp; &nbsp; &nbsp;M ≡ 2^d (mod N)

代码实现

M = pow(2, d, N) &nbsp;# Python内置的快速幂模运算
print(f"M =&nbsp;{M}")

计算结果

M = 6602940601029543050476765433

关键:Python的pow函数

  • pow(base, exp, mod) 使用快速幂算法
  • 时间复杂度:O(log exp)
  • 即使d非常大,也能快速计算

4.7 验证解的正确性

在继续之前,我们必须验证M是否正确:

# 验证 M^e mod N 是否等于 2
verification = pow(M, e, N)
print(f"M^{e}&nbsp;mod N =&nbsp;{verification}")

if&nbsp;verification ==&nbsp;2:
&nbsp; &nbsp; print("✓ 验证成功!M是正确的解")
else:
&nbsp; &nbsp; print("✗ 验证失败")

输出

M^83 mod N = 2
✓ 验证成功!M是正确的解

4.8 转换为16进制

将M转换为16进制表示,为后续编码做准备:

M_hex = hex(M)[2:].upper() &nbsp;# 去掉'0x'前缀,转大写
print(f"M (16进制) =&nbsp;{M_hex}")
print(f"长度:&nbsp;{len(M_hex)//2}&nbsp;字节")

输出

M (16进制) = 1555D30F38B0DBCAEC83C0F9
长度: 12 字节

这是一个12字节(96位)的数据。

五、自定义Base64编码

5.1 为什么需要编码?

虽然我们已经得到了数学上的解M,但:

  1. M是一个很大的数字,不便记忆和输入
  2. 题目要求序列号只能包含字母和数字
  3. 需要一种编码方案将二进制数据转为可打印字符

Base64 是一种常见的二进制到文本的编码方案。

5.2 Base64编码原理

基本概念

  • Base64使用64个ASCII字符来表示二进制数据
  • 标准字符表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

编码过程

1. 将数据按每3个字节(24位)分组
2. 每组24位分成4个6位的片段
3. 每个6位片段(值0-63)对应Base64表中的一个字符
4. 如果最后不足3字节,用'='填充

图解示例

原始数据(3字节): 0x15 0x55 0xD3
二进制: 00010101 01010101 11010011

分成4组(每组6位):
&nbsp; 000101 | 010101 | 010111 | 010011
&nbsp; &nbsp; 5 &nbsp; | &nbsp; 21 &nbsp; | &nbsp; 23 &nbsp; | &nbsp; 19

查表(标准Base64):
&nbsp; 5→F, 21→V, 23→X, 19→T
编码结果: FVXT

5.3 自定义Base64字符表

题目使用了一个自定义的Base64字符表

standard_table =&nbsp;"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
custom_table &nbsp; =&nbsp;"ABCyVPGHTJKLMNOFQRSIUEWDYZgbc8sfah1jklmnopqret5v0xX9wi234u67dz+/"

对比分析

| 位置 | 标准 | 自定义 | 备注 | | — | — | — | — | | 0-2 | ABC | ABC | 相同 | | 3 | D | y | 替换 | | 4 | E | V | 替换 | | 5 | F | P | 替换 | | … | … | … | … |

为什么使用自定义表?

  1. 增加趣味性:让编码结果看起来”有意义”
  2. 隐藏特征:不容易被直接识别为Base64
  3. 嵌入信息:可以让结果包含特定字符串(如”readyu”)

5.4 实现自定义Base64编码

def&nbsp;custom_base64_encode(data_hex):
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; 自定义Base64编码
&nbsp; &nbsp; 输入:16进制字符串
&nbsp; &nbsp; 输出:自定义Base64编码字符串
&nbsp; &nbsp; """
&nbsp; &nbsp; standard_table =&nbsp;"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
&nbsp; &nbsp; custom_table =&nbsp;"ABCyVPGHTJKLMNOFQRSIUEWDYZgbc8sfah1jklmnopqret5v0xX9wi234u67dz+/"

&nbsp; &nbsp;&nbsp;# 1. 将16进制字符串转为字节数组
&nbsp; &nbsp; data_bytes = bytes.fromhex(data_hex)

&nbsp; &nbsp;&nbsp;# 2. 标准Base64编码
&nbsp; &nbsp; result = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, len(data_bytes),&nbsp;3):
&nbsp; &nbsp; &nbsp; &nbsp; chunk = data_bytes[i:i+3] &nbsp;# 取3个字节

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 将3个字节转为一个24位整数
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;b&nbsp;in&nbsp;chunk:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; n = (n <<&nbsp;8) | b

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 根据字节数生成Base64字符
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(chunk) ==&nbsp;3:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 3字节 → 4个Base64字符
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; indices = [
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (n >>&nbsp;18) &&nbsp;0x3F, &nbsp;# 最高6位
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (n >>&nbsp;12) &&nbsp;0x3F, &nbsp;# 次高6位
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (n >>&nbsp;6) &&nbsp;0x3F, &nbsp;&nbsp;# 次低6位
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; n &&nbsp;0x3F&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 最低6位
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;idx&nbsp;in&nbsp;indices:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result.append(standard_table[idx])

&nbsp; &nbsp; standard_encoded =&nbsp;''.join(result)

&nbsp; &nbsp;&nbsp;# 3. 转换为自定义Base64
&nbsp; &nbsp; custom_encoded =&nbsp;''
&nbsp; &nbsp;&nbsp;for&nbsp;c&nbsp;in&nbsp;standard_encoded:
&nbsp; &nbsp; &nbsp; &nbsp; idx = standard_table.index(c)
&nbsp; &nbsp; &nbsp; &nbsp; custom_encoded += custom_table[idx]

&nbsp; &nbsp;&nbsp;return&nbsp;custom_encoded

# 编码M
M_hex =&nbsp;"1555D30F38B0DBCAEC83C0F9"
sn = custom_base64_encode(M_hex)
print(f"编码结果:&nbsp;{sn}")

详细执行过程

M_hex = 1555D30F38B0DBCAEC83C0F9 (12字节)

第1组(3字节): 15 55 D3
&nbsp; 二进制: 00010101 01010101 11010011
&nbsp; 分组: 000101 010101 010111 010011
&nbsp; 索引: 5, 21, 23, 19
&nbsp; 标准: F, V, X, T
&nbsp; 自定义: P, E, D, I

第2组(3字节): 0F 38 B0
&nbsp; 二进制: 00001111 00111000 10110000
&nbsp; 分组: 000011 110011 100010 110000
&nbsp; 索引: 3, 51, 34, 48
&nbsp; 标准: D, z, i, w
&nbsp; 自定义: y, 9, 1, 0

第3组(3字节): DB CA EC
&nbsp; 二进制: 11011011 11001010 11101100
&nbsp; 分组: 110110 111100 101011 101100
&nbsp; 索引: 54, 60, 43, 44
&nbsp; 标准: 2, 8, r, s
&nbsp; 自定义: 2, d, r, e

第4组(3字节): 83 C0 F9
&nbsp; 二进制: 10000011 11000000 11111001
&nbsp; 分组: 100000 111100 000011 111001
&nbsp; 索引: 32, 60, 3, 57
&nbsp; 标准: g, 8, D, 5
&nbsp; 自定义: g, d, y, u

拼接: P+E+D+I + y+9+1+0 + 2+d+r+e + g+d+y+u
&nbsp; &nbsp; = PEDIy9102dreadyu

输出结果

编码结果: PEDIy9102dreadyu

注意到结果中包含了”readyu”,这正是文件名中的ID!

5.5 生成最终序列号

题目规则只允许使用字母和数字,不能使用特殊字符(如’-‘)。

分隔符的选择

  • 原本想用:PEDIy-9102d-readyu
  • 实际使用:PEDIyV9102dVreadyu(用’V’作为分隔符)
sn_clean = sn.rstrip('=') &nbsp;# 移除可能的填充字符
print(f"Base64编码:&nbsp;{sn_clean}")

# 在特定位置插入V分隔符
# PEDIy9102dreadyu → PEDIyV9102dVreadyu
# 分段:PEDIy + V + 9102d + V + readyu (5-5-6)
final_code = sn_clean[:5] +&nbsp;'V'&nbsp;+ sn_clean[5:10] +&nbsp;'V'&nbsp;+ sn_clean[10:]
print(f"最终序列号:&nbsp;{final_code}")

输出

Base64编码: PEDIy9102dreadyu
最终序列号: PEDIyV9102dVreadyu

六、验证机制分析

6.1 程序验证流程

当用户输入序列号后,crackme程序执行以下验证:

┌─────────────────────────────────────────┐
│ 1. 输入CODE:PEDIyV9102dVreadyu &nbsp; &nbsp; &nbsp; &nbsp;│
└─────────────┬───────────────────────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼
┌─────────────────────────────────────────┐
│ 2. 格式检查:只包含字母和数字? &nbsp; &nbsp; &nbsp; &nbsp; │
│ &nbsp; &nbsp;不通过 → 错误提示 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
└─────────────┬───────────────────────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ ✓
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼
┌─────────────────────────────────────────┐
│ 3. 去除分隔符V:PEDIy9102dreadyu &nbsp; &nbsp; &nbsp; │
└─────────────┬───────────────────────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼
┌─────────────────────────────────────────┐
│ 4. 自定义Base64解码 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
│ &nbsp; &nbsp;→ 得到M = 6602940601029543050476... │
└─────────────┬───────────────────────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼
┌─────────────────────────────────────────┐
│ 5. 构造N(生成3到73的素数乘积) &nbsp; &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp;N = 20364840299624512075310661735 &nbsp; &nbsp;│
└─────────────┬───────────────────────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼
┌─────────────────────────────────────────┐
│ 6. 范围检查:2 ≤ M < N ? &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp;不通过 → 错误提示 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
└─────────────┬───────────────────────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ ✓
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼
┌─────────────────────────────────────────┐
│ 7. 计算 X = M^83 mod N &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
└─────────────┬───────────────────────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼
┌─────────────────────────────────────────┐
│ 8. 位长检查:X的比特长度 < 32 ? &nbsp; &nbsp; &nbsp; │
│ &nbsp; &nbsp;否 → "sn doesn't work!" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
└─────────────┬───────────────────────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ ✓
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ▼
┌─────────────────────────────────────────┐
│ 9. 值检查:X == 2 ? &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp;是 → "yes, correct sn!" ✓ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp;否 → "no, wrong sn!!!!" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
└─────────────────────────────────────────┘

6.2 为什么检查位长?

程序限制X的位长必须小于32位,这是一个安全检查

if&nbsp;X.bit_length() >=&nbsp;32:
&nbsp; &nbsp; print("sn doesn't work!")
&nbsp; &nbsp;&nbsp;return&nbsp;False

原因分析

  1. 防止大数溢出:如果X很大,转换为32位整数会溢出
  2. 快速过滤:大部分错误输入会产生很大的X值,可以快速排除
  3. 唯一性保证:正确的M应该使得X = 2,而2只需2个比特

6.3 实现验证程序

我们可以用Python实现完整的验证逻辑:

def&nbsp;verify_code(code):
&nbsp; &nbsp;&nbsp;"""验证CODE的正确性"""

&nbsp; &nbsp; print(f"输入的CODE:&nbsp;{code}")

&nbsp; &nbsp;&nbsp;# 步骤1: 移除分隔符V
&nbsp; &nbsp; sn = code.replace('V',&nbsp;'')
&nbsp; &nbsp; print(f"移除分隔符后:&nbsp;{sn}")

&nbsp; &nbsp;&nbsp;# 步骤2: 自定义Base64解码
&nbsp; &nbsp; standard_table =&nbsp;"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
&nbsp; &nbsp; custom_table =&nbsp;"ABCyVPGHTJKLMNOFQRSIUEWDYZgbc8sfah1jklmnopqret5v0xX9wi234u67dz+/"

&nbsp; &nbsp;&nbsp;# 转换为标准Base64
&nbsp; &nbsp; standard_sn =&nbsp;''
&nbsp; &nbsp;&nbsp;for&nbsp;c&nbsp;in&nbsp;sn:
&nbsp; &nbsp; &nbsp; &nbsp; idx = custom_table.index(c)
&nbsp; &nbsp; &nbsp; &nbsp; standard_sn += standard_table[idx]

&nbsp; &nbsp;&nbsp;# 添加填充
&nbsp; &nbsp;&nbsp;while&nbsp;len(standard_sn) %&nbsp;4&nbsp;!=&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; standard_sn +=&nbsp;'='

&nbsp; &nbsp;&nbsp;# 解码
&nbsp; &nbsp;&nbsp;from&nbsp;base64&nbsp;import&nbsp;b64decode
&nbsp; &nbsp; decoded_bytes = b64decode(standard_sn)
&nbsp; &nbsp; M = int(decoded_bytes.hex(),&nbsp;16)
&nbsp; &nbsp; print(f"解码得到M:&nbsp;{M}")

&nbsp; &nbsp;&nbsp;# 步骤3: 构造N
&nbsp; &nbsp; N =&nbsp;1
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(3,&nbsp;79):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;is_prime(i):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; N *= i
&nbsp; &nbsp; print(f"N =&nbsp;{N}")

&nbsp; &nbsp;&nbsp;# 步骤4: 范围检查
&nbsp; &nbsp;&nbsp;if&nbsp;M <&nbsp;2&nbsp;or&nbsp;M >= N:
&nbsp; &nbsp; &nbsp; &nbsp; print("✗ M不在有效范围")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp; print("✓ M在有效范围内")

&nbsp; &nbsp;&nbsp;# 步骤5: 计算X = M^83 mod N
&nbsp; &nbsp; e =&nbsp;83
&nbsp; &nbsp; X = pow(M, e, N)
&nbsp; &nbsp; print(f"X = M^{e}&nbsp;mod N =&nbsp;{X}")

&nbsp; &nbsp;&nbsp;# 步骤6: 位长检查
&nbsp; &nbsp;&nbsp;if&nbsp;X.bit_length() >=&nbsp;32:
&nbsp; &nbsp; &nbsp; &nbsp; print("✗ X的位长过大")
&nbsp; &nbsp; &nbsp; &nbsp; print("提示: sn doesn't work!")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False

&nbsp; &nbsp;&nbsp;# 步骤7: 值检查
&nbsp; &nbsp;&nbsp;if&nbsp;X ==&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; print("✓ X == 2")
&nbsp; &nbsp; &nbsp; &nbsp; print("提示: yes, correct sn!")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;True
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; print(f"✗ X =&nbsp;{X}&nbsp;≠ 2")
&nbsp; &nbsp; &nbsp; &nbsp; print("提示: no, wrong sn!!!!")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False

# 测试
result = verify_code("PEDIyV9102dVreadyu")

运行结果

输入的CODE: PEDIyV9102dVreadyu
移除分隔符后: PEDIy9102dreadyu
解码得到M: 6602940601029543050476765433
N = 20364840299624512075310661735
✓ M在有效范围内
X = M^83 mod N = 2
✓ X == 2
提示: yes, correct sn!

七、完整解题代码

7.1 求解脚本(solve.py)

#!/usr/bin/env python3
"""
青梅竹马 CTF Crypto Challenge - 完整解题脚本
"""

def&nbsp;is_prime(n):
&nbsp; &nbsp;&nbsp;"""判断是否为素数"""
&nbsp; &nbsp;&nbsp;if&nbsp;n <&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;if&nbsp;n ==&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;True
&nbsp; &nbsp;&nbsp;if&nbsp;n %&nbsp;2&nbsp;==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(3, int(n**0.5) +&nbsp;1,&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;n % i ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;return&nbsp;True

def&nbsp;extended_gcd(a, b):
&nbsp; &nbsp;&nbsp;"""扩展欧几里得算法"""
&nbsp; &nbsp;&nbsp;if&nbsp;a ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;b,&nbsp;0,&nbsp;1
&nbsp; &nbsp; gcd, x1, y1 = extended_gcd(b % a, a)
&nbsp; &nbsp; x = y1 - (b // a) * x1
&nbsp; &nbsp; y = x1
&nbsp; &nbsp;&nbsp;return&nbsp;gcd, x, y

def&nbsp;mod_inverse(e, phi):
&nbsp; &nbsp;&nbsp;"""求模逆元"""
&nbsp; &nbsp; gcd, x, y = extended_gcd(e, phi)
&nbsp; &nbsp;&nbsp;if&nbsp;gcd !=&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;Exception("模逆元不存在")
&nbsp; &nbsp;&nbsp;return&nbsp;(x % phi + phi) % phi

def&nbsp;custom_base64_encode(data_hex):
&nbsp; &nbsp;&nbsp;"""自定义Base64编码"""
&nbsp; &nbsp; standard_table =&nbsp;"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
&nbsp; &nbsp; custom_table =&nbsp;"ABCyVPGHTJKLMNOFQRSIUEWDYZgbc8sfah1jklmnopqret5v0xX9wi234u67dz+/"

&nbsp; &nbsp; data_bytes = bytes.fromhex(data_hex)
&nbsp; &nbsp; result = []

&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, len(data_bytes),&nbsp;3):
&nbsp; &nbsp; &nbsp; &nbsp; chunk = data_bytes[i:i+3]
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;b&nbsp;in&nbsp;chunk:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; n = (n <<&nbsp;8) | b

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(chunk) ==&nbsp;3:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; indices = [
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (n >>&nbsp;18) &&nbsp;0x3F,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (n >>&nbsp;12) &&nbsp;0x3F,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (n >>&nbsp;6) &&nbsp;0x3F,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; n &&nbsp;0x3F
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;idx&nbsp;in&nbsp;indices:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result.append(standard_table[idx])

&nbsp; &nbsp; standard_encoded =&nbsp;''.join(result)
&nbsp; &nbsp; custom_encoded =&nbsp;''
&nbsp; &nbsp;&nbsp;for&nbsp;c&nbsp;in&nbsp;standard_encoded:
&nbsp; &nbsp; &nbsp; &nbsp; idx = standard_table.index(c)
&nbsp; &nbsp; &nbsp; &nbsp; custom_encoded += custom_table[idx]

&nbsp; &nbsp;&nbsp;return&nbsp;custom_encoded

def&nbsp;main():
&nbsp; &nbsp; print("="*70)
&nbsp; &nbsp; print("青梅竹马 CTF Crypto Challenge - 完整解题")
&nbsp; &nbsp; print("="*70)
&nbsp; &nbsp; print()

&nbsp; &nbsp;&nbsp;# 步骤1: 生成素数
&nbsp; &nbsp; primes = [i&nbsp;for&nbsp;i&nbsp;in&nbsp;range(2,&nbsp;100)&nbsp;if&nbsp;is_prime(i)]
&nbsp; &nbsp; print(f"[1] 100以内素数共&nbsp;{len(primes)}&nbsp;个")

&nbsp; &nbsp;&nbsp;# 步骤2: 构造N
&nbsp; &nbsp; N =&nbsp;1
&nbsp; &nbsp;&nbsp;for&nbsp;p&nbsp;in&nbsp;primes:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;p ==&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;p >=&nbsp;79:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; N *= p
&nbsp; &nbsp; print(f"[2] N =&nbsp;{N}")

&nbsp; &nbsp;&nbsp;# 步骤3: 计算φ(N)
&nbsp; &nbsp; phi_N =&nbsp;1
&nbsp; &nbsp;&nbsp;for&nbsp;p&nbsp;in&nbsp;primes:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;p ==&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;p >=&nbsp;79:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; phi_N *= (p -&nbsp;1)
&nbsp; &nbsp; print(f"[3] φ(N) =&nbsp;{phi_N}")

&nbsp; &nbsp;&nbsp;# 步骤4: 求私钥d
&nbsp; &nbsp; e =&nbsp;83
&nbsp; &nbsp; d = mod_inverse(e, phi_N)
&nbsp; &nbsp; print(f"[4] e =&nbsp;{e}, d =&nbsp;{d}")

&nbsp; &nbsp;&nbsp;# 步骤5: 计算M
&nbsp; &nbsp; M = pow(2, d, N)
&nbsp; &nbsp; print(f"[5] M =&nbsp;{M}")

&nbsp; &nbsp;&nbsp;# 步骤6: 验证
&nbsp; &nbsp; verification = pow(M, e, N)
&nbsp; &nbsp; print(f"[6] 验证: M^{e}&nbsp;mod N =&nbsp;{verification}&nbsp;{'✓'&nbsp;if&nbsp;verification ==&nbsp;2&nbsp;else&nbsp;'✗'}")

&nbsp; &nbsp;&nbsp;# 步骤7: 转16进制
&nbsp; &nbsp; M_hex = hex(M)[2:].upper()
&nbsp; &nbsp; print(f"[7] M (hex) =&nbsp;{M_hex}")

&nbsp; &nbsp;&nbsp;# 步骤8: Base64编码
&nbsp; &nbsp; sn = custom_base64_encode(M_hex)
&nbsp; &nbsp; print(f"[8] Base64编码 =&nbsp;{sn}")

&nbsp; &nbsp;&nbsp;# 步骤9: 添加分隔符
&nbsp; &nbsp; final_code = sn[:5] +&nbsp;'V'&nbsp;+ sn[5:10] +&nbsp;'V'&nbsp;+ sn[10:]
&nbsp; &nbsp; print(f"[9] 最终序列号 =&nbsp;{final_code}")

&nbsp; &nbsp; print()
&nbsp; &nbsp; print("="*70)
&nbsp; &nbsp; print(f"✓✓✓ 答案:&nbsp;{final_code}&nbsp;✓✓✓")
&nbsp; &nbsp; print("="*70)

&nbsp; &nbsp;&nbsp;return&nbsp;final_code

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

7.2 运行结果

======================================================================
青梅竹马 CTF Crypto Challenge - 完整解题
======================================================================

[1] 100以内素数共 25 个
[2] N = 20364840299624512075310661735
[3] φ(N) = 5133855159158901099724800000
[4] e = 83, d = 1855610298491169072189686747
[5] M = 6602940601029543050476765433
[6] 验证: M^83 mod N = 2 ✓
[7] M (hex) = 1555D30F38B0DBCAEC83C0F9
[8] Base64编码 = PEDIy9102dreadyu
[9] 最终序列号 = PEDIyV9102dVreadyu

======================================================================
✓✓✓ 答案: PEDIyV9102dVreadyu ✓✓✓
======================================================================

八、技术要点深入解析

8.1 RSA安全性分析

为什么RSA通常是安全的?

  1. 大整数分解困难
  • 在实际应用中,N是两个大素数的乘积(通常2048位或更长)
  • 已知N,分解出p和q在计算上是不可行的
  • 目前没有已知的多项式时间算法能分解大整数
  1. 计算φ(N)困难
  • 不知道p和q,就无法直接计算φ(N)
  • φ(N) = (p-1)(q-1) 需要知道素因子
  1. 求私钥d困难
  • 不知道φ(N),就无法求出d = e⁻¹ mod φ(N)

这道题为什么不安全?

  1. N可分解
  • N是多个小素数的乘积
  • 可以轻松枚举出所有素因子
  1. φ(N)可计算
  • 知道所有素因子,直接计算φ(N)
  1. 私钥可求
  • 有了φ(N),用扩展欧几里得算法求出d

启示:RSA的安全性完全依赖于N的不可分解性!

8.2 为什么使用快速幂模算法?

在计算 2^d mod N 时,d是一个非常大的数:

d = 1855610298491169072189686747

朴素算法的问题

# 朴素算法(错误示范)
result =&nbsp;1
for&nbsp;i&nbsp;in&nbsp;range(d): &nbsp;# 需要循环10^27次!
&nbsp; &nbsp; result = (result *&nbsp;2) % N

这需要约 10^27 次运算,即使每秒进行10^9次运算,也需要10^18秒(约300亿年)!

快速幂算法

基本思想:将指数写成二进制,利用:

a^13 = a^(1101₂) = a^8 × a^4 × a^1
def&nbsp;fast_pow_mod(base, exp, mod):
&nbsp; &nbsp;&nbsp;"""快速幂模算法"""
&nbsp; &nbsp; result =&nbsp;1
&nbsp; &nbsp; base = base % mod

&nbsp; &nbsp;&nbsp;while&nbsp;exp >&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;exp %&nbsp;2&nbsp;==&nbsp;1: &nbsp;# 如果exp是奇数
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = (result * base) % mod
&nbsp; &nbsp; &nbsp; &nbsp; exp = exp >>&nbsp;1&nbsp; &nbsp;&nbsp;# exp除以2
&nbsp; &nbsp; &nbsp; &nbsp; base = (base * base) % mod

&nbsp; &nbsp;&nbsp;return&nbsp;result

时间复杂度

  • 朴素算法:O(exp)
  • 快速幂:O(log exp)

对于 d = 1855610298491169072189686747:

  • log₂(d) ≈ 90
  • 只需约90次运算!

Python内置实现

# Python的pow函数内部使用快速幂模算法
result = pow(2, d, N) &nbsp;# 非常快速

8.3 Base64编码的数学原理

为什么是3字节→4字符?

输入:3字节 = 24位
输出:4字符,每字符表示6位

24位 ÷ 6位/字符 = 4字符

编码效率

原始数据:n字节
编码后:⌈4n/3⌉字符
膨胀率:约33%

为什么选择6位分组?

  • 2^6 = 64,正好64个字符
  • 6位是24(3×8)的因子,便于整除
  • 生成的字符都是可打印的ASCII字符

8.4 欧拉定理的证明思路

定理:如果 gcd(a, n) = 1,则 a^φ(n) ≡ 1 (mod n)

证明思路(针对n为素数乘积的情况):

设 n = p₁ × p₂ × … × pₖ(素数)

  1. 费马小定理:对于素数p,a^(p-1) ≡ 1 (mod p)
  2. 推广
  • a^φ(p₁) ≡ 1 (mod p₁)
  • a^φ(p₂) ≡ 1 (mod p₂)
  1. 中国剩余定理:如果对所有素数pi都有 a^φ(n) ≡ 1 (mod pi), 则 a^φ(n) ≡ 1 (mod n)

这个定理是RSA正确性的数学保证。

九、解题技巧总结

9.1 CTF密码学题通用思路

1. 识别算法
&nbsp; &nbsp;├─ RSA特征:大数、模运算、公钥/私钥
&nbsp; &nbsp;├─ AES特征:块加密、轮函数
&nbsp; &nbsp;└─ 其他经典算法特征

2. 寻找弱点
&nbsp; &nbsp;├─ 参数选择不当(如小素数)
&nbsp; &nbsp;├─ 实现错误
&nbsp; &nbsp;└─ 侧信道信息泄露

3. 利用数学工具
&nbsp; &nbsp;├─ 数论:gcd、模逆元、欧拉定理
&nbsp; &nbsp;├─ 代数:群、环、域
&nbsp; &nbsp;└─ 概率统计

4. 编写脚本验证
&nbsp; &nbsp;├─ Python:sympy、pycrypto
&nbsp; &nbsp;├─ SageMath:强大的数学工具
&nbsp; &nbsp;└─ 在线工具:factordb、RSA calculator

9.2 逆向分析技巧

1. 静态分析
&nbsp; &nbsp;├─ strings:查找字符串
&nbsp; &nbsp;├─ objdump/IDA:反汇编
&nbsp; &nbsp;└─ 识别加密常数(如S盒)

2. 动态分析
&nbsp; &nbsp;├─ 调试器:gdb、x64dbg
&nbsp; &nbsp;├─ 追踪:ltrace、strace
&nbsp; &nbsp;└─ Hook:Frida、DBI

3. 符号执行
&nbsp; &nbsp;├─ angr
&nbsp; &nbsp;└─ Z3约束求解

4. 模式识别
&nbsp; &nbsp;├─ 识别库函数
&nbsp; &nbsp;├─ 识别算法特征
&nbsp; &nbsp;└─ 对比已知实现

9.3 本题关键点回顾

  1. 文化线索:”青梅竹马”→”两小无猜”→2
  2. 数学建模:识别RSA变体,建立幂模方程
  3. 参数确定:分析为何排除2、为何在79截止
  4. 算法实现:正确实现扩展欧几里得、快速幂模
  5. 编码转换:理解自定义Base64的设计意图

十、拓展思考

10.1 如何加固这个系统?

如果要让这个验证系统更安全:

  1. 使用大素数
   # 使用2048位的素数
   p = generate_large_prime(1024)
   q = generate_large_prime(1024)
   N = p * q
  1. 隐藏φ(N)
  • 不公开N的因子
  • 使用真正的大素数,使分解不可行
  1. 选择合适的e
  • 通常选择 e = 65537(2^16 + 1)
  • 既保证安全性,又便于快速加密
  1. 添加填充
  • 使用OAEP等填充方案
  • 防止数学攻击

10.2 实际应用中的RSA

密钥长度建议

  • 2048位:目前主流,安全期约到2030年
  • 3072位:长期安全
  • 4096位:极高安全性,但计算开销大

RSA的应用场景

  1. 数字签名:证明消息的真实性和完整性
  2. 密钥交换:在TLS/SSL中协商对称密钥
  3. 身份认证:SSH公钥认证

RSA的局限性

  1. 加密速度慢:通常只用于加密对称密钥
  2. 密钥长度大:相比椭圆曲线加密(ECC)
  3. 量子威胁:量子计算机可能破解RSA

10.3 从这道题学到什么?

密码学原理

  • RSA的数学基础(欧拉函数、模逆元)
  • 加密与解密的对称性
  • 参数选择对安全性的影响

编程技巧

  • 大整数运算
  • 快速幂模算法
  • Base64编码实现

逆向思维

  • 从题目名称挖掘线索
  • 分析魔术数字的含义
  • 理解算法意图

实践能力

  • 完整实现一个密码系统
  • 验证和调试数学算法
  • 编写清晰的技术文档

十一、总结

这道”青梅竹马”题目是一道设计精巧的密码学题目,它完美地融合了:

技术层面

  • RSA密码学原理:从密钥生成到加解密的完整流程
  • 数论知识:素数、欧拉函数、模逆元、欧拉定理
  • 编码技术:自定义Base64实现
  • 算法优化:快速幂模算法

文化层面

  • 成语隐喻:”青梅竹马”→”两小无猜”
  • 数字寓意:”两”指数字2,”小”指小素数
  • 趣味性:在技术中融入文化元素

教育意义

  • 理论与实践结合:从数学推导到代码实现
  • 安全意识培养:理解什么使密码系统安全/不安全
  • 解决问题的方法论:分析、建模、实现、验证

最终答案

序列号(Serial Number): PEDIyV9102dVreadyu
验证提示: yes, correct sn!

完整验证

  • M = 6602940601029543050476765433
  • M^83 mod N = 2 ✓
  • 所有数学验证通过 ✓

参考资料

  • [1] RSA算法原理与应用
  • [2] 数论基础:欧拉函数与欧拉定理
  • [3] Base64编码标准(RFC 4648)
  • [4] 密码学:原理与实践
  • [5] CTF密码学题解题思路

作者寄语

密码学是一门美丽的学科,它将抽象的数学理论应用于实际的信息安全。希望这篇文章能帮助大家:

  • 理解RSA的工作原理
  • 掌握基本的密码学分析方法
  • 培养对安全问题的思考
  • 享受解题的乐趣

记住:密码学的安全性,永远建立在数学的坚实基础之上。


本文为技术研究分享,仅用于学习交流。


查看原文:《青梅竹马:一道融合密码学与文化的CTF逆向题深度解析》

评论:0   参与:  0