Certificate Authority - Writeup by AI
题目信息
- 来源: Bugku CTF
- 类别: Crypto (密码学)
- 考点: 椭圆曲线密码学(ECC)、非标准加密方案、私钥泄露攻击
题目描述
题目提供了一个"非正规"的椭圆曲线加密方案,包含以下文件:
p384-key.pem: 包含完整的EC私钥和公钥(使用P-384曲线)ECC.enc: 包含加密算法描述和密文
加密算法
python
# 1. 明文分割(flag被分为两部分)
x1 = int(codecs.encode(flag[:12],'hex'),16) # 前12字节转为大整数
x2 = int(codecs.encode(flag[12:],'hex'),16) # 剩余部分转为大整数
X = (x1, x2)
# 2. 随机数生成
k = randrange(1, n-1)
# 3. 计算共享点
y0 = k * generator_384 # y0是临时公钥
KPoint = k * PubKey # KPoint是共享密钥点
# 4. 密文计算(关键!)
Y = (X[0] * KPoint.x() % p, X[1] * KPoint.y() % p)
密文数据
Y = (34113256825339373520369275283701161541979556324226185535033938159804983053982282645600429717939710433082631730889659,
32053109834346133043413144503065578404362769943246011698385086926019077101042898269877084920237885778463453847609171)
y0 = (2133726374662399094674717622068093787945171315744414869137518727533960205385423874048257464199675228007664821044234,
6814381752433500362278594762998419406730403502594534675593344187219595358988094395062831144664837476184888758539022)
考点分析
| 考点 | 权重 | 说明 |
|---|---|---|
| PEM文件解析 | 20% | 从PEM格式提取EC私钥和曲线参数 |
| 椭圆曲线基础 | 25% | 理解点加法、标量乘法运算 |
| 加密方案逆向 | 30% | 分析非标准ECC加密流程 |
| 私钥泄露利用 | 25% | 利用已知私钥直接计算共享点 |
核心漏洞
这是一个自定义的"伪ECC"加密方案,不是标准的ECIES或ElGamal。关键问题在于:
- 私钥完全泄露:PEM文件中包含了完整的私钥
- 加密方式简单:仅使用模乘混淆,没有哈希派生密钥
- 可逆性强:知道私钥即可完全还原共享点KPoint
解题思路
攻击策略
读取PEM私钥文件
提取私钥d和曲线参数
获取密文Y和临时公钥y0
计算KPoint = d * y0
计算模逆元 KPoint.x^-1 mod p
解密: x1 = Y_x * KPoint.x^-1 mod p
同理解密x2
十六进制转字符串得到Flag
关键数学推导
核心发现:虽然不知道随机数k,但可以利用私钥d直接计算KPoint!
已知:
- PubKey = d * G (d是私钥,G是基点)
- y0 = k * G (k是随机数)
- KPoint = k * PubKey
推导:
KPoint = k * PubKey
= k * (d * G)
= d * (k * G) ← 椭圆曲线标量乘法满足交换律
= d * y0 ← 关键!无需知道k
解密公式:
python
KPoint = d * y0 # 椭圆曲线标量乘法
x1 = Y[0] * inverse(KPoint.x(), p) % p
x2 = Y[1] * inverse(KPoint.y(), p) % p
详细步骤
步骤1: 解析PEM文件提取参数
使用ecdsa库解析PEM文件,提取:
- 私钥数值
d - 曲线参数
(p, a, b, n) - 基点
G - 公钥点
Q
python
import ecdsa
with open('p384-key.pem', 'r') as f:
pem_content = f.read()
sk = ecdsa.SigningKey.from_pem(pem_content)
private_value = sk.privkey.secret_multiplier
curve = sk.curve
p = curve.curve.p()
a = curve.curve.a()
b = curve.curve.b()
n = curve.order
提取结果:
私钥 d = 345314803247976874399655422352802886843693493822396778637045222934897919285324680489080513563917526647267225494080206
曲线 p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319
曲线 a = -3
曲线 b = 27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575
阶 n = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
步骤2: 实现椭圆曲线运算
由于这是自定义加密,需要手动实现椭圆曲线点运算:
python
class EllipticCurvePoint:
def __init__(self, x, y, curve_a, curve_p, infinity=False):
self.x = x
self.y = y
self.a = curve_a
self.p = curve_p
self.infinity = infinity
def point_add(P, Q):
"""椭圆曲线点加法"""
if P.infinity: return Q
if Q.infinity: return P
if P.x == Q.x:
if P.y != Q.y:
return EllipticCurvePoint(0, 0, P.a, P.p, infinity=True)
lam = (3 * P.x * P.x + P.a) * mod_inverse(2 * P.y, P.p) % P.p
else:
lam = (Q.y - P.y) * mod_inverse(Q.x - P.x, P.p) % P.p
x_r = (lam * lam - P.x - Q.x) % P.p
y_r = (lam * (P.x - x_r) - P.y) % P.p
return EllipticCurvePoint(x_r, y_r, P.a, P.p)
def scalar_multiply(k, P):
"""标量乘法 (double-and-add算法)"""
result = EllipticCurvePoint(0, 0, P.a, P.p, infinity=True)
addend = P
while k:
if k & 1:
result = point_add(result, addend)
addend = point_add(addend, addend)
k >>= 1
return result
步骤3: 计算共享点KPoint
利用私钥d和临时公钥y0计算:
python
y0_point = EllipticCurvePoint(y0_x, y0_y, a, p)
KPoint = scalar_multiply(private_value, y0_point)
# 结果:
# KPoint.x = 18835199024416865644365965566902449266278348711722870356856651542135605633788994751183739828403791317654420216560817
# KPoint.y = 11558519284930642642341208101507709758464537415633959470643930313232338707486127894445738639245491232995442111490098
步骤4: 解密明文
计算模逆元并恢复x1和x2:
python
KPoint_x_inv = mod_inverse(KPoint.x, p)
KPoint_y_inv = mod_inverse(KPoint.y, p)
x1 = (Y_x * KPoint_x_inv) % p # 31698494968736568155583361887
x2 = (Y_y * KPoint_y_inv) % p # 26445730285019478715091403133
步骤5: 转换为Flag
将大整数转换回十六进制再解码为字符串:
python
hex_x1 = hex(x1)[2:] # '18e7e5c5f5f33'
hex_x2 = hex(x2)[2:] # '142a5e3766315'
# 补齐偶数长度
if len(hex_x1) % 2 != 0: hex_x1 = '0' + hex_x1
if len(hex_x2) % 2 != 0: hex_x2 = '0' + hex_x2
flag_part1 = codecs.decode(hex_x1, 'hex').decode('utf-8') # 'flag{3CC_I3_'
flag_part2 = codecs.decode(hex_x2, 'hex').decode('utf-8') # 'Use7f1_HaHA}'
flag = flag_part1 + flag_part2 # 'flag{3CC_I3_Use7f1_HaHA}'
运行结果
============================================================
Certificate Authority - ECC解密
============================================================
✓ 成功使用ecdsa库解析PEM
曲线参数:
p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319
a = -3
b = 27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575
n = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
私钥 d = 345314803247976874399655422352802886843693493822396778637045222934897919285324680489080513563917526647267225494080206
公钥 Q = (206472480816501406837281388996335457607609068965647575006464669330299894293999634147080012252011563534842054211995280, 271284199742960113939785931274188966364076177927911299446156024948916655234077123509433666197034008248433960277493512)
[步骤2] 加载密文数据...
Y = (34113256825339373520369275283701161541979556324226185535033938159804983053982282645600429717939710433082631730889659,
32053109834346133043413144503065578404362769943246011698385086926019077101042898269877084920237885778463453847609171)
y0 = (2133726374662399094674717622068093787945171315744414869137518727533960205385423874048257464199675228007664821044234,
6814381752433500362278594762998419406730403502594534675593344187219595358988094395062831144664837476184888758539022)
[步骤3] 计算共享点 KPoint = d * y0...
KPoint = (18835199024416865644365965566902449266278348711722870356856651542135605633788994751183739828403791317654420216560817,
11558519284930642642341208101507709758464537415633959470643930313232338707486127894445738639245491232995442111490098)
[步骤4] 解密明文...
x1 = 31698494968736568155583361887
x2 = 26445730285019478715091403133
[步骤5] 转换为flag字符串...
Flag部分1 (前12字节): 'flag{3CC_I3_'
Flag部分2 (剩余): 'Use7f1_HaHA}'
============================================================
完整Flag: flag{3CC_I3_Use7f1_HaHA}
============================================================
✓ 解密成功!