原由
RSA是CTF密码学中非常常考的题目,前一阵配置服务器密钥登录的时候,也涉及到了公钥私钥,于是研究了一下
理论
RSA涉及到了一些数学概念性的东西
1.因数:其实就是小学学的乘数
2.质数:除了1乘自己,没有其它两个整数相乘能得到的数
3.余数:除到个位余下的数,小于除数大于零
4.欧拉函数:我也不知道是什么无伤大雅,名字挺高级挺唬人的
参数
p,q是两个质数
N是两个质数的积(n=p*q)
T是欧拉函数(p-1)*(q-1)
E是公钥 公钥是质数,并且公钥在1和T之间, 正常默认:e=65537
D是私钥 (D*E)%T=1
公式
加密: c=m^E%N
将明文的公钥次方模N得到密文
解密: m=c^D%N
将密文的私钥次方模N得到明文
脚本实现
pow()函数用法
RSA求私钥(求逆元)
pow(E,-1,T)
(D*E)%T=1,求D
RSA加解密
解密:c^D%N
pow(c,D,N)
加密:m^E%N
pow(m,E,N)
解密脚本
p=
q=
e=
c=
N=p*q
T=(p-1)*(q-1)
D=pow(E,-1,T)
m=pow(c,D,N)
print(m)
gmpy2库
RSA涉及的数一般比较大,常规算法会出现精度的偏差,推荐使用gmpy2库
安装
pip install gmpy2
# 如果官方源下载失败或者下载慢,可使用其它源
pip install gmpy2 -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
常用参数
mpz
作用:把超大整数转成 gmpy2 安全类型
用法:
N = gmpy2.mpz(N)
c = gmpy2.mpz(c)
isqrt
作用:整数开平方,分解 N=p*q 专用
S = gmpy2.isqrt( (p-q)*(p-q) + 4*N )
p = ((p+q) + (p-q)) // 2
q = ((p+q) - (p-q) // 2
gcd
作用:求两个数最大公约数个 n1,n2 直接 gcd(n1,n2) 出 p
invert
作用:求模逆元, 求私钥 d 专用
d=e^−1mod T
d = gmpy2.invert(e, T)
powmod(a, b, N)
作用:加密、解密都靠它
m = c^d mod N
c = m^e mod N
# 解密
m = gmpy2.powmod(c, d, N)
# 加密
c = gmpy2.powmod(m, e, N)
常见题型
经典常规型
给出p,q,e,c,求m
解密脚本
p=
q=
e=
c=
N=p*q
T=(p-1)*(q-1)
D=pow(E,-1,T)
m=pow(c,D,N)
print(m)
单模多密文
给出n,e list_c,求m
from Crypto.Util.number import long_to_bytes
n=
e=65537
list_c=[]
# 分解p,q
def get_pq(n):
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return (i, n // i)
# 如果不确定是否为质数,需要return (n,1)
# return (n, 1)
p, q = get_pq(n)
T=(p-1)*(q-1)
d=pow(e,-1,T)
flag=b''
for c in list_c:
m=pow(c,d,n)
flag+=long_to_bytes(m)
print(flag)
共模攻击
同一套 n,两个不同 e,加密同一个 m → 不用分解 n 就能直接算出 m
思路:同 n + 同 m + 互质 e → 直接算 m,不拆 n、不求 d
n =
e1 =
e2 =
c1 =
old_r, r = e1, e2
old_s, s = 1, 0
old_t, t = 0, 1
while r != 0:
q = old_r // r
old_r, r = r, old_r - q * r
old_s, s = s, old_s - q * s
old_t, t = t, old_t - q * t
x = old_s
y = old_t
# 共模解密 m = c1^x * c2^y mod n
m = (pow(c1, x, n) * pow(c2, y, n)) % n
print("明文 m =", m)
# 转字符串
print("明文字符串 =", bytes.fromhex(hex(m)[2:]).decode())
低指数广播攻击
import gmpy2
from Crypto.Util.number import long_to_bytes
n1 =
c1 =
n2 =
c2 =
n3 =
c3 =
def crt(a_list, m_list):
M = 1
for m in m_list:
M *= m
result = 0
for a, m in zip(a_list, m_list):
Mi = M // m
inv = gmpy2.invert(Mi, m)
result += a * Mi * inv
return result % M
# 合并得到 m^3
x = crt([c1, c2, c3], [n1, n2, n3])
# 开立方根
m, _ = gmpy2.iroot(x, 3)
print(m)
print("临时密码 (十六进制):", hex(m))
print("临时密码 (字符串):", long_to_bytes(m))
题目给文件
不管文件是什么,一律用二进制模式读取,最安全、最通用、不会出错!
万能、通用、不管给什么文件都能用的 Python 读取方法,CTF 里 100% 能用,不用管文件后缀是 .pem/.en/.bin/.txt /.zip全都通吃!
读取任意文件内容(最通用)
# 打开文件,读取所有内容(二进制模式,万能)
with open("你的文件名", "rb") as f:
data = f.read()
# 查看内容
print("文件原始字节内容:", data)
如果内容是文本(公钥.pem 这种)
with open("公钥.pem", "r") as f:
text = f.read()
print(text)
如果是密文,可能是 base64 编码(CTF 超常见)
很多 .en 密文是 base64 编码,需要先解码:
import base64
with open("密文.en", "rb") as f:
cipher_b64 = f.read()
cipher_bytes = base64.b64decode(cipher_b64)
print("解码后的密文字节:", cipher_bytes)
把读取到的内容转成大整数(RSA 解密必须)
RSA 运算需要把文件内容变成数字,这是 CTF 固定操作:
# 字节 → 大整数(RSA 专用)
m = int.from_bytes(data, byteorder="big")
print("大整数:", m)
4 行万能代码
任何文件,直接用这 4 行读取内容,永远不会错:
# 万能读取
with open("文件名", "rb") as f:
content = f.read()
# 转成 RSA 需要的大整数
number = int.from_bytes(content, "big")
CTF 最常见的两种文件怎么读?
读公钥文件(xxx.pem)
from Crypto.PublicKey import RSA
with open("public.pem", "rb") as f:
key = RSA.import_key(f.read())
n = key.n
e = key.e
print("n =", n)
print("e =", e)
读密文文件(xxx.en)
with open("enc.en", "rb") as f:
c_bytes = f.read()
c = int.from_bytes(c_bytes, "big")
print("密文数字 c =", c)
一句话总结
CTF 里不管给什么文件:
- 先用
rb模式读成字节 - 是文本就直接打印
- 是 base64 就解码
- 是 RSA 密文就转成大整数
这一套流程通杀所有文件!
总结
- 万能读取:
open("文件", "rb").read() - 文本文件:用
"r"模式 - base64 密文:
base64.b64decode() - RSA 运算:
int.from_bytes(内容, "big")