Crypto之M>N的特殊rsa

admin 2026-03-18 22:57:57 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档附带战队招新信息,核心解析2026年hgame中babyrsa题目解法。针对RSA加密中明文m大于模数n的情形,作者提出利用已知明文前后缀将问题转化为隐数问题HNP,并构造格运用LLL算法求解未知部分。文章包含详细推导与完整SageMath脚本,技术含量高且实战性强。 综合评分: 90 文章分类: CTF,解决方案,实战经验


cover_image

Crypto之M>N的特殊rsa

yumhaoxouhymxmuy yumhaoxouhymxmuy

OnePanda-Sec

2026年3月13日 11:05 浙江

招新

OnePanda-Sec

-招新说明-

**招新要求

· 热爱网络安全,喜欢CTF

· 拥有CTF比赛经验,有较好比赛成绩的

· 乐于奉献、热爱分享,愿意提升   自己同时帮助他人

· 时间允许参加各类赛事,服从战队管理与安排

· 各类比赛获奖者、能力出众者视情况考量

· 未参与其他高校联队

· 大一同学视情况放宽资历要求**

联系方式

发送简历于邮箱

· 简历邮箱:[email protected]

题目信息:

题目名称:babyrsa(来自2026年hgame)

题目类型:Crypto

附件:task.py

Task.py:rsa的加密过程,给出了flag的格式,以及flag的大小

第一步:阅读代码

k = randint(30, 40)str = string.digits + string.ascii_letters + "_@"flag = b"VIDAR{" + "".join([choice(str) for i in range(k)]).encode() + b"}"

1.生成一个30 到 40 之间的随机整数 k,用来指定flag 中 VIDAR{} 中间部分的字符长度。

2.拼接字符集:包含数字(0-9)、大小写字母(a-z/A-Z)、下划线 _ 和@,后续用来随机生成flag 内容。

3.构造 flag 字节串:

choice(str):从字符集str 中随机选一个字符;

[choice(str) for i in range(k)]:生成k 个随机字符的列表;

“”.join(…):将列表转为字符串;

.encode():将字符串转为字节串(如”123″ → b”123″);

最终flag 格式为 b”VIDAR{随机字符}”,比如b”VIDAR{8a9_7B@89s…}”(中间30-40 个随机字符)。

可以得出flag是37-47个字约为296-376位

p = getPrime(120)q = getPrime(120)n = p * q

n为240位

第二步:分析给的信息

因为在加密或者解密的过程中会modN,那么m大于以后就会减掉k倍的N

那么对于解决这道题的关键就是找到m1,然后通过m//n(k)*n+m1就可以得到flag

而m1的值话则可以通过这样得到:

加密:c=m^e%n → 根据模运算性质,等价于 c = (m1)^e % n

解密:c^d %n=m

而在m>n,则

c^d % n = (m1^e % n)^d % n = m1^(e×d) % n

当0 ≤ m1 < n 时,m1^(e×d) ≡ m1 (mod n)(这是欧拉定理的推论);

因此最终解密结果是:c^d % n = m1 = m % n。

下面我写了一个模拟加密过程的,给大家演示

from&nbsp;Crypto.Util.number&nbsp;import&nbsp;*from&nbsp;gmpy2&nbsp;import&nbsp;*from&nbsp;random&nbsp;import&nbsp;*import&nbsp;stringk = randint(30,&nbsp;40)str&nbsp;= string.digits + string.ascii_letters +&nbsp;"_@"flag =&nbsp;b"VIDAR{"&nbsp;+&nbsp;"".join([choice(str)&nbsp;for&nbsp;i&nbsp;in&nbsp;range(k)]).encode() +&nbsp;b"}"p = getPrime(120)q = getPrime(120)n = p * qe =&nbsp;65537phi = (p-1)*(q-1)d =&nbsp;pow(e,-1,phi)m = bytes_to_long(flag)print("m=",m)c =&nbsp;pow(m, e, n)print(f'c =&nbsp;{c}')print(f'p =&nbsp;{p}')print(f'q =&nbsp;{q}')print("n=",n)print("d=",d)

结果:

m = 47182387137031888683193786631811481680018945054280589140335017056651965465398746556476496557542234493c = 271530001392968364132741416971348442690719532783738504885895356571604188p = 1105098843875526305577012916655789761q = 1003359477327253282628279227971265709n = 1108811398385899951350476094717464147948849489769168722713438298172605549d = 356835698969391578405067233985169237223548379331274138820991356400646913e = 65537&nbsp;m1 = pow(c,d,n)print(m1)m1 = 973962589215066063395061466358659236539972501198758474963830687022499469print(m1==m%n)print(m//n)print(m//n*n+m1==m)

这是一个简单的m>n的展示过程

第三步:回到题目解决问题

我们由于 k 的范围很大,导致m//n的取值范围过大,我们没办法用上面的那办法去爆破k的直接,那么我们就需要用已知明文来求解这道题。

我们一直前缀和后缀,那么只需知道中间的就可以求解了,这就可以转化为一个隐数问题,利用 LLL 算法求解。

设A为前缀B为后缀C为中间未知部分,L为中间部分的比特长度,则明文结构为:

m=A⋅2^(L+8)+C⋅2^8+B

其中2^(L+8) = 把前缀往前挪多少位,让它刚好放在整个 flag 最前面的位置。

由于m≡m1(modn),代入明文结构得:

A⋅2^(L+8)+C⋅2^8+B = m1(mod n)

整理后可解出未知部分C 的同余关系(2^8=256):

C=(m1−A⋅2^(L+8)−B)⋅256^−1(mod n)

到这里,我们得到了一个非常关键的式子:

C≡T(modn)

其中T=(m1−A⋅2^(L+8)−B)⋅256^−1modn

这个式子告诉我们一件事:

未知的中间部分C,在模 n 下等于一个我们完全能算出来的数 Target。

但问题还没结束:满足C ≡ Target (mod n) 的 C 有无数个,比如:

C = Target

C = Target + n

C = Target + 2n

我们怎么知道哪一个才是真正的flag 中间部分?

关键:C 不是随便的大数

C 是一串可见字符:数字、大小写字母、_、@

每一个字符都是1 字节,也就是 0~255 之间的一个小数字。

如果把C 拆成一个个字节:

C=c0·256^(k−1)+c1·256^(k−2)+……+ck−1

这个公式就就相当于123的十进制:

123 = 1×10² + 2×10¹ + 3×10⁰

那么每一个ci都很小(只是一个 ASCII 码),而不是像 n 那样几百位的超大数。

这就是为什么要用LLL 格基算法

我们的问题变成:

在所有满足c0·256k−1+c1·256k−2+……+ck−1=Target(modn)的解里面,

找到一组c0~cK-1,每一个都很小(是合法字符)。

这种“在同余方程里找一组很小的未知数” 的问题,就是标准的 隐数问题,正是 LLL 格基规约算法 最擅长解决的。

下面就是构造格M的问题: M = [1   0   …  0  C0·W   80

0 1   …  0  C1·W   80

⋮⋮ ⋱ ⋮  ⋮

0 0   …  1  CK-1·W  80

0 0   …  0  n·W     0

0 0   …  0  T·W     1]

对矩阵 M 进行 LLL 规约,寻找最短向量。如果构造正确,最短向量的前 k 个分量即为 ci − 80 的差值,从而恢复出 C。

最终脚本:

from&nbsp;sage.all&nbsp;import&nbsp;*import&nbsp;string# 题目数据encrypted_flag =&nbsp;451420045234442273941376910979916645887835448913611695130061067762180161prime1 =&nbsp;722243413239346736518453990676052563prime2 =&nbsp;777452004761824304315754169245494387encryption_exp =&nbsp;65537from&nbsp;sage.all&nbsp;import&nbsp;*import&nbsp;string# 题目数据encrypted_flag =&nbsp;451420045234442273941376910979916645887835448913611695130061067762180161prime1 =&nbsp;722243413239346736518453990676052563prime2 =&nbsp;777452004761824304315754169245494387encryption_exp =&nbsp;65537# 计算模数和私钥modulus = prime1 * prime2phi = (prime1 -&nbsp;1) * (prime2 -&nbsp;1)private_key = inverse_mod(encryption_exp, phi)# 解密得到数字形式的明文decrypted_num = power_mod(encrypted_flag, private_key, modulus)# 定义合法字符allowed_chars = string.digits + string.ascii_letters +&nbsp;"_@"char_codes = {ord(c)&nbsp;for&nbsp;c&nbsp;in&nbsp;allowed_chars}def&nbsp;recover_flag_from_hnp(key_length):&nbsp; &nbsp;flag_prefix =&nbsp;b"VIDAR{"&nbsp; &nbsp;flag_suffix =&nbsp;b"}"&nbsp; &nbsp;# 计算需要处理的比特数&nbsp; &nbsp;total_byte_len = key_length +&nbsp;len(flag_suffix)&nbsp; &nbsp;bit_shift = total_byte_len *&nbsp;8&nbsp; &nbsp;&nbsp; &nbsp;# 构建前缀和后缀的数值表示&nbsp; &nbsp;prefix_val = Integer(int.from_bytes(flag_prefix,&nbsp;'big')) * (Integer(2)**bit_shift)&nbsp; &nbsp;suffix_val = Integer(int.from_bytes(flag_suffix,&nbsp;'big'))&nbsp; &nbsp;&nbsp; &nbsp;# 计算目标值(中间部分)&nbsp; &nbsp;middle_val = (decrypted_num - prefix_val - suffix_val) % modulus&nbsp; &nbsp;inv_256 = inverse_mod(256, modulus)&nbsp; &nbsp;target_val = (middle_val * inv_256) % modulus&nbsp; &nbsp;&nbsp; &nbsp;# 构建格&nbsp; &nbsp;lattice_dim = key_length +&nbsp;2&nbsp; &nbsp;# 创建零矩阵&nbsp; &nbsp;lattice = matrix(ZZ, lattice_dim, lattice_dim)&nbsp; &nbsp;&nbsp; &nbsp;# 使用nbits()获取位数&nbsp; &nbsp;weight_factor =&nbsp;2&nbsp;** (modulus.nbits() +&nbsp;10)ascii_mean =&nbsp;80&nbsp; &nbsp;&nbsp; &nbsp;# 填充变量部分&nbsp; &nbsp;for&nbsp;idx&nbsp;in&nbsp;range(key_length):&nbsp; &nbsp; &nbsp; &nbsp;lattice[idx, idx] =&nbsp;1&nbsp; &nbsp; &nbsp; &nbsp;power_factor = power_mod(256, key_length -&nbsp;1&nbsp;- idx, modulus)&nbsp; &nbsp; &nbsp; &nbsp;lattice[idx, key_length] = power_factor * weight_factor&nbsp; &nbsp; &nbsp; &nbsp;lattice[key_length +&nbsp;1, idx] = ascii_mean&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp;# 模数约束行&nbsp; &nbsp;lattice[key_length, key_length] = modulus * weight_factor&nbsp; &nbsp;&nbsp; &nbsp;# 目标值行&nbsp; &nbsp;lattice[key_length +&nbsp;1, key_length] = target_val * weight_factor&nbsp; &nbsp;lattice[key_length +&nbsp;1, key_length +&nbsp;1] =&nbsp;1&nbsp; &nbsp;&nbsp; &nbsp;# 应用LLL算法&nbsp; &nbsp;reduced_basis = lattice.LLL()&nbsp; &nbsp;&nbsp; &nbsp;# 检查每一行寻找有效解&nbsp; &nbsp;for&nbsp;basis_vector&nbsp;in&nbsp;reduced_basis:&nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;abs(basis_vector[key_length +&nbsp;1]) !=&nbsp;1:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;continue&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;multiplier = -basis_vector[key_length +&nbsp;1]&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;candidate_chars = []&nbsp; &nbsp; &nbsp; &nbsp;is_valid =&nbsp;True&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;for&nbsp;idx&nbsp;in&nbsp;range(key_length):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;char_val = basis_vector[idx] * multiplier + ascii_mean&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 转换为Python整数进行比较&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;char_val_int =&nbsp;int(char_val)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;char_val_int&nbsp;not&nbsp;in&nbsp;char_codes:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;is_valid =&nbsp;False&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;break&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;candidate_chars.append(char_val_int)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;is_valid:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return&nbsp;flag_prefix +&nbsp;bytes(candidate_chars) + flag_suffix&nbsp; &nbsp;return&nbsp;Nonefor&nbsp;possible_length&nbsp;in&nbsp;range(30,&nbsp;41):&nbsp; &nbsp; result = recover_flag_from_hnp(possible_length)&nbsp; &nbsp;&nbsp;if&nbsp;result&nbsp;is&nbsp;not&nbsp;None:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"成功恢复Flag:&nbsp;{result.decode('utf-8')}")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;breakelse:&nbsp; &nbsp;&nbsp;print("未找到有效的Flag")

OnePandaSec团队交流群,欢迎网络安全爱好者加入


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:OnePanda-Sec yumhaoxouhymxmuy yumhaoxouhymxmuy《Crypto之M>N的特殊rsa》

Crypto之M>N的特殊rsa 网络安全文章

Crypto之M>N的特殊rsa

文章总结: 文档附带战队招新信息,核心解析2026年hgame中babyrsa题目解法。针对RSA加密中明文m大于模数n的情形,作者提出利用已知明文前后缀将问题
评论:0   参与:  0