# Cryptohack ECC > Author: 堇姬Naup ## Background Reading ### 橢圓曲線: $y^2 = x^3 + ax + b$ $4a^3 + 27b^2 ≠ 0$ - Why ECC need $4a^3 + 27b^2 ≠ 0$ https://www.quora.com/For-an-elliptic-curve-in-the-form-Y-2-X-3+AX+B-why-is-4A-3+27B-2-neq-0-the-condition-for-non-singularity ![image](https://hackmd.io/_uploads/H12rVwFCp.png) 點加法:簡單來說就是兩點連線交點的映射 證明:(可以看我之前打的) https://hackmd.io/@naup96321/H1nACqsH0 ``` (a) P + O = O + P = P (b) P + (−P) = O (c) (P + Q) + R = P + (Q + R) (d) P + Q = Q + P ``` ### 有限域 (伽羅瓦域) 有限域 $GF(p)$ 指給定某個質數p,由0、1、2……p-1共p個元素組成的整數集合中定義的加減乘除運算 > Flag: crypto{ABELIAN} ### order階 $E$ 上有一點$P$ ,可以找到$nP=0$,$n$稱為order ### 橢圓曲線的離散對數問題 在橢圓曲線$E$上 給定$P$和$T$ 求出 $d$ $P+P+...+P+P=T=dP$ | 符號 | 意思 | | --- | ----| |T|public key| |d|private key| |P|Generate point| ### 橢圓曲線的離散對數問題 (Discrete Logarithm Problem with Elliptic Curves, ECDLP) 給定一個橢圓曲線$E$,$E$上的一點$P$經過了d次點加法得到$T$ 現在已知$P$、$T$ 求出 $d$ ## Point Negation 現在把它放入有限域中使他變成很多點的集合,代表運算時是模$F_p$下進行 $E(Fp) = \{(x,y) : x,y ∈ F_p | y^2 = x^3 + a x + b\} ∪ O$ 當把$E$放入有限域後橢圓曲線不再是一條光滑曲線,而是一些不連續的點 ![image](https://hackmd.io/_uploads/HJw_-WMWR.png) ### 題目 $E: Y^2 = X^3 + 497 X + 1768 , p: 9739$ 利用上面的曲線和點 P(8045,6936),求點 Q(x, y),使得 P + Q = O。 ### solve 因為P+Q=0 所以可以知道Q的x=8045,y為該P的y於該曲線上的對稱-6939,並且在有限域9739中運算故要mod 9739 ![image](https://hackmd.io/_uploads/rJfIW2gWR.png) Flag: crypto{8045,2803} ## Point Addition - $P=0$ 則 $P+Q=Q$ - $Q=0$ 則 $P+Q=P$ (0為無窮遠點) - $P=(x_p,y_p),Q=(x_q,y_q)$ 則$x_p=x_q$且$y_p=-y_q$ $P+Q=0$ 若$P,Q$為不同點,曲線$E$ 與 $P,Q$直線相交點的求反為$R(x_r,y_r)$ 則$d=\frac{y_q-y_p}{x_q-x_p}$ $x_r=d^2-x_p-x_q$ $y_r=d(x_p-x_r)-y_p$ - 若$P,Q$重合 $d$則改用導數的方式呈現 $d=\frac{3x_p^2+a}{2y_p}$ $x_r=d^2-x_p-x_q$ $y_r=d(x_p-x_r)-y_p$ ### 證明 https://blog.csdn.net/guyongqiangx/article/details/121793398 ### 題目 $E: Y^2 = X^3 + 497 X + 1768, p: 9739$ Using the above curve, and the points P = (493, 5564), Q = (1539, 4742), R = (4403,5202), find the point S(x,y) = P + P + Q + R by implementing the above algorithm. ```python= from sage.all import GF class Point: def __init__(self,x,y,p): F = GF(p) #定義有限域 self.x = F(x) #讓x,y在有限域下運算 self.y = F(y) self.modulus = p #模數 def addition(p1 : Point, p2: Point, a,b): x1 = p1.x x2 = p2.x y1 = p1.y y2 = p2.y if x1 == x2 and y1 == y2: #如果P,Q重合 d = (3*x1**2 + a) / (2*y1) else: #P和Q不同兩點 d = (y2 - y1) / (x2 - x1) x = d**2 - x1 - x2 y = d*(x1 - x) - y1 return Point(x,y,p1.modulus) P = Point(493, 5564,9739) Q = Point(1539, 4742,9739) R = Point(4403,5202,9739) x1 = addition(P,P,497,1768) x2 = addition(x1,Q,497,1768) x3 = addition(x2,R,497,1768) assert x3.y**2 == x3.x**3 + 497*x3.x + 1768 #確認所運算是否在E上 print(x3.x, x3.y) ``` ![image](https://hackmd.io/_uploads/rJVL5eWbR.png) ## Scalar Multiplication We will work with the following elliptic curve, and prime: E: $Y^2 = X^3 + 497 X + 1768, p: 9739$ Using the above curve, and the points P = (2339, 2213), find the point Q(x,y) = 7863 P by implementing the above algorithm. ```python= from sage.all import GF class Point: def __init__(self,x,y,p): F = GF(p) #定義有限域 self.x = F(x) #讓x,y在有限域下運算 self.y = F(y) self.modulus = p #模數 def addition(p1 : Point, p2: Point, a,b): x1 = p1.x x2 = p2.x y1 = p1.y y2 = p2.y if x1 == x2 and y1 == y2: #如果P,Q重合 d = (3*x1**2 + a) / (2*y1) else: #P和Q不同兩點 d = (y2 - y1) / (x2 - x1) x = d**2 - x1 - x2 y = d*(x1 - x) - y1 return Point(x,y,p1.modulus) #np->快速冪 def scalar_multiplication(p: Point, n,a,b): q = p r = 0 while n > 0: if n % 2 == 1: try: r = addition(r,q,a,b) except: r = q q = addition(q,q,a,b) n = n//2 return r p = Point(2339, 2213,9739) a = scalar_multiplication(p,7863 ,497,1768) print(a.x, a.y) ``` ## Curves and Logs ### 敘述 Alice generates a secret random integer $n_A$ and calculates $Q_A$ = $n_AG$ Bob generates a secret random integer $n_B$ and calculates $Q_B$ = $n_BG$ Alice sends Bob $Q_A$, and Bob sends Alice $Q_B$. Due to the hardness of ECDLP, an onlooker Eve is unable to calculate $n_A,n_B$ in reasonable time. Alice then calculates $n_AQ_B$, and Bob calculates $n_BQ_A$. Due to the associativity of scalar multiplication, $S = n_AQ_B = n_BQ_A$. Alice and Bob can use $S$ as their shared secret. 由於$n_A,n_B$不可知(求出$n_A,n_B$涉及ECDLP),所以中間人沒辦法知道$S$是多少 ### 題目 Using the curve, prime and generator: $E: Y^2 = X^3 + 497 X + 1768, p: 9739, G: (1804,5368)$ Calculate the shared secret after Alice sends you $Q_A$ = (815, 3190), with your secret integer $n_B$ = 1829. Generate a key by calculating the SHA1 hash of the x coordinate (take the integer representation of the coordinate and cast it to a string). The flag is the hexdigest you find. ### solve ```python= class Point: def __init__(self,x,y,p): F = GF(p) self.x = F(x) self.y = F(y) self.modulus = p def addition(p1 : Point, p2: Point, a,b): x1 = p1.x x2 = p2.x y1 = p1.y y2 = p2.y if x1 == x2 and y1 == y2: lamda = (3*x1**2 + a) / (2*y1) else: lamda = (y2 - y1) / (x2 - x1) x = lamda**2 - x1 - x2 y = lamda*(x1 - x) - y1 return Point(x,y,p1.modulus) def scalar_multiplication(p: Point, n,a,b): q = p r = 0 while n > 0: if n % 2 == 1: try: r = addition(r,q,a,b) except: r = q q = addition(q,q,a,b) n = n//2 return r q = Point(815, 3190,9739) a = scalar_multiplication(q,1829,497,1768) print(a.x,a.y) ``` ```python= import hashlib x_coordinate = str(7929) hash_object = hashlib.sha1(x_coordinate.encode()) sha1_hash = hash_object.hexdigest() print(sha1_hash) ``` ## Efficient Exchange 公鑰不需要發送$x,y$,因為不管對方收到的$x,y$是啥,只要兩人約定的$E$一致,那$x$就會相同,所以可以直接以$x$來當作約定的公鑰 ### solve ```py import hashlib from sympy.ntheory import sqrt_mod from sage.all import * class Point: def __init__(self,x,y,p): F = GF(p) self.x = F(x) self.y = F(y) self.modulus = p def addition(p1 : Point, p2: Point, a,b): x1 = p1.x x2 = p2.x y1 = p1.y y2 = p2.y if x1 == x2 and y1 == y2: lamda = (3*x1**2 + a) / (2*y1) else: lamda = (y2 - y1) / (x2 - x1) x = lamda**2 - x1 - x2 y = lamda*(x1 - x) - y1 return Point(x,y,p1.modulus) def scalar_multiplication(p: Point, n,a,b): q = p r = 0 while n > 0: if n % 2 == 1: try: r = addition(r,q,a,b) except: r = q q = addition(q,q,a,b) n = n//2 return r x = 4726 n_b = 6534 [y1,y2] = sqrt_mod(x**3 + 497*x + 1768, 9739, True) p1 = Point(x,y1,9739) p2 = Point(x,y2,9739) a1 = scalar_multiplication(p1,n_b,497,1768) a2 = scalar_multiplication(p2,n_b,497, 1768) shared_secret1 = a1 shared_secret2 = a2 print(a1.x,a2.x,a1.y,a2.y) ``` ![image](https://hackmd.io/_uploads/SygvXSfWR.png) ```python= from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import hashlib def is_pkcs7_padded(message): padding = message[-message[-1]:] return all(padding[i] == len(padding) for i in range(0, len(padding))) def decrypt_flag(shared_secret: int, iv: str, ciphertext: str): # Derive AES key from shared secret sha1 = hashlib.sha1() sha1.update(str(shared_secret).encode('ascii')) key = sha1.digest()[:16] # Decrypt flag ciphertext = bytes.fromhex(ciphertext) iv = bytes.fromhex(iv) cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = cipher.decrypt(ciphertext) if is_pkcs7_padded(plaintext): return unpad(plaintext, 16).decode('ascii') else: return plaintext.decode('ascii') shared_secret = 1791 iv = 'cd9da9f1c60925922377ea952afc212c' ciphertext = 'febcbe3a3414a730b125931dccf912d2239f3e969c4334d95ed0ec86f6449ad8' print(decrypt_flag(shared_secret, iv, ciphertext)) ``` ## Smooth Criminal ### Source code ```python3= from Crypto.Cipher import AES from Crypto.Util.number import inverse from Crypto.Util.Padding import pad, unpad from collections import namedtuple from random import randint import hashlib import os # Create a simple Point class to represent the affine points. Point = namedtuple("Point", "x y") # The point at infinity (origin for the group law). O = 'Origin' FLAG = b'crypto{??????????????????????????????}' def check_point(P: tuple): if P == O: return True else: return (P.y**2 - (P.x**3 + a*P.x + b)) % p == 0 and 0 <= P.x < p and 0 <= P.y < p def point_inverse(P: tuple): if P == O: return P return Point(P.x, -P.y % p) def point_addition(P: tuple, Q: tuple): # based of algo. in ICM if P == O: return Q elif Q == O: return P elif Q == point_inverse(P): return O else: if P == Q: lam = (3*P.x**2 + a)*inverse(2*P.y, p) lam %= p else: lam = (Q.y - P.y) * inverse((Q.x - P.x), p) lam %= p Rx = (lam**2 - P.x - Q.x) % p Ry = (lam*(P.x - Rx) - P.y) % p R = Point(Rx, Ry) assert check_point(R) return R def double_and_add(P: tuple, n: int): # based of algo. in ICM Q = P R = O while n > 0: if n % 2 == 1: R = point_addition(R, Q) Q = point_addition(Q, Q) n = n // 2 assert check_point(R) return R def gen_shared_secret(Q: tuple, n: int): # Bob's Public key, my secret int S = double_and_add(Q, n) return S.x def encrypt_flag(shared_secret: int): # Derive AES key from shared secret sha1 = hashlib.sha1() sha1.update(str(shared_secret).encode('ascii')) key = sha1.digest()[:16] # Encrypt flag iv = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pad(FLAG, 16)) # Prepare data to send data = {} data['iv'] = iv.hex() data['encrypted_flag'] = ciphertext.hex() return data # Define the curve p = 310717010502520989590157367261876774703 a = 2 b = 3 # Generator g_x = 179210853392303317793440285562762725654 g_y = 105268671499942631758568591033409611165 G = Point(g_x, g_y) # My secret int, different every time!! n = randint(1, p) # Send this to Bob! public = double_and_add(G, n) print(public) # Bob's public key b_x = 272640099140026426377756188075937988094 b_y = 51062462309521034358726608268084433317 B = Point(b_x, b_y) # Calculate Shared Secret shared_secret = gen_shared_secret(B, n) # Send this to Bob! ciphertext = encrypt_flag(shared_secret) print(ciphertext) ``` ### output ``` Point(x=280810182131414898730378982766101210916, y=291506490768054478159835604632710368904) {'iv': '07e2628b590095a5e332d397b8a59aa7', 'encrypted_flag': '8220b7c47b36777a737f5ef9caa2814cf20c1c1ef496ec21a9b4833da24a008d0870d3ac3a6ad80065c138a2ed6136af'} ``` ### 分析 ``` p = 310717010502520989590157367261876774703 a = 2 b = 3 ``` 建構了一個橢圓曲線 $Y^2 = X^3 + 2 X + 3 , p: 310717010502520989590157367261876774703$ $G$ : ``` g_x = 179210853392303317793440285562762725654 g_y = 105268671499942631758568591033409611165 G = Point(g_x, g_y) ``` $n$是我的secret key,我把它做$G \times n$,這就是我們的公鑰 $B$ : ``` b_x = 272640099140026426377756188075937988094 b_y = 51062462309521034358726608268084433317 B = Point(b_x, b_y) ``` Bob用$B \times n$來產生Shared Secret去加密flag ### 解法 目標找出 shared_secret ,我們要找出n是多少,另外目前我們知道 $G \times n$ ,所以我們必須解決 ECDLP 問題 可以來看看Pohlig-Hellman是否可行 首先我們先嘗試找出 $E$ 的**order** ![image](https://hackmd.io/_uploads/HkHiFNIfR.png) 質因數分解他 ![image](https://hackmd.io/_uploads/B1zRK4IGR.png) $\sqrt{179317983307} 近似 423459$ BSGS 的複雜度為 O(sqrt(n)) 所以可以用`.discrete_log` ![image](https://hackmd.io/_uploads/ryuhjN8MR.png) ```python= from sage.all import * p = 310717010502520989590157367261876774703 a = 2 b = 3 E = EllipticCurve(GF(p),[a,b]) g_x = 179210853392303317793440285562762725654 g_y = 105268671499942631758568591033409611165 G = E(g_x, g_y) b_x = 272640099140026426377756188075937988094 b_y = 51062462309521034358726608268084433317 B = E(b_x, b_y) publickey=E(280810182131414898730378982766101210916,291506490768054478159835604632710368904) print("n:",G.discrete_log(publickey)) ``` 算出$n=47836431801801373761601790722388100620$ 接下來就是解密了 ```python= from Crypto.Cipher import AES from Crypto.Util.number import inverse from Crypto.Util.Padding import pad, unpad from collections import namedtuple from random import randint import hashlib import os O = 'Origin' def check_point(P: tuple): if P == O: return True else: return (P.y**2 - (P.x**3 + a*P.x + b)) % p == 0 and 0 <= P.x < p and 0 <= P.y < p def point_inverse(P: tuple): if P == O: return P return Point(P.x, -P.y % p) def point_addition(P: tuple, Q: tuple): # based of algo. in ICM if P == O: return Q elif Q == O: return P elif Q == point_inverse(P): return O else: if P == Q: lam = (3*P.x**2 + a)*inverse(2*P.y, p) lam %= p else: lam = (Q.y - P.y) * inverse((Q.x - P.x), p) lam %= p Rx = (lam**2 - P.x - Q.x) % p Ry = (lam*(P.x - Rx) - P.y) % p R = Point(Rx, Ry) assert check_point(R) return R def double_and_add(P: tuple, n: int): # based of algo. in ICM Q = P R = O while n > 0: if n % 2 == 1: R = point_addition(R, Q) Q = point_addition(Q, Q) n = n // 2 assert check_point(R) return R def gen_shared_secret(Q: tuple, n: int): # Bob's Public key, my secret int S = double_and_add(Q, n) return S.x def decrypt_flag(shared_secret: int, iv: bytes, ciphertext: bytes): # Derive AES key from shared secret sha1 = hashlib.sha1() sha1.update(str(shared_secret).encode('ascii')) key = sha1.digest()[:16] # Encrypt flag cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.decrypt(pad(ciphertext, 16)) print(ciphertext) p = 310717010502520989590157367261876774703 a = 2 b = 3 Point = namedtuple("Point", "x y") b_x = 272640099140026426377756188075937988094 b_y = 51062462309521034358726608268084433317 B = Point(b_x, b_y) n=47836431801801373761601790722388100620 shared_secret = gen_shared_secret(B, n) print(shared_secret) iv=bytes.fromhex('07e2628b590095a5e332d397b8a59aa7') c=bytes.fromhex('8220b7c47b36777a737f5ef9caa2814cf20c1c1ef496ec21a9b4833da24a008d0870d3ac3a6ad80065c138a2ed6136af') decrypt_flag(shared_secret,iv,c) ``` ![image](https://hackmd.io/_uploads/ryf3AN8fA.png) ### BSGS(大步小步算法) $\mathbb{F}_p$是一個有限群,在裡面求離散對數的解$\log_gh$ (換句話說 $g^x = h$,求出$x$)非常難。 目標$g^x = h$,求出$x$ 假設現在有個數 $T=\sqrt{order(g)}$ ( $order(g)=p-1$ ),可以假設$aT+b$ ( $0 \leq a,b \leq T$ ),這樣就可以枚舉(0 ~ p-1) $g^{aT+b} = h$ $g^{aT} = h \times g^{-b}$ 左邊$g^{aT}$和右邊$g^{-b}$有$T$個,可以暴力枚舉$a,b$,將他寫進一張表,看看有無碰撞 找到$a,b$就可以求$aT+b=x$ (本質上跟 meet in the middle很像) 也就是最後的複雜度是$O(\sqrt{order(g)})$ BSGS可以求解任意群的離散對數 https://blog.csdn.net/qq_58207591/article/details/123954286 https://luckyglass.github.io/2019/OldVer12/ https://chenliang.org/2021/06/06/attack-dicrete-logarithm/ https://zhuanlan.zhihu.com/p/523658036 ### smooth數 如果一個數所有的質因數都小於等於$B$,就稱為這個數叫做$B-smooth$ 如: $15750=2 \times 3^2 \times 5^3 \times 7$,是個$7-smooth$ ### pohlig-hellman https://www.ruanx.net/pohlig-hellman/ https://lazzzaro.github.io/2020/11/07/crypto-ECC/index.html#Pohlig-Hellman%E7%AE%97%E6%B3%95 #### 說明 求解 $Q=lP \pmod p$ $P的order = n$ 這裡將$n$分解 $n={p_1}^{e_1}+{p_2}^{e_2}+ ... {p_r}^{e_r}$ 將因數取出來 $l_i \equiv l \pmod {{p_i}^{e_i}}$ $i=[1,r]$ 也就是 $l_1 \equiv l \pmod {{p_1}^{e_1}}$ $l_2 \equiv l \pmod {{p_2}^{e_2}}$ $...$ $l_r \equiv l \pmod {{p_r}^{e_r}}$ 若求得 $l_i, i=[1,r]$ ,用crt可以求得 $l$ 開始求出 $l_i$ 將 $l_i$ 設為 $p_i$ 的多項式, $l_i = {z_0}+{z_1}{p_i}+{z_2}{p_i}^2+...+{z_{e-1}}{p_i}^{e-1}$ $z=[0,p_{i-1}-1]$ 這邊要計算 $z_i$ 分別取 $P_0、Q_0$(用來構造子群,更快求解) $P_0=\frac{n}{p_i}P$ $Q_0=\frac{n}{p_i}Q$ 這樣可以知道 $Q_0=\frac{n}{p_i}Q=\frac{n}{p_i}lP=l(\frac{n}{p_i}P)=lP_0$ 相當於在原式兩邊同乘$\frac{n}{p_i}$ ($Q_0=lP_0$) 回到$l_i$ 求解$z_0$ $l_iP_0=Q_0$ $({z_0}+{z_1}{p_i}+{z_2}{p_i}^2+...+{z_{e-1}}{p_i}^{e-1})P_0=Q_0$ $({z_0})P_0=Q_0$ 這邊已經將$P$域上的離散對數分解到$P_0$域上 所以這邊已經可以用${p_i}$的order來求解$z_0$了(很明顯$p_i$遠遠小於 $n$,可以用更高效來求解) > 這邊為甚麼說$order=p_i$呢? 我們知道找order是指$nP=O$($P的order=n$) 我們使用 $P_0=\frac{n}{p_i}P$ ${p_i}P_0={n}P=O$ 所以$P_0$的$order=p_i$ 回到原來,這樣就可以用$order=p_i$來求解$z_0$ 接下來開始推到$z_1、z_2 ...$ $({z_0}+{z_1}{p_i}+{z_2}{p_i}^2+...+{z_{e-1}}{p_i}^{e-1})P_0=Q_0$ $({z_1}{p_i}+{z_2}{p_i}^2+...+{z_{e-1}}{p_i}^{e-1})P_0=Q_0-{z_0}P_0$ $z_1p_i=Q_0-z_0P_0$ 求解$z_1$ 依序求所有的$z$就可以得到$l_1$ 再根據此作法推到所有的$l_i$ CRT後可以求出$l$ 了,而複雜度是$\sqrt {p_r}$ 中間求解子群的離散對數可以用BSGS求解(複雜度就是根號order) ```python= from Crypto.Util.number import * from sympy.polys.galoistools import gf_crt from sympy.polys.domains import ZZ import gmpy2 import random def Pohlig_Hellman(g, h, p): # 模数分解 p_1 = p - 1 d, factors = 2, [] while d*d <= p_1: while (p_1 % d) == 0: factors.append(d) p_1 //= d d += 1 if p_1 > 1: factors.append(p) factors = [[i, factors.count(i)] for i in set(factors)] # 求每个素因子进制下的c_i x = [] for factor in factors: c_i_list = [] for i in range(factor[1]): if i != 0: beta = (beta * pow(g, -(c_i_list[-1] * (factor[0] ** (i - 1))), p)) % p else: beta = h e1 = pow(beta, (p-1) // (factor[0] ** (i + 1)), p) e2 = pow(g, (p-1) // factor[0], p) for c_i in (range(factor[0])): if pow(e2, c_i, p) == e1: c_i_list.append(c_i) break x.append(c_i_list) # 将模p_i意义下的p_i进制表示转换为模p_i意义下的十进制 system = [] for i, factor in enumerate(factors): res = 0 for j, x_j in enumerate(x[i]): res += x_j * (factor[0] ** j) res = res % (factor[0] ** factor[1]) system.append(res) # 中国剩余定理 factors = [factors[i][0] ** factors[i][1] for i in range(len(factors))] result = gf_crt(system, factors, ZZ) return result ``` ```python= #Sage Code 1 p = a = b = gx = gy = px = py = E = EllipticCurve(GF(p), [a, b]) G = E(gx, gy) n = E.order() QA = E(px, py) factors = list(factor(n)) m = 1 moduli = [] remainders = [] print(factors) for i, j in factors: if i > 10**9: print(i) break mod = i**j g2 = G*(n//mod) q2 = QA*(n//mod) r = discrete_log(q2, g2, operation='+') remainders.append(r) moduli.append(mod) m *= mod r = crt(remainders, moduli) print(r) ``` ```python= #Sage Code 2 E = EllipticCurve(GF(p), [a, b]) G = E() Q = E() factors, exponents = zip(*factor(E.order())) primes = [factors[i] ^ exponents[i] for i in range(len(factors))][:-2] print(primes) dlogs = [] for fac in primes: t = int(int(G.order()) // int(fac)) dlog = discrete_log(t*Q,t*G,operation="+") dlogs += [dlog] print("factor: "+str(fac)+", Discrete Log: "+str(dlog)) #calculates discrete logarithm for each prime order l = crt(dlogs,primes) print(l) ``` ## Exceptional Curves ### source code ```python= from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from random import randint import hashlib FLAG = b'crypto{??????????????????????}' def gen_public_key(): private = randint(1, E.order() - 1) public = G * private return(public, private) def shared_secret(public_key, private_key): S = public_key * private_key return S.xy()[0] def encrypt_flag(flag): # Bob's public key b_x = 0x7f0489e4efe6905f039476db54f9b6eac654c780342169155344abc5ac90167adc6b8dabacec643cbe420abffe9760cbc3e8a2b508d24779461c19b20e242a38 b_y = 0xdd04134e747354e5b9618d8cb3f60e03a74a709d4956641b234daa8a65d43df34e18d00a59c070801178d198e8905ef670118c15b0906d3a00a662d3a2736bf B = E(b_x, b_y) # Calculate shared secret A, nA = gen_public_key() print(f'Public Key: {A}') secret = shared_secret(B, nA) # Derive AES key from shared secret sha1 = hashlib.sha1() sha1.update(str(secret).encode('ascii')) key = sha1.digest()[:16] # Encrypt flag iv = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pad(FLAG, 16)) # Prepare encryption to send data = {} data['iv'] = iv.hex() data['encrypted_flag'] = ciphertext.hex() return data # Curve params p = 0xa15c4fb663a578d8b2496d3151a946119ee42695e18e13e90600192b1d0abdbb6f787f90c8d102ff88e284dd4526f5f6b6c980bf88f1d0490714b67e8a2a2b77 a = 0x5e009506fcc7eff573bc960d88638fe25e76a9b6c7caeea072a27dcd1fa46abb15b7b6210cf90caba982893ee2779669bac06e267013486b22ff3e24abae2d42 b = 0x2ce7d1ca4493b0977f088f6d30d9241f8048fdea112cc385b793bce953998caae680864a7d3aa437ea3ffd1441ca3fb352b0b710bb3f053e980e503be9a7fece # Define curve E = EllipticCurve(GF(p), [a, b]) # Protect against Pohlig-Hellman Algorithm assert is_prime(E.order()) # Create generator G = E.gens()[0] print(f'Generator: {G}') encrypted_flag = encrypt_flag(FLAG) print(encrypted_flag) ``` ### output.txt ``` Generator: (3034712809375537908102988750113382444008758539448972750581525810900634243392172703684905257490982543775233630011707375189041302436945106395617312498769005 : 4986645098582616415690074082237817624424333339074969364527548107042876175480894132576399611027847402879885574130125050842710052291870268101817275410204850 : 1) Public Key: (4748198372895404866752111766626421927481971519483471383813044005699388317650395315193922226704604937454742608233124831870493636003725200307683939875286865 : 2421873309002279841021791369884483308051497215798017509805302041102468310636822060707350789776065212606890489706597369526562336256272258544226688832663757 : 1) {'iv': '719700b2470525781cc844db1febd994', 'encrypted_flag': '335470f413c225b705db2e930b9d460d3947b3836059fb890b044e46cbb343f0'} ``` ### 檢查 我發現了 $E$ 的order跟 $p$ 一樣 ```sage= sage: p = 0xa15c4fb663a578d8b2496d3151a946119ee42695e18e13e90600192b1d0abdbb6f787f90c8d102ff88e284dd4526f5f6b6c980bf88f1d0490714b67e8a2a2b77 ....: a = 0x5e009506fcc7eff573bc960d88638fe25e76a9b6c7caeea072a27dcd1fa46abb15b7b6210cf90caba982893ee2779669bac06e267013486b22ff3e24abae2d42 ....: b = 0x2ce7d1ca4493b0977f088f6d30d9241f8048fdea112cc385b793bce953998caae680864a7d3aa437ea3ffd1441ca3fb352b0b710bb3f053e980e503be9a7fece ....: ....: E = EllipticCurve(GF(p),[a,b]) ....: print(E.order()) ....: print(E.order()==p) ....: 8451139905551902831160354990243448994735923344327179631310525520435196861496551764162970081762137226285330762159796842785356834064520159547540428116601719 True ``` 可以使用 **Smart’s Attack** 證明: https://wstein.org/edu/2010/414/projects/novotney.pdf ```python= def SmartAttack(P,Q,p): E = P.curve() Eqp = EllipticCurve(Qp(p, 2), [ ZZ(t) + randint(0,p)*p for t in E.a_invariants() ]) P_Qps = Eqp.lift_x(ZZ(P.xy()[0]), all=True) for P_Qp in P_Qps: if GF(p)(P_Qp.xy()[1]) == P.xy()[1]: break Q_Qps = Eqp.lift_x(ZZ(Q.xy()[0]), all=True) for Q_Qp in Q_Qps: if GF(p)(Q_Qp.xy()[1]) == Q.xy()[1]: break p_times_P = p*P_Qp p_times_Q = p*Q_Qp x_P,y_P = p_times_P.xy() x_Q,y_Q = p_times_Q.xy() phi_P = -(x_P/y_P) phi_Q = -(x_Q/y_Q) k = phi_Q/phi_P return ZZ(k) ``` ### solve ```python= from Crypto.Cipher import AES from Crypto.Util.number import inverse from Crypto.Util.Padding import pad, unpad from collections import namedtuple from random import randint import hashlib from sage.all import * def SmartAttack(P,Q,p): E = P.curve() Eqp = EllipticCurve(Qp(p, 2), [ ZZ(t) + randint(0,p)*p for t in E.a_invariants() ]) P_Qps = Eqp.lift_x(ZZ(P.xy()[0]), all=True) for P_Qp in P_Qps: if GF(p)(P_Qp.xy()[1]) == P.xy()[1]: break Q_Qps = Eqp.lift_x(ZZ(Q.xy()[0]), all=True) for Q_Qp in Q_Qps: if GF(p)(Q_Qp.xy()[1]) == Q.xy()[1]: break p_times_P = p*P_Qp p_times_Q = p*Q_Qp x_P,y_P = p_times_P.xy() x_Q,y_Q = p_times_Q.xy() phi_P = -(x_P/y_P) phi_Q = -(x_Q/y_Q) k = phi_Q/phi_P return ZZ(k) def shared_secret(public_key, private_key): S = public_key * private_key return S.xy()[0] p = 0xa15c4fb663a578d8b2496d3151a946119ee42695e18e13e90600192b1d0abdbb6f787f90c8d102ff88e284dd4526f5f6b6c980bf88f1d0490714b67e8a2a2b77 a = 0x5e009506fcc7eff573bc960d88638fe25e76a9b6c7caeea072a27dcd1fa46abb15b7b6210cf90caba982893ee2779669bac06e267013486b22ff3e24abae2d42 b = 0x2ce7d1ca4493b0977f088f6d30d9241f8048fdea112cc385b793bce953998caae680864a7d3aa437ea3ffd1441ca3fb352b0b710bb3f053e980e503be9a7fece E = EllipticCurve(GF(p), [a, b]) G = E.gens()[0] public=E(4748198372895404866752111766626421927481971519483471383813044005699388317650395315193922226704604937454742608233124831870493636003725200307683939875286865 , 2421873309002279841021791369884483308051497215798017509805302041102468310636822060707350789776065212606890489706597369526562336256272258544226688832663757) private_key=SmartAttack(G,public,p) print("private key:",private_key) b_x = 0x7f0489e4efe6905f039476db54f9b6eac654c780342169155344abc5ac90167adc6b8dabacec643cbe420abffe9760cbc3e8a2b508d24779461c19b20e242a38 b_y = 0xdd04134e747354e5b9618d8cb3f60e03a74a709d4956641b234daa8a65d43df34e18d00a59c070801178d198e8905ef670118c15b0906d3a00a662d3a2736bf B = E(b_x, b_y) secret = shared_secret(B, private_key) sha1 = hashlib.sha1() sha1.update(str(secret).encode('ascii')) key = sha1.digest()[:16] iv= bytes.fromhex('719700b2470525781cc844db1febd994') encrypted_flag=bytes.fromhex('335470f413c225b705db2e930b9d460d3947b3836059fb890b044e46cbb343f0') cipher = AES.new(key, AES.MODE_CBC, iv) m=cipher.decrypt(pad(encrypted_flag, 16)) print(m) ``` ![image](https://hackmd.io/_uploads/r1VpL7cz0.png) ## Micro Transmissions ### source code ```python from Crypto.Util.number import getPrime from Crypto.Random import random from Crypto.Cipher import AES from Crypto.Util.number import inverse from Crypto.Util.Padding import pad, unpad import hashlib FLAG = b"crypto{???????????????????}" def gen_key_pair(G, nbits): n = random.getrandbits(nbits) P = n*G return P.xy()[0], n def gen_shared_secret(P, n): S = n*P return S.xy()[0] def encrypt_flag(shared_secret: int): # Derive AES key from shared secret sha1 = hashlib.sha1() sha1.update(str(shared_secret).encode('ascii')) key = sha1.digest()[:16] # Encrypt flag iv = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pad(FLAG, 16)) # Prepare data to send data = {} data['iv'] = iv.hex() data['encrypted_flag'] = ciphertext.hex() return data # Efficient key exchange nbits = 64 pbits = 256 # Curve parameters p = getPrime(pbits) a = 1 b = 4 E = EllipticCurve(GF(p), [a,b]) G = E.gens()[0] print(f"Sending curve parameters:") print(f"{E}") print(f"Generator point: {G}\n") # Generate key pairs ax, n_a = gen_key_pair(G, nbits) bx, n_b = gen_key_pair(G, nbits) print(f"Alice sends public key: {ax}") print(f"Bob sends public key: {bx}\n") # Calculate point from Bob B = E.lift_x(bx) # Encrypted flag with shared secret shared_secret = gen_shared_secret(B,n_a) encrypted_flag = encrypt_flag(shared_secret) print(f"Alice sends encrypted_flag: {encrypted_flag}") ``` ### output.txt ``` Sending curve parameters: Elliptic Curve defined by y^2 = x^3 + x + 4 over Finite Field of size 99061670249353652702595159229088680425828208953931838069069584252923270946291 Generator point: (43190960452218023575787899214023014938926631792651638044680168600989609069200 : 20971936269255296908588589778128791635639992476076894152303569022736123671173 : 1) Alice sends public key: 87360200456784002948566700858113190957688355783112995047798140117594305287669 Bob sends public key: 6082896373499126624029343293750138460137531774473450341235217699497602895121 Alice sends encrypted_flag: {'iv': 'ceb34a8c174d77136455971f08641cc5', 'encrypted_flag': 'b503bf04df71cfbd3f464aec2083e9b79c825803a4d4a43697889ad29eb75453'} ``` ### 解法 這題應該算pohlig-hellman的進階利用,首先先確認order(G) ```sage= from sage.all import * p = 99061670249353652702595159229088680425828208953931838069069584252923270946291 a = 1 b = 4 E = EllipticCurve(GF(p), [a,b]) G=E(43190960452218023575787899214023014938926631792651638044680168600989609069200 , 20971936269255296908588589778128791635639992476076894152303569022736123671173) factor(G.order()) ``` 可以看到$order(G)$是 ``` 7 * 11 * 17 * 191 * 317 * 331 * 5221385621 * 5397618469 * 210071842937040101 * 637807437018177170959577732683 ``` 發現最後兩個質因數非常大,如果要用pohlig-hellman複雜度會太高,但是可以看到secret key的bit只有64,所以可以不管最後兩個 ![image](https://hackmd.io/_uploads/B1Zdw_BQA.png) $x_A=G \times n_A(64 bits)$ 先找出$n_A$就可以找出$shared$了 $shared=B \times n_A$ ```python= from Crypto.Cipher import AES from Crypto.Util.number import inverse from Crypto.Util.Padding import pad, unpad from collections import namedtuple from random import randint import hashlib from sage.all import * def gen_shared_secret(P, n): S = n*P return S.xy()[0] p = 99061670249353652702595159229088680425828208953931838069069584252923270946291 a = 1 b = 4 E = EllipticCurve(GF(p), [a,b]) G=E(43190960452218023575787899214023014938926631792651638044680168600989609069200 , 20971936269255296908588589778128791635639992476076894152303569022736123671173) A = E.lift_x(ZZ(87360200456784002948566700858113190957688355783112995047798140117594305287669)) orderG = G.order() # 少取後兩個factor factors = list(factor(orderG))[:-2] m = 1 moduli = [] remainders = [] print(factors) # pohlig-hellman for i, j in factors: mod = i**j g2 = G*(orderG//mod) q2 = A*(orderG//mod) r = discrete_log(q2, g2, operation='+') remainders.append(r) moduli.append(mod) m *= mod n_a = crt(remainders, moduli) print("n_a:",n_a) # 產生secret key B=E.lift_x(ZZ(6082896373499126624029343293750138460137531774473450341235217699497602895121)) secret=gen_shared_secret(B,n_a) # 解密 sha1 = hashlib.sha1() sha1.update(str(secret).encode('ascii')) key = sha1.digest()[:16] iv= bytes.fromhex('ceb34a8c174d77136455971f08641cc5') encrypted_flag=bytes.fromhex('b503bf04df71cfbd3f464aec2083e9b79c825803a4d4a43697889ad29eb75453') cipher = AES.new(key, AES.MODE_CBC, iv) m=cipher.decrypt(pad(encrypted_flag, 16)) print(m) ``` 由此可知 $n$ 的bit如果太小,就算$order(G)$不是smooth,也可以破解 另外 如果你有該$E$上一點的x,可以用這個來找回y,變回一組點 ``` E.lift_x(ZZ()) ```