概述
LostKey是来自于HTB(hackthebox.com)的一个中级密码学挑战,完成该挑战所需要掌握的知识点在于椭圆曲线以及相关的离散对数算法。
题目分析
相关的任务文件包括一个encrypt.py源代码和output.txt文本文件。
encrypt.py内容如下
#!/usr/bin/env python3
from Crypto.Util.number import *
from hashlib import sha1
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from secret import flag, n
class coord:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"coord({self.x}, {self.y})"
class EC:
def __init__(self, p):
self.p = p
self.zero = coord(0,0)
def add(self, P,Q):
if P == self.zero:
return Q
if Q == self.zero:
return P
if P.x == Q.x and P.y == -Q.y:
return self.zero
if P != Q:
Lambda = (Q.y - P.y) * inverse(Q.x - P.x, self.p)
else:
Lambda = (3*(P.x*Q.x) + 417826948860567519876089769167830531934*P.x + 177776968102066079765540960971192211603) * inverse(P.y+Q.y+3045783791, self.p)
Lambda %= self.p
R = coord(0,0)
R.x = (Lambda**2-P.x-Q.x-208913474430283759938044884583915265967) % self.p
R.y = (Lambda*(P.x-R.x) - P.y - 3045783791) % self.p
return R
def mul(self, P, n):
Q = P
R = self.zero
while n > 0:
if n % 2 == 1:
R = self.add(R,Q)
n >>= 1
Q = self.add(Q,Q)
return R
def encrypt(key):
iv = __import__('os').urandom(16)
key = sha1(str(key).encode('ascii')).digest()[0:16]
cipher = AES.new(key, AES.MODE_CBC, iv)
ct = cipher.encrypt(pad(flag,16))
return(ct.hex(),iv.hex())
assert(n < 38685626227668133590597631)
e = EC(101177610013690114367644862496650410682060315507552683976670417670408764432851)
G = coord(14374457579818477622328740718059855487576640954098578940171165283141210916477, 97329024367170116249091206808639646539802948165666798870051500045258465236698)
print ("G =",G)
print ("Gn =", e.mul(G,n).x)
enc = encrypt(n)
print ("Ciphertext: {}\nIV: {}".format(enc[0],enc[1]))
encrypt.py中的EC类定义了一个椭圆曲线, 其加密过程在于用该曲线上的一个点G通过n * G计算出Gn, 并将n作为AES密钥对明文加密, 因此对密文解密则需要获得n的值。
output.txt文本文件则给出了G,Gn(x坐标),Ciphertext和IV的数值
G = coord(14374457579818477622328740718059855487576640954098578940171165283141210916477, 97329024367170116249091206808639646539802948165666798870051500045258465236698)
Gn = 32293793010624418281951109498609822259728115103695057808533313831446479788050
Ciphertext: df572f57ac514eeee9075bc0ff4d946a80cb16a6e8cd3e1bb686fabe543698dd8f62184060aecff758b29d92ed0e5a315579b47f6963260d5d52b7ba00ac47fd
IV: baf9137b5bb8fa896ca84ce1a98b34e5
#解题过程
首先根据椭圆曲线的一般形式及其点相加的计算公式,获得该曲线的参数。
SageMath代码如下
p = 101177610013690114367644862496650410682060315507552683976670417670408764432851
a1 = 0
a2 = 417826948860567519876089769167830531934 // 2
a3 = 3045783791
a4 = 177776968102066079765540960971192211603
Gx = 14374457579818477622328740718059855487576640954098578940171165283141210916477
Gy = 97329024367170116249091206808639646539802948165666798870051500045258465236698
a6 = Gy^2 + a1 * Gx * Gy + a3 * Gy - (Gx^3 + a2 * Gx^2 + a4 * Gx)
a6 = a6 % p
EC = EllipticCurve(Zmod(p), [a1, a2, a3, a4, a6])
G = EC(Gx, Gy)
然后,我们可以通过给出的Gnx坐标得到Gn的y坐标。
Gnx = 32293793010624418281951109498609822259728115103695057808533313831446479788050
## get point from x
Gn = EC.lift_x(Gnx)
print("Gn =", Gn.xy())
同时也可以看到该曲线的order不是一个素数, 因此可以使用Pohlig–Hellman算法来计算Gn和G的离散对数, 也就是n
print("EC order=", ecOrder)
##EC order= 101177610013690114367644862496650410682371882435919767898009148385876141737891
F = factor(ecOrder)
#F = 3^2 * 59 * 14771 * 27733 * 620059697 * 2915987653003935133321 * 257255080924232005234239344602998871
对曲线order的素因数分解给出了7个素因数, 只有前5个值较小可以直接求离散对数, 而后则需要使用题目中给出的n的上限来逐个枚举可能的数值以找到n。
primes = [9, 59, 14771, 27733, 620059697]
dlogs = []
product = 1
for fac in primes:
t = ecOrder // fac
dlog = discrete_log(t*Gn, t*G, operation="+")
dlogs.append(dlog)
print("factor: ", fac, ", Discrete Log: ", dlog)
product = product * fac
L = crt(dlogs, primes)
print("L =", L)
print("check L =", L * G == Gn)
print("product =", product)
n = L
while (n <= 38685626227668133590597631):
if (n * G == Gn):
print("Found n =", n)
break
else:
n = n + product
得到n之后,套用AES解密方法即可得到Flag的明文。
from Crypto.Cipher import AES
from hashlib import sha1
iv = bytes.fromhex("baf9137b5bb8fa896ca84ce1a98b34e5")
cipherText = bytes.fromhex("df572f57ac514eeee9075bc0ff4d946a80cb16a6e8cd3e1bb686fabe543698dd8f62184060aecff758b29d92ed0e5a315579b47f6963260d5d52b7ba00ac47fd")
key = sha1(str(n).encode('ascii')).digest()[0:16]
cipher = AES.new(key, AES.MODE_CBC, iv)
plainText = cipher.decrypt(cipherText)
print ("plainText=", plainText)