1. 题目背景与NTRU密码系统初探
这道来自DASCTF2022.07赋能赛的easyNTRU题目,考察的是对NTRU加密系统的理解与攻击能力。NTRU(N-th Degree Truncated Polynomial Ring Units)是一种基于格的公钥密码系统,与RSA、ECC相比具有加解密速度快、抗量子计算等优势。在CTF密码学题目中,NTRU相关挑战往往涉及参数选取不当导致的漏洞利用。
比赛环境提供了两个关键文件:
pub.key:包含NTRU公钥参数enc.txt:加密后的flag密文
通过分析题目文件名和内容结构,可以确定这是标准的NTRU加密实现题目,需要参赛者通过公钥信息恢复私钥或直接破解加密过程。这类题目通常考察以下知识点:
- NTRU密钥生成原理
- 多项式环上的卷积运算
- 格基约化攻击方法
- 参数选取不当导致的脆弱性
2. NTRU加密原理深度解析
2.1 数学基础构建
NTRU工作在多项式商环R = Z[X]/(X^N - 1)上,其中N是安全参数。关键运算为多项式卷积乘法:
(f ∗ g)(X) = ∑_{i+j≡k mod N} f_i g_j X^k
加密系统涉及三个整数参数(N,p,q),其中:
- p和q是互素的模数,通常满足q ≫ p
- 典型参数集:(N,p,q) = (251,3,128)或(503,3,256)
2.2 密钥生成过程
私钥由两个短多项式f和g组成:
- f ∈ L_f:可逆模q的多项式
- g ∈ L_g:通常系数较小
公钥h的计算公式:
h ≡ p ∗ f_q ∗ g mod q
其中f_q是f模q的逆元
2.3 加密解密流程
加密过程:
- 将明文m表示为系数在(-p/2,p/2]的多项式
- 随机选择短多项式r
- 计算密文e ≡ r ∗ h + m mod q
解密过程:
- 计算a ≡ f ∗ e mod q
- 中心提升a到标准区间
- 恢复明文m ≡ f_p ∗ a mod p
3. 题目文件分析与漏洞定位
3.1 公钥参数提取
使用Python解析pub.key文件:
python复制from ast import literal_eval
with open('pub.key','r') as f:
data = literal_eval(f.read())
N = data['N']
h = data['h']
q = data['q']
典型输出显示:
- N = 107
- q = 64
- h为107维多项式系数列表
3.2 参数脆弱性分析
题目参数存在以下安全隐患:
- 维度N=107过小(现代安全建议N≥251)
- 模数q=64极小,导致格攻击可行
- 未采用标准参数集,暗示存在特殊攻击路径
通过检查h的系数分布发现:
- 系数集中在0附近
- 最大绝对值不超过20
- 符合g多项式系数小的特征
4. 格基约化攻击实现
4.1 构建格矩阵
利用公钥h构造NTRU格:
L = [ I_N 0 ]
[ M_h qI_N ]
其中M_h是h的循环矩阵
具体实现代码:
python复制def build_lattice(h, q):
N = len(h)
# 构建h的循环矩阵
M = matrix(ZZ, N, N)
for i in range(N):
for j in range(N):
M[i,j] = h[(j-i) % N]
# 构造完整格
B = block_matrix([[identity_matrix(N), M], [zero_matrix(N,N), q*identity_matrix(N)]])
return B
4.2 LLL算法应用
对构建的格进行约化:
python复制L = build_lattice(h, q)
L_LLL = L.LLL()
在SageMath中运行后,可以在约化基的前几行找到短向量(f,g),其中:
- f对应私钥的第一部分
- g的系数应满足小整数特征
4.3 私钥恢复验证
从LLL结果中提取候选私钥:
python复制for row in L_LLL:
if row.norm() < 50: # 根据题目调整阈值
potential_f = list(row[:N])
potential_g = list(row[N:])
# 验证f的可逆性
try:
f_p = inverse_mod(potential_f, p)
print("Found valid f:", potential_f)
break
except:
continue
5. 密文解密与Flag获取
5.1 密文数据加载
解析enc.txt文件:
python复制with open('enc.txt','r') as f:
e = literal_eval(f.read())
5.2 解密流程实现
使用恢复的私钥进行解密:
python复制def decrypt(f, g, e, q, p):
N = len(f)
a = convolution(f, e, q)
m = convolution(invert(f,p), a, p)
return m
def convolution(f, g, mod):
N = len(f)
return [sum(f[i]*g[(k-i)%N] for i in range(N)) % mod for k in range(N)]
5.3 明文转换与Flag提取
将解密后的多项式转换为ASCII:
python复制m = decrypt(f_recovered, g_recovered, e, q, p)
flag = ''.join([chr(c) for c in m if c < 128])
print("Flag:", flag)
6. 攻击优化与技巧总结
6.1 参数调整经验
- LLL算法参数优化:
python复制L.BKZ(block_size=30) # 比标准LLL效果更好
- 格构造变体尝试:
- 可以尝试构造垂直格或对偶格
- 调整格的缩放因子提高成功率
6.2 常见问题排查
- 解密失败的可能原因:
- LLL找到的向量不是真实私钥
- 模数p的选择与加密时不一致
- 多项式系数提升处理不当
- 解决方案:
- 检查f模p的可逆性
- 尝试多个短向量作为候选
- 验证h ≡ f^{-1} ∗ g mod q
6.3 防御措施建议
安全NTRU实现应:
- 使用标准参数集(N≥251)
- 添加抗格攻击的扰动项
- 实现强制的参数检查
7. 完整解题脚本参考
python复制from sage.all import *
import ast
# 参数加载
with open('pub.key','r') as f:
data = ast.literal_eval(f.read())
N, h, q = data['N'], data['h'], data['q']
p = 3 # 根据题目提示确定
# 格构造
def build_lattice(h, q):
M = matrix(ZZ, N, N)
for i in range(N):
for j in range(N):
M[i,j] = h[(j-i)%N]
return block_matrix([[identity_matrix(N), M],
[zero_matrix(N,N), q*identity_matrix(N)]])
L = build_lattice(h, q)
Lred = L.LLL()
# 私钥恢复
for row in Lred:
if max(abs(x) for x in row) < 20:
f = list(row[:N])
try:
fp = inverse_mod(matrix(GF(p), N, N,
[f[(j-i)%N] for i in range(N) for j in range(N)]),
N).list()
break
except:
continue
# 密文解密
with open('enc.txt','r') as f:
e = ast.literal_eval(f.read())
def conv(f,g,mod):
return [sum(f[i]*g[(k-i)%N] for i in range(N))%mod for k in range(N)]
a = conv(f,e,q)
a = [x if x <= q//2 else x-q for x in a]
m = conv(fp, a, p)
flag = ''.join([chr(abs(x)) for x in m])
print("Flag:", flag.strip())
在实际比赛中,这类题目通常需要2-3小时的解题时间,关键点在于快速识别加密系统类型和选择合适的攻击方法。对于NTRU题目,当发现参数异常时,优先考虑格攻击是最有效的策略。
