# Isogenies/Cryptohack * Đây là 1 mảng mới được ứng dụng dựa trên các đường cong elliptic siêu kỳ dị. Từ đó phát triển lên các khái niệm mật mã mới là **SIDH** và **CSIDH**. * Để nhớ lại các khái niệm liên quan tới **ECC** thì mình tham khảo [ở đây](https://hackmd.io/@nomorecaffeine/SyjQeqRZh#ELLIPTIC-CURVES-IN-CRYPTOGRAPHY) * Do các hàm trong **sagemath** liên quan tới **Isogenies** khá mới nên mình có đọc ở [đây](https://doc.sagemath.org/html/en/reference/arithmetic_curves/sage/schemes/elliptic_curves/ell_curve_isogeny.html) * Còn 1 số khái niệm quan trọng nữa của mảng này đó là * **Phép đẳng cấu giữa các đường cong elliptic**: * Nếu dựa trên lí thuyết đồ thị như trong toán rời rạc hoặc mấy phần graph trong lập trình thi đấu thì xem [ở đây](https://vi.wikipedia.org/wiki/Ph%C3%A9p_%C4%91%E1%BA%B3ng_c%E1%BA%A5u_%C4%91%E1%BB%93_th%E1%BB%8B) * **Phép đẳng cấu (isomorphism)** giữa hai đường cong elliptic là **một ánh xạ (hàm ánh xạ)** bảo toàn cấu trúc nhóm giữa các điểm trên hai đường cong elliptic. Xét hai đường cong elliptic $E_1,E_2$, thì định nghĩ 1 **phép đẳng cấu** là **phép song ánh (bijection)** $φ : E_1 -> E_2$ sao cho φ là một phép đồng cấu nhóm: nghĩa là với mọi điểm $𝑃,𝑄∈𝐸_1$ , ta có $𝜑(𝑃+𝑄)=𝜑(𝑃)+𝜑(𝑄)$ trong $𝐸_2$. Đây là điều kiện bảo toàn phép cộng trên các đường cong elliptic. * Phép ánh xạ $𝜑$ phải bảo toàn điểm gốc $O$ (điểm vô cực), tức là $𝜑(𝑂_{𝐸_1})=𝑂_{𝐸_2}$ , trong đó $𝑂_{𝐸_1}$ và $0_{𝐸_2}$ là các điểm vô cực tương ứng của $𝐸_1$ và $𝐸_2$ * $𝜑$ phải là một ánh xạ đại số khả nghịch, nghĩa là tồn tại một ánh xạ $𝜑^{−1} : 𝐸_2→𝐸1$ để ánh xạ ngược lại. $𝜑^{−1}$ còn được gọi là **isogeny** đối ngẫu. * Ví dụ: Cho $E_1$ : $y^2 = x^3 + x + 2$ và $E_2$ : $y^2 = x^3 + 16x + 128$ Thì ta có thể tìm được 1 trong rất nhiều phép đẳng cấu thỏa mãn chẳng hạn như : $𝜑(x,y) = (4x,8y)$. Để kiểm tra thì đơn giản bạn chỉ cần thay giá trị $x' = 4x,y' = 8y$ vào $E_1$ xem có ra 1 đường cong mới giống $E_2$ không là được. Còn phép $𝜑^{−1}(x,y) = (\frac{x}{4},\frac{y}{8})$ . Hoặc ví dụ khác: ![image](https://hackmd.io/_uploads/H1m4pHNCA.png) * Ý nghĩa: Vậy phép đẳng cấu này có tác dụng gì ? Dựa vào những gì mình tìm hiểu ở trên thì do nó bảo toàn cấu trúc nhóm giữa 2 đường cong elliptic, hay dễ hiểu hơn là nó "giống nhau" bản chất dù khác công thức . Từ đó cho ta chuyển đổi giữa các đường cong mà không làm mất đi các thuộc tính cơ bản của chúng. * **Kernel**: * **Kernel** của một **isogeny** giữa **hai đường cong elliptic** là **tập hợp các điểm** trên **đường cong nguồn** mà khi áp dụng isogeny, chúng **ánh xạ tới điểm vô cùng** trên **đường cong đích**. * Ví dụ có 1 isogeny $𝜑 : E_1 → E_2$ thì $ker(𝜑)$ là tập hợp các điểm $P ∈ E_1$ sao cho $𝜑(P)=O$ với $O$ là điểm vô cực trên $E_2$. * Kích thước của **Kernel**: Kích thước của **kernel** cho biết số lượng điểm trong tập hợp này. Nếu **kernel** có kích thước $𝑛$, điều này có nghĩa là có $n$ điểm ánh xạ tới điểm vô cùng. * Ngắn gọn lại thì nếu **isogeny** có bậc $n$, thì $∣ker(𝜙)∣=n$ * **Degree của Isogeny**: * **Degree** của một isogeny là **số lượng các điểm trong kernel**. Nếu isogeny có bậc **d**, điều đó có nghĩa là kích thước của **kernel** là **d**. * Ví dụ: Khi một isogeny có bậc là 2, điều này có nghĩa là kernel chứa 2 điểm. Trong trường hợp này, kernel sẽ bao gồm điểm vô cực và một điểm khác trên đường cong. * Túm lại : **size of a kernel = degree** # Challenge :::warning Do là mảng này mình viết khá dài, mà hackmd giới hạn độ dài writeups nên mình xin phép không push source code của challenge đã cho. Các bạn đọc thì chịu khó lên [Cryptohack](https://cryptohack.org/) để xem đề nhen. ::: ## Introduction ### Introduction to Isogenies * ![image](https://hackmd.io/_uploads/ry-SmLNAR.png) * Như mình nói ở trên thì : **size of a kernel = degree** * **Flag: 65537** ## Starter ### The j-invariant * ![image](https://hackmd.io/_uploads/SkLp7L4C0.png) * Ở đây họ kí hiệu $k$ là 1 trường thì $\overline{k}$ là [phần đóng đại số](https://en.wikipedia.org/wiki/Algebraic_closure) của nó. * Thì họ bonus thêm là 2 đường cong đồng cấu trên $\overline{k}$ khi và chỉ khi nó có cùng 1 giá trị $j(E)$. * Full script: ```py E = EllipticCurve(GF(163), [145, 49]) print(E.j_invariant()) ``` * **Flag: 127** ### Where's the Supersingular Curve * ![image](https://hackmd.io/_uploads/B1wbvIERA.png) * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from sage.all import * p = 2**127 - 1 F = GF(p) A_coefficients = [42066522414747786381920855365597565167, 134737796133950768030018008799183714450, 91227728321286829861377337128366199392, 36028658925231693144107803298680870937, 76980490750956259225056736203333999910, 126464584711216077690137140186180693785, 120907192356535756729244966659175101927, 2073101923485307949792397315592577773, 106605223173829520914696961378825556644, 132050324183446216441182346564708456430, 124925153504327803154756555702421815457, 77660524902287384914209674976778162880, 16925059684259068279300282630403302492, 132526617782398476874569966601318594117, 37496280130274706477358606089317978157, 91534360167274359091293339760566159682, 120709879291518822698484419346768523476, 65363777855209839999210042121004357333, 55967872730073787267488839585941569643, 36898228725344658691223127152754689708, 154541377159837330781469948056573864248, 13491755064214040906252463281637003507, 108942779083954481764769642529914352298, 148339474803908132859207234892176688872, 52779580168403190690189094462689129092, 133118771370904939228695413069672106301, 37742029987768224512401633920499017927, 83070983011286908751419953762864350804, 27579898547487494629585968117425586134, 115653129582046969888509682678358651292, 101704360405093002365341608151788197801, 26577867537091108935632327578899771513, 63823008561770200181128212771463569059, 65567745784333136215045519526981712608, 94067826399698019061457587710503262758, 157599028084077522633805211440852474188, 50964910790177922795145482477309765050, 120922548311156791677654872844569348131, 59168891682494541493872447445083865177, 147837049250659841035474307770567756447, 13642843499473036336330049788238558798, 45666199191182278515747915944910919562, 103074928257298029070114474733055407882, 16962967100651145532896179458853429025, 154909948485415956842534840410675083382, 22820775199676215898647235824150575751, 154329269756413993112920009420869474380, 138300956197972238000951467341005201893, 166046660427895907846214752672019787536, 141774513481148066771761235501253922565, 124404668777350018566758645287948962779, 141710390876873989703209183888838141129, 43383267258553897726444281302908756439, 8382163174802298716075674661700526105, 42647418827645100430740897684493106281, 115369337788739564019292557470108514746, 51602469273125266568465673976482373008, 21274053188388536879722524326434447058, 155399839214546532918143677492979875025, 134844799136804904273265536589445951710, 170141183460469230846243588177825628225, 34714351369438188536819236204585680389, 106014160818648567132647832826887771006, 26341808657057110683680629380277446393, 98549712359055082009930885194365045772, 143002488899264107138574246913562763245, 83199987237136698285155302750237377917, 163678062518962214263756137167913332057, 107703968966152189273001348451115931501, 154459054255799385370678297918227508273, 160394354920882308230455249642244327876, 63082984734540825428754448178295870172, 158310155193384062203205713864649469386, 156168266884432986041374465845782494518, 110147569271988413629697268109820438653, 60009165402929810599765761482393291600, 160471809661117354771700362601108099823, 70205649924045989651475317850057844161, 44696813017317041212253313033993441039, 45036886626031031391784472846348343968, 77934017380754619764511795264587760758, 23111293661357074492564163833427567827, 39989430534767072035052164998617514373, 435384467928024061094999646205823086, 109651347199199233302113859638926694534, 140233796564908846498304362688605532781, 159429449304855699713742269141196048727, 84378534640632088318459736438433440511, 35039618806759555128366649831062612185, 94527269434501797889731458720771789225, 161408562964041668171049211002143456626, 3870684416023152928311974603228368345, 28196517242154639492840099181523267155, 99806088062582844867731983345213108996, 53032136485981165230199947023828963494, 108846135438150868833980774050157615416, 111171383309545532264862195892560386067, 56167964140714208429900388258541421783, 133956857210332500232234627352247869689, 105983790995194300223960924968597095964] curves = [] for A in A_coefficients: E = EllipticCurve(F, [0, A, 0, 1, 0]) curves.append(E) if E.cardinality() == p + 1: print(A) ``` * Hoặc 1 cách làm khác được tham khảo ở [đây](https://doc.sagemath.org/html/en/reference/arithmetic_curves/sage/schemes/elliptic_curves/ell_finite_field.html#sage.schemes.elliptic_curves.ell_finite_field.EllipticCurve_finite_field.is_supersingular) ```py A_coefficients = [42066522414747786381920855365597565167, 134737796133950768030018008799183714450, 91227728321286829861377337128366199392, 36028658925231693144107803298680870937, 76980490750956259225056736203333999910, 126464584711216077690137140186180693785, 120907192356535756729244966659175101927, 2073101923485307949792397315592577773, 106605223173829520914696961378825556644, 132050324183446216441182346564708456430, 124925153504327803154756555702421815457, 77660524902287384914209674976778162880, 16925059684259068279300282630403302492, 132526617782398476874569966601318594117, 37496280130274706477358606089317978157, 91534360167274359091293339760566159682, 120709879291518822698484419346768523476, 65363777855209839999210042121004357333, 55967872730073787267488839585941569643, 36898228725344658691223127152754689708, 154541377159837330781469948056573864248, 13491755064214040906252463281637003507, 108942779083954481764769642529914352298, 148339474803908132859207234892176688872, 52779580168403190690189094462689129092, 133118771370904939228695413069672106301, 37742029987768224512401633920499017927, 83070983011286908751419953762864350804, 27579898547487494629585968117425586134, 115653129582046969888509682678358651292, 101704360405093002365341608151788197801, 26577867537091108935632327578899771513, 63823008561770200181128212771463569059, 65567745784333136215045519526981712608, 94067826399698019061457587710503262758, 157599028084077522633805211440852474188, 50964910790177922795145482477309765050, 120922548311156791677654872844569348131, 59168891682494541493872447445083865177, 147837049250659841035474307770567756447, 13642843499473036336330049788238558798, 45666199191182278515747915944910919562, 103074928257298029070114474733055407882, 16962967100651145532896179458853429025, 154909948485415956842534840410675083382, 22820775199676215898647235824150575751, 154329269756413993112920009420869474380, 138300956197972238000951467341005201893, 166046660427895907846214752672019787536, 141774513481148066771761235501253922565, 124404668777350018566758645287948962779, 141710390876873989703209183888838141129, 43383267258553897726444281302908756439, 8382163174802298716075674661700526105, 42647418827645100430740897684493106281, 115369337788739564019292557470108514746, 51602469273125266568465673976482373008, 21274053188388536879722524326434447058, 155399839214546532918143677492979875025, 134844799136804904273265536589445951710, 170141183460469230846243588177825628225, 34714351369438188536819236204585680389, 106014160818648567132647832826887771006, 26341808657057110683680629380277446393, 98549712359055082009930885194365045772, 143002488899264107138574246913562763245, 83199987237136698285155302750237377917, 163678062518962214263756137167913332057, 107703968966152189273001348451115931501, 154459054255799385370678297918227508273, 160394354920882308230455249642244327876, 63082984734540825428754448178295870172, 158310155193384062203205713864649469386, 156168266884432986041374465845782494518, 110147569271988413629697268109820438653, 60009165402929810599765761482393291600, 160471809661117354771700362601108099823, 70205649924045989651475317850057844161, 44696813017317041212253313033993441039, 45036886626031031391784472846348343968, 77934017380754619764511795264587760758, 23111293661357074492564163833427567827, 39989430534767072035052164998617514373, 435384467928024061094999646205823086, 109651347199199233302113859638926694534, 140233796564908846498304362688605532781, 159429449304855699713742269141196048727, 84378534640632088318459736438433440511, 35039618806759555128366649831062612185, 94527269434501797889731458720771789225, 161408562964041668171049211002143456626, 3870684416023152928311974603228368345, 28196517242154639492840099181523267155, 99806088062582844867731983345213108996, 53032136485981165230199947023828963494, 108846135438150868833980774050157615416, 111171383309545532264862195892560386067, 56167964140714208429900388258541421783, 133956857210332500232234627352247869689, 105983790995194300223960924968597095964] for A in A_coefficients: E = EllipticCurve(GF(2**127-1), [0, A, 0, 1, 0]) if E.is_supersingular(): print(A) ``` * **Flag:170141183460469230846243588177825628225** ### Image Point Arithmetic * Áp dụng tính chất của phép đẳng cấu thôi: $𝜑(𝑃+𝑄)=𝜑(𝑃)+𝜑(𝑄)$ * ![image](https://hackmd.io/_uploads/SJN3PLVR0.png) ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * phi_P = (48622,27709) phi_Q = (9460,13819) p = 63079 x1,y1 = phi_P[0],phi_P[1] x2,y2 = phi_Q[0],phi_Q[1] m = ((y2-y1)*pow(x2-x1,-1,p))%p x3 = (m**2 - x1 - x2)%p print(x3) ``` * **Flag:37097** ### Montgomery Curves * Tìm **Montgomery point** theo [doc này của sagemath](https://doc.sagemath.org/html/en/reference/arithmetic_curves/sage/schemes/elliptic_curves/ell_generic.html#sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic.montgomery_model) * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC p = 1912812599 a = 312589632 b = 654443578 E = EllipticCurve(GF(p), [a, b]) Ea = E.montgomery_model() print(Ea) ``` * **Flag:723347356** ### DLOG on the Surface * Ở đây có 1 khái niệm mới đó là [The Weil pairing](https://github.com/defeo/MathematicsOfIBC/blob/popayan-temp/poly.pdf). Mục 6 nhe :satisfied: * ![image](https://hackmd.io/_uploads/HJtPmzBRC.png) * Nhưng [ở đây](https://www.sagemath.org/files/thesis/hansen-thesis-2009.pdf) thì nó lại không giống lắm :)) * ![image](https://hackmd.io/_uploads/BJU4DMBC0.png) * Nôm na thì **Weil pairing** $e_n(P,Q)$ là 1 phép ghép đôi 2 điểm $P,Q$ trong nhóm $E[n]$ thuộc cùng 1 đường cong Elliptic $E$. * 1 số tính chất quan trọng của **Weil pairing**: * **Song tuyến tính**: $e_n(aP,bQ)$ = $e_n(P,Q)^{ab}$ * **Luân phiên**: **Weil pairing** thỏa mãn $𝑒_𝑛(𝑃,𝑃)=1$ cho mọi $𝑃∈𝐸[𝑛]$. * **Không suy biến**: Nếu $𝑒_𝑛(𝑃,𝑄)=1$ cho mọi $Q∈E[n]$, thì $P=(0:1:0)$. * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 iv = bytes.fromhex('6f5a901b9dc00aded4add3791812883b') ct = bytes.fromhex('56ecb68a90cad9787a24a4511720d40d625901577f6d0f1eef9fc34cf042709110cdc061fff91e934877674a30ed911283b83927dbcc270ae358d6b1fe2d5bed18ce1b02d8805de55e5b36deb0d28883') def decrypt_flag(a, b, c, d,iv,ct): data_abcd = str(a) + str(b) + str(c) + str(d) key = SHA256.new(data=data_abcd.encode()).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag from sage.all import * p = 2**127 - 1 F.<i> = GF(p^2, modulus=[1,0,1]) E = EllipticCurve(F, [1,0]) P, Q = E.gens() P = E((24722427318870186874942502106037863239*i + 62223422355562631021732597235582046928, 66881667812593541117238448140445071224*i + 149178354082347398743922440593055790802)) Q = E((136066972787979381470429160016223396048*i + 52082760150043245190232762320312239515, 37290474751398918861353632929218878189*i + 89777436105166947842660822806860901885)) R = E((115434063687215369570994517493754451626*i + 158874018596958922133589852067300239562, 62259011436032820287439957155108559928*i + 81253318200557694469168638082106161224)) S = E((42595488035799156418773068781330714859*i + 113049342376647649006990912915011269440, 25404988689109287499485677343768857329*i + 125117346805247292256813555413193592812)) n = p + 1 assert n == P.order() == Q.order() e = P.weil_pairing(Q, n) a = R.weil_pairing(Q, n).log(e) % n b = -R.weil_pairing(P, n).log(e) % n c = S.weil_pairing(Q, n).log(e) % n d = -S.weil_pairing(P, n).log(e) % n flag = decrypt_flag(a,b,c,d,iv,ct) print(flag) ``` * Giải thích : * Giá trị $e$ chính là $e_n(P,Q)$, là cơ sở gốc để tính toán logarithm rời rạc. * Với giá trị $a$ tính như kia thì mình giải thích như sau: * $R$.**wei_pairing**$(Q, n)$ = $e_n(R,Q)$ = $e_n(aP + bQ,Q)$ = $e_n(aP,Q)$ * $e_n(bQ,Q)$ = $e_n(P,Q)^a$ * $e_n(Q,Q)^b$ = $e_n(P,Q)^a$ * $1$ # do tính luân phiên. = $e^a$ * Vậy tính $a$ thì thông qua phương thức **log(e)** thôi. * Với giá trị $b$: * $R$.**wei_pairing**$(P, n)$ = $e_n(R,P)$ = $e_n(aP + bQ,P)$ = $e_n(aP,P)$ * $e_n(bQ,P)$ = $e_n(P,Q)^a$ * $e_n(Q,P)^b$ = $1$ * $(e_n(P,Q)^{-b})$ # do tính luân phiên. = $e^{-b}$ * Các giá trị $c,d$ còn lại cũng tính tương tự nhe. * **Flag:Flag:crypto{now_try_writing_a_function_for_fast_torsion_basis_generation!}** ## Road to SIDH ### Two Isogenies * ![image](https://hackmd.io/_uploads/S1qTbmSRA.png) * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 2**18 * 3**13 - 1 F.<i> = GF(p^2, modulus=[1,0,1]) E = EllipticCurve(F, [1,0]) K = E(i, 0) phi = E.isogeny(K) print(phi.codomain().j_invariant()) ``` * **Flag:287496** ### Three Isogenies * Dựa vào định lí **Vélu** ở trên thì ta có : * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 2**18 * 3**13 - 1 F.<i> = GF(p^2, modulus=[1,0,1]) E = EllipticCurve(F, [1,0]) K = E(483728976, 174842350631) K2 = 2 * K # Tính [2]K # Bước 3: Tính toán các tham số t_Q, u_Q, w_Q cho từng điểm trong kernel def compute_t_u_w(Q): x_Q, y_Q = Q.xy() # Lấy tọa độ x, y của điểm Q t_Q = 3 * x_Q^2 + E.a4() # t_Q = 3*x_Q^2 + A u_Q = 2 * y_Q^2 # u_Q = 2*y_Q^2 w_Q = u_Q + t_Q * x_Q # w_Q = u_Q + t_Q*x_Q return t_Q, u_Q, w_Q # Tính các giá trị t_Q, u_Q, w_Q cho K và [2]K t_K, u_K, w_K = compute_t_u_w(K) t_K2, u_K2, w_K2 = compute_t_u_w(K2) # Bước 4: Tính tổng t và w t = t_K + t_K2 # Tổng t cho tất cả các điểm trong kernel (bỏ qua 0) w = w_K + w_K2 # Tổng w cho tất cả các điểm trong kernel # Bước 5: Tính hàm r(x) def r(x): return x + (t_K / (x - K[0])) + (u_K / (x - K[0])^2) + (t_K2 / (x - K2[0])) + (u_K2 / (x - K2[0])^2) # Bước 6: Tính isogeny và đường cong đích E' A_prime = E.a4() - 5 * t # A' = A - 5t B_prime = E.a6() - 7 * w # B' = B - 7w E_prime = EllipticCurve([A_prime, B_prime]) # Đường cong đích E' # Bước 7: Tính j-invariant của E' j_invariant = E_prime.j_invariant() # Kết quả: j-invariant print(j_invariant) ``` * Còn không thì code bài trên cũng không có gì thay đổi cả: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 2**18 * 3**13 - 1 F.<i> = GF(p^2, modulus=[1,0,1]) E = EllipticCurve(F, [1,0]) K = E(483728976, 174842350631) phi = E.isogeny(K) print(phi.codomain().j_invariant()) ``` * **Flag:96392670793** ### Composite Isogenies * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 2**18 * 3**13 - 1 F.<i> = GF(p^2, modulus=[1,0,1]) E = EllipticCurve(F, [1,0]) K = E(357834818388*i+53943911829,46334220304*i+267017462655) phi = E.isogeny(K,algorithm='factored') print(phi.codomain().j_invariant()) ``` * **Flag:249510360818*i + 292990704480** ### SIDH Key Exchange * Đề bài đã cho chúng ta gợi ý về cách hoàn thiệu các hàm bị hidden rồi. Làm theo hướng dẫn thôi. * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 def decrypt_flag(shared_secret,iv,ct): key = SHA256.new(data=str(shared_secret).encode()).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag ea, eb = 110, 67 p = 2**ea*3**eb - 1 F.<i> = GF(p**2, modulus=[1,0,1]) # Public curve E0 = EllipticCurve(F, [1,0]) # Torsion points P2 = E0(118242575052254473701407051403380184157502700009529430046122822477*i + 57638278144985143549644316704182130279784191379170896458696787312, 80915735815367072410310689908590367651933218830435520913424043510*i + 35228327576503752484578273317308597612913304063200715424014549037) Q2 = E0(27856673727210297071672501895829918842041821446996051944738115273*i + 101349537690838191347553037323956940169953967852439843389873653018, 45955772915614774101614751673022340983778200451506382887743008335*i + 76499786039494489791183573966490259392789635716963920208794989512) P3 = E0(68702305424425607424554396971378391833152415806389206440833676844*i + 63162905189208938201083385603424075109355856156240516441321158383, 14452401602439328239712793251073780692192036710425129093829067649*i + 110903430163815016394569999096524527007769669322432532390635606190) Q3 = E0(50967992419888058158544483269655763559879646024537212566396940681*i + 39165103284419354968504615023980382940222714919046676966425620242, 113476160032430656302485251779124302915433268423829474022852380544*i + 74814862075401178218909769629701747112662266906635780085603780902) # Secret Keys sA = 225902606209514408534212339057054 sB = 38410379124791756271891302485727 iv = bytes.fromhex('05a4cfbce59acb952128af83c9694390') ct = bytes.fromhex('755b72b2e3bef2e7a4b2ce4a370f287ad04c1359bace25f3def8be23c0c49e89b4302408ad2dcb02d4875fe58c543d91') KA = P2 + sA * Q2 KB = P3 + sB * Q3 phi_B = E0.isogeny(KB, algorithm='factored') EA = phi_B.codomain() phi_B_P2_value = phi_B(P2) phi_B_Q2_value = phi_B(Q2) K_SA = phi_B_P2_value + sA * phi_B_Q2_value phi_A = E0.isogeny(KA, algorithm='factored') EB = phi_A.codomain() phi_A_P3_value = phi_A(P3) phi_A_Q3_value = phi_A(Q3) K_SB = phi_A_P3_value + sB * phi_A_Q3_value phi_SA = EA.isogeny(K_SA,algorithm='factored') phi_SB = EB.isogeny(K_SB,algorithm='factored') assert phi_SA.codomain().j_invariant() == phi_SB.codomain().j_invariant() flag = decrypt_flag(phi_SB.codomain().j_invariant(),iv,ct) print(flag) ``` * **Flag:crypto{congratulations_you_are_an_isogenist!}** ### Breaking SIDH * Sau 1 hồi đọc hết các **paper** người ta cho thì mình không hint được gì cả. :santa: ~~( Hoặc có thể do mình non )~~ * Sau 1 hồi vật vờ đi tìm thêm tài liệu thì mình có thấy được 1 [chall cũ](https://ctftime.org/writeup/36033) trên ctf time. * Nó cũng tương tự gần hết đó. Và thêm quả đề bài gợi ý là **[Castryck-Decru-Attack](https://github.com/GiacomoPope/Castryck-Decru-SageMath/tree/main)** thì có vẻ nó legit :)) * Việc của mình là kết hợp 2 **link** trên để **recover secret key** của Alice và Bob thôi. * Recover `Bob private key` dùng file **baby_SIDH.sage** nhe. Thêm các thông số của mình vào nữa. * Script tìm Bob_key: ```py from Crypto.Util.number import * # from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 # Local imports import public_values_aux from public_values_aux import * # Load Sage files load('castryck_decru_shortcut.sage') # Base field f, ea, eb = 45, 117, 73 p = f * 2**ea * 3**eb - 1 F.<i> = GF(p**2, modulus=[1,0,1]) i = F.gen() # Parameter curve E0 = EllipticCurve(F, [1,0]) P2 = E0(41638913017947770142057706586022928767376787911881052119498505735726709*i + 42302691118463190267789836743381974415789982933013793177648925959412830, 398449090103547450120756760379594152224190852334260435573814509309180322*i + 233727912055570344238170576715487571464638389819881232356163774868717298) Q2 = E0(206871044564057174074702402057560500385307897843892757429570895488690781*i + 345361103047041832006854423588672657209780675425156486637093636756615871, 282631959619596644195862856762358013067369927756734494353761273142886198*i + 424042212353240809393676298236811630816767068488333281198829926787994817) P3 = E0(283521037412897973446265653799790187322986891540188206463957119516634991*i + 54836870836833212880150277757892094716552831163383470059384919428256731, 161092808075362244162644980545740896186924081971711047761724742595133944*i + 197849415496275495990937264988931290383245818223505983906164366276638868) Q3 = E0(170277682213687313511268961329652085176588846737029816748389863411297316*i + 110089263347312130053961094497783417490346085079501067635398757092208463, 198706722901789469217115416185417389094489501048971022152415817993951671*i + 257742376830499988002294097301881965831489887177374506301544311076566391) # Alice Public Key EAa = 341962904043010047547037514847227313183414494665183661406956171643838238*i+466221816892334489710400228219157166377700467211697103422573316531708902 EAb = 145050307288456998377054421680667611887270583594997309677606837459597101*i+356616269078165763297060128562598654455817286639025312561154777133108454 EA = EllipticCurve(F, [EAa, EAb]) phiA_P3 = EA(180748165080544728482273627011017446820851964671689844940751613607027042*i + 386399657558539814871631131488087670882259175999167407491042206165954470, 327787397151786462956783797299341040567085054575313609836298710436619785*i + 196105866987178697015232911678783180764030519956520455354300809913687633) phiA_Q3 = EA(258847568494100760477286708131798122974987254057932346978697593107825530*i + 38490496870779580975180675248455270468746617165569920252532162902409675, 306358844762893197793766376830898073944594560219460755063030400129179690*i + 182510704835708623157232097040016637690000728438765134566515343181898854) # Bob's Public Key EBa = 157597846018840576315827348546173953758796122446152006870494733059078284*i+418874181143594794621438950780746815165757267567012420151805770819363162 EBb = 348442605611456141157838566652599367279127166743233127676107176086957458*i+420861724499942691978671729713269838482830342041809365231340529697579831 EB = EllipticCurve(F, [EBa, EBb]) phiB_P2 = EB(109886154562978369974676578144987934657805602134271866282447816334164466*i + 498136804583811803693793430218572265900633458962105597996566642692906940, 401907510168736586072062757459383110335655309941214043767511647670013113*i + 259294509134396545471598619471155420455160713238944617603235341590681170) phiB_Q2 = EB(273276717406517362983403161180051140354308241184578465602742126078679674*i + 164406415263418135648467271613257338667532330432266713670747057596335763, 1092247214699018416365584399170122624205811861075813640384878660598131*i + 354817717070268141803451242665805576947926358877574336158195333352961708) public_values_aux.p = p Fp2.<i> = GF(p^2, modulus=x^2+1) R.<x> = PolynomialRing(Fp2) a = ea b = eb check_torsion_points(E0, a, b, P2, Q2, P3, Q3) E0.set_order((p+1)^2) phi = EllipticCurveIsogeny(E0, x) E1728 = phi.codomain() for iota in E1728.automorphisms(): P = E1728.random_point() if iota(iota(P)) == -P: two_i = phi.post_compose(iota).post_compose(phi.dual()) print(two_i) break # =================================== # ===== ATTACK ==================== # =================================== def RunAttack(num_cores): return CastryckDecruAttack(E0, P2, Q2, EB, phiB_P2, phiB_Q2, two_i, num_cores=num_cores) if __name__ == '__main__' and '__file__' in globals(): if '--parallel' in sys.argv: # Set number of cores for parallel computation num_cores = os.cpu_count() print(f"Performing the attack in parallel using {num_cores} cores") else: num_cores = 1 recovered_key = RunAttack(num_cores) ``` * ![image](https://hackmd.io/_uploads/rJsRW0fkye.png) * `Bob_key = 39990433064274301814750584859416466` * Thực ra lúc đầu nếu gen **two_i** y như file **baby_SIDH.sage** thì lỗi tùm lum mà mình cũng không biết cách fix như nào ~~( chính xác là không biết vì sao không được)~~ * ![image](https://hackmd.io/_uploads/rJcLZNEkJl.png) * Mình nghĩ là do đường cong của bài này khác, nên **two_i** khác nhau. Nhưng may mà [chall cũ trên CTF Time](https://ctftime.org/writeup/36033) có đưa ra cách tạo **two_i** phù hợp. Nên mình mút đoạn code đó là được code hoàn chỉnh tìm **private_key**. * Đây nè :![image](https://hackmd.io/_uploads/HkcoGVE1kg.png) * Việc còn lại thì đơn giản rồi . * Script: ```py from Crypto.Util.number import * from gmpy2 import * import math from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from hashlib import md5 import json import time from Crypto.Hash import SHA256 import os import random import os import Crypto.Util.number as cun # Base field f, ea, eb = 45, 117, 73 p = f * 2**ea * 3**eb - 1 F.<i> = GF(p**2, modulus=[1,0,1]) i = F.gen() # Parameter curve E0 = EllipticCurve(F, [1,0]) P2 = E0(41638913017947770142057706586022928767376787911881052119498505735726709*i + 42302691118463190267789836743381974415789982933013793177648925959412830, 398449090103547450120756760379594152224190852334260435573814509309180322*i + 233727912055570344238170576715487571464638389819881232356163774868717298) Q2 = E0(206871044564057174074702402057560500385307897843892757429570895488690781*i + 345361103047041832006854423588672657209780675425156486637093636756615871, 282631959619596644195862856762358013067369927756734494353761273142886198*i + 424042212353240809393676298236811630816767068488333281198829926787994817) P3 = E0(283521037412897973446265653799790187322986891540188206463957119516634991*i + 54836870836833212880150277757892094716552831163383470059384919428256731, 161092808075362244162644980545740896186924081971711047761724742595133944*i + 197849415496275495990937264988931290383245818223505983906164366276638868) Q3 = E0(170277682213687313511268961329652085176588846737029816748389863411297316*i + 110089263347312130053961094497783417490346085079501067635398757092208463, 198706722901789469217115416185417389094489501048971022152415817993951671*i + 257742376830499988002294097301881965831489887177374506301544311076566391) # Alice Public Key EAa = 341962904043010047547037514847227313183414494665183661406956171643838238*i+466221816892334489710400228219157166377700467211697103422573316531708902 EAb = 145050307288456998377054421680667611887270583594997309677606837459597101*i+356616269078165763297060128562598654455817286639025312561154777133108454 EA = EllipticCurve(F, [EAa, EAb]) phiA_P3 = EA(180748165080544728482273627011017446820851964671689844940751613607027042*i + 386399657558539814871631131488087670882259175999167407491042206165954470, 327787397151786462956783797299341040567085054575313609836298710436619785*i + 196105866987178697015232911678783180764030519956520455354300809913687633) phiA_Q3 = EA(258847568494100760477286708131798122974987254057932346978697593107825530*i + 38490496870779580975180675248455270468746617165569920252532162902409675, 306358844762893197793766376830898073944594560219460755063030400129179690*i + 182510704835708623157232097040016637690000728438765134566515343181898854) # Bob's Public Key EBa = 157597846018840576315827348546173953758796122446152006870494733059078284*i+418874181143594794621438950780746815165757267567012420151805770819363162 EBb = 348442605611456141157838566652599367279127166743233127676107176086957458*i+420861724499942691978671729713269838482830342041809365231340529697579831 EB = EllipticCurve(F, [EBa, EBb]) phiB_P2 = EB(109886154562978369974676578144987934657805602134271866282447816334164466*i + 498136804583811803693793430218572265900633458962105597996566642692906940, 401907510168736586072062757459383110335655309941214043767511647670013113*i + 259294509134396545471598619471155420455160713238944617603235341590681170) phiB_Q2 = EB(273276717406517362983403161180051140354308241184578465602742126078679674*i + 164406415263418135648467271613257338667532330432266713670747057596335763, 1092247214699018416365584399170122624205811861075813640384878660598131*i + 354817717070268141803451242665805576947926358877574336158195333352961708) def decrypt_flag(iv,ct,shared_secret): key = SHA256.new(data=str(shared_secret).encode()).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag iv = bytes.fromhex('f45273daf12b8234bd41607d8b517913') ct = bytes.fromhex('65917ae4d3de3ba753d50bab78992b2239cb189493807fcc3da8d058da1eda7d993c041d0ecb09089808d759a982f087') sB = 39990433064274301814750584859416466 K = phiA_P3 + sB * phiA_Q3 E = EA.isogeny(K, algorithm="factored").codomain() shared_secret = E.j_invariant() flag = decrypt_flag(iv,ct,shared_secret) print(flag) ``` * **Flag:crypto{welcome_to_the_future_of_isogenies}** * Do mình lười ghép code nên chia thành 2 file. Các bạn thông cảm nhe. * À còn 1 điều nữa là mình có thử recover lại cả **Alice_key** tương tự như trên nhưng không được. Mình cũng không biết tại sao nhưng lỡ ra flag rồi thì mình không tìm cách fix nữa. :vampire: ## Road to CSIDH ### Special Isogenies * Bài này thì ta tìm điểm có $order = 5$ và tính đường cong $E'$ là kết quả của phép đẳng cấu $E$ với điểm vừa tìm được. * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 419 F = GF(p) E0 = EllipticCurve(F,[1,0]) P = None for point in E0.points(): if point.order() == 5: P = point break phi = E0.isogeny(P) E_prime = phi.codomain() Ea = E_prime.montgomery_model() Ea ``` * **Flag:199** ### Prime Power Isogenies * Nếu làm bằng **isogeny_ell_graph()**: * Script: ```python from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 E0 = EllipticCurve(GF(419), [1,0]) print(E0) # Xem xét đồ thị isogeny bậc 7 G7 = E0.isogeny_ell_graph(7, directed=False, label_by_j=True) G7.plot() ``` * ![image](https://hackmd.io/_uploads/B19a_QB00.png) * Đếm tay thì được 27. * Nếu muốn ra luôn kết quả thì: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 E = EllipticCurve(GF(419), [1, 0]) print(E.isogeny_ell_graph(7)) ``` * **Note**: * 1 cách làm khác là cứ đẳng cấu đường cong đó, tới khi nào về đường cong đó thì dừng: ```py p = 419 Fp = GF(p) E = EllipticCurve(Fp, (1, 0)) ctr = 0 j = E.j_invariant() while E.j_invariant() != j or ctr == 0: K = E.gen(0) K *= (p+1)//7 phi = E.isogeny(K) E = phi.codomain() ctr += 1 print(ctr) ``` * **Flag:27** ### Secret Exponents * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 419 F = GF(p) # Định nghĩa đường cong Montgomery ban đầu E0 = EllipticCurve(F,[1,0]) # y^2 = x^3 + x mod 419 print(E0) for i in range(2): P_order_3 = None for point in E0.points(): if point.order() == 3: P_order_3 = point break phi = E0.isogeny(P_order_3) E0 = phi.codomain() for i in range(3): P_order_ = None for point in E0.points(): if point.order() == 5: P_order_5 = point break phi = E0.isogeny(P_order_5) E0 = phi.codomain() for i in range(4): P_order_7 = None for point in E0.points(): if point.order() == 7: P_order_7 = point break phi = E0.isogeny(P_order_7) E0 = phi.codomain() Ea = E0.montgomery_model() Ea ``` * **Flag:404** ### Twisted CSIDH Isogenies * Cách làm đơn giản nhất đó là cứ đếm số lần để nó tiến được về đường cong cũ. Sau đó lùi 1 giá trị là tìm được đường cong cần tìm thôi. ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 419 Fp = GF(p) E = EllipticCurve(Fp, (1, 0)) ctr = 0 j = E.j_invariant() while E.j_invariant() != j or ctr == 0: K = E.gen(0) K *= (p+1)//3 phi = E.isogeny(K) E = phi.codomain() ctr += 1 for i in range(ctr-1): K = E.gen(0) K *= (p+1)//3 phi = E.isogeny(K) E = phi.codomain() Ea = E.montgomery_model() Ea ``` * **Note**: Hoặc là dùng code lùi (vì nếu như có quá nhiều lần tiến thì code trên không tối ưu) ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 419 Fp = GF(p) E = EllipticCurve(Fp, (1, 0)) E = E.quadratic_twist() K = E.gen(0) K *= (p+1)//3 phi = E.isogeny(K) E = phi.codomain() E = E.quadratic_twist() print(E.montgomery_model().a2()) ``` * **Flag:261** ### CSIDH Key Exchange * Ờm ý tưởng bài này thì nó không có gì khó khăn cả. Nếu $e$ dương thì tiến $e$ lần ,$e$ âm thì lùi $e$ lần, $e = 0$ thì thôi. $e$ ở đây mình đại diện cho giá trị khóa $private$ của Alice,Bob. * Script: ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 def decrypt_flag(iv,ct,shared_secret): key = SHA256.new(data=str(shared_secret).encode()).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag iv = bytes.fromhex("daf6cd181775664b099609789fb564c9") ct = bytes.fromhex("3dd92e255c8e677f4a92226d09f56e2b2f567052ffd4f6f60200018454a83affc2e694c2bf2ad27da38f7f49b6e89928") # CSIDH-512 prime ells = [3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 587] p = 4 * prod(ells) - 1 F = GF(p) E = EllipticCurve(F, [1, 0]) E0 = E a_priv = [-1, -2, -3, -3, -2, -3, -3, 0, 2, -1, 2, -1, -2, -3, 1, 2, 1, 2, 0, 0, 1, -1, 0, 2, -1, 0, 0, 0, 1, -1, -3, 1, -1, -3, -3, 2, 2, 1, -1, -1, 1, 0, 1, 1, 1, -2, 2, 2, -2, -2, 0, 0, 2, 0, -1, -3, -2, -2, 0, -1, -3, -1, -2, -3, -2, 2, 1, 1, -2, 0, 1, -1, -3, 2] b_priv = [-1, -1, 0, 1, 2, 0, 2, -1, -3, 1, 0, -2, -2, 2, -1, -2, -3, -3, -3, 2, 2, 2, -2, -1, 1, -2, 0, -3, -1, 1, -1, -1, -3, -1, -2, 1, -1, -2, -3, 1, 0, -1, 1, 2, 2, 0, 0, -1, -2, -2, 1, -1, 1, 1, 1, 1, 0, 0, 0, -3, -2, -1, 2, 0, -3, -2, 1, 1, -2, -1, -1, 2, 0, 1] assert len(ells) == len(a_priv) == len(b_priv) def forward(l,e,E): for _ in range(abs(e)): K = E.gen(0) K *= (p+1)//l phi = E.isogeny(K) E = phi.codomain() return E def backward(l, e, E): E = E.quadratic_twist() E = forward(l,e,E) E = E.quadratic_twist() return E def gen(ells,key,E): for i in tqdm(range(len(key))): l = ells[i] e = key[i] if e>0: E = forward(l,e,E) elif e<0: E = backward(l,e,E) return E E_A = gen(ells,a_priv,E) E_B = gen(ells,b_priv,E) share_secret_A = gen(ells,a_priv,E_B).montgomery_model().a2() share_secret_B = gen(ells,b_priv,E_A).montgomery_model().a2() # print(share_secret_A) # print(share_secret_B) assert share_secret_A == share_secret_B flag = decrypt_flag(iv,ct,share_secret_A) print(flag) ``` ![image](https://hackmd.io/_uploads/B1m2TdM0R.png) * **Flag:crypto{post_quantum_NIKE_isogenies_just_do_it}** * **Note:** * Chỉ cần thay đổi nhỏ ở hàm forward là rất nhanh. còn Script cũ là 2 tiếng. ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 def decrypt_flag(iv,ct,shared_secret): key = SHA256.new(data=str(shared_secret).encode()).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag iv = bytes.fromhex("daf6cd181775664b099609789fb564c9") ct = bytes.fromhex("3dd92e255c8e677f4a92226d09f56e2b2f567052ffd4f6f60200018454a83affc2e694c2bf2ad27da38f7f49b6e89928") # CSIDH-512 prime ells = [3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 587] p = 4 * prod(ells) - 1 F = GF(p) E = EllipticCurve(F, [1, 0]) E0 = E a_priv = [-1, -2, -3, -3, -2, -3, -3, 0, 2, -1, 2, -1, -2, -3, 1, 2, 1, 2, 0, 0, 1, -1, 0, 2, -1, 0, 0, 0, 1, -1, -3, 1, -1, -3, -3, 2, 2, 1, -1, -1, 1, 0, 1, 1, 1, -2, 2, 2, -2, -2, 0, 0, 2, 0, -1, -3, -2, -2, 0, -1, -3, -1, -2, -3, -2, 2, 1, 1, -2, 0, 1, -1, -3, 2] b_priv = [-1, -1, 0, 1, 2, 0, 2, -1, -3, 1, 0, -2, -2, 2, -1, -2, -3, -3, -3, 2, 2, 2, -2, -1, 1, -2, 0, -3, -1, 1, -1, -1, -3, -1, -2, 1, -1, -2, -3, 1, 0, -1, 1, 2, 2, 0, 0, -1, -2, -2, 1, -1, 1, 1, 1, 1, 0, 0, 0, -3, -2, -1, 2, 0, -3, -2, 1, 1, -2, -1, -1, 2, 0, 1] assert len(ells) == len(a_priv) == len(b_priv) def forward(l,e,E): for _ in range(abs(e)): G = ((p + 1) // l) * E.random_point() inf = E((0, 1, 0)) while G == inf: G = ((p + 1) // l) * E.random_point() E = isogeny_codomain_from_kernel(E, G) return E def backward(l, e, E): E = E.quadratic_twist() E = forward(l,e,E) E = E.quadratic_twist() return E def gen(ells,key,E): for i in tqdm(range(len(key))): l = ells[i] e = key[i] if e>0: E = forward(l,e,E) elif e<0: E = backward(l,e,E) return E E_A = gen(ells,a_priv,E) E_B = gen(ells,b_priv,E) share_secret_A = gen(ells,a_priv,E_B).montgomery_model().a2() share_secret_B = gen(ells,b_priv,E_A).montgomery_model().a2() # print(share_secret_A) # print(share_secret_B) assert share_secret_A == share_secret_B flag = decrypt_flag(iv,ct,share_secret_A) print(flag) ``` * Mất có vài giây: * ![image](https://hackmd.io/_uploads/BkayAuzC0.png) * Nguyên nhân: Vì dùng **isogeny_codomain_from_kernel(E, G)** thay cho **phi = E.isogeny(K), E = phi.codomain()** sẽ nhanh hơn rất nhiều vì code cũ mình phải tính tất cả các **isogeny** : **phi = E.isogeny(K)**, sau đó mới tìm đường cong đích : **E = phi.codomain()**, còn code mới thì chỉ cần tính từ **kernel** của điểm **G**. ## Isogeny Challenges ### What's My Kernel * Bài này áp dụng 1 chút về phép biến đổi đồng cấu của đẳng cấu như sau: * $𝜑(𝑃+n𝑄)=𝜑(𝑃)+n𝜑(𝑄)$ $<=>𝜑(R)=𝜑(𝑃)+n𝜑(𝑄)$ $<=>O=𝜑(𝑃)+n𝜑(𝑄)$ # điểm vô cực. * Từ đó tìm $n$ bằng **discrete_log** thôi. * Script: ```python from Crypto.Util.number import * from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 l_a, l_b = 2, 3 e_a = 216 e_b = 137 p = (l_a ^ e_a) * (l_b ^ e_b) - 1 F = GF(p ^ 2, name="i", modulus=[1, 0, 1]) E = EllipticCurve(F, [0, 1]) i, = F.gens() E_A = EllipticCurve(F, [(7712764739056777529792987423646889337402217452473623818903331120045456102569165026218420598248767317603435401713968530490357307795*i+9758256230562931813080263636289065758287866439147365597646450723337045772222738857092908727407334585972591094553818411432094464777), (14111394476215446245563948462935499285975180615187777011147664203502283568940589822501200201326292294803458962166963919769448620180*i+13109242201116529741594122295056720779908966260700480865503735602954357430118695171517916549873840990587047235292662060447639572670)]) phi_a_P_b = E_A(23952894958295317549971106723685134737893696601669145219207726439639811367925016451999242069428068943047723238819908252774486766337*i + 4305462457135268462142264590143148158970142912257623729725051176173395849821915511030491264545144391647666649473524326829794956856, 22266818407313663485605006496877581796532115423284800341915760912566163458303144152456856269304745399393537962410019487747495397607*i + 4419726782686001239678340241398527123282988628405899347621803247232804830981975088769950625242092440018396292009058977329258215839) phi_a_Q_b = E_A(831800239128719623934089118985220873854807542447712150093021613769936785750351133960474011564870745205073463311921316788621778431*i + 15402744117044645512001884312795650889329806711970659247428390301404175742876408129656571894858120059888562036870272272006051303602, 15910085843685894625838711841842809245031051484845379580040299498782585224630037907430690257895403864870016080008595192352518845469*i + 11737105265834767072297460524082128066585409511311853470189225057161817943162394756398035248684338249353988301973489992957376234646) n = discrete_log(-phi_a_P_b,phi_a_Q_b,operation='+',ord=2**216) flag = b'crypto{'+ long_to_bytes(n)+ b'}' print(flag) ``` * Giá trị `ord` ở đây được hàm trong sagemath định nghĩa là `order()` * **Flag:crypto{whoops_this_was_just_a_dlog}** ### André Encoding * Ý tưởng bài này thì mình dùng cái này từ [cái này](https://github.com/defeo/MathematicsOfIBC/blob/popayan-temp/poly.pdf)![image](https://hackmd.io/_uploads/ryGjl1vCA.png) * Với **degree** $d$ = ${2^{64}*b}$. Còn lại các giá trị khác mình biết òi. * Từ đó tìm từng kí tự của $flag$, chính là tìm từng giá trị $b.$ * Script: ```py from Crypto.Util.number import * # from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 from output import * import string import json lcm = 8337245403447921335829504375888192675135162254454825924977726845769444687965016467695833282339504042669808000 # Andre's encryption curve p = 37 * 2**64 * lcm - 1 F.<i> = GF((p, 2), modulus=[1,0,1]) E = EllipticCurve(F, [0,1]) E.set_order((p+1)**2) # Generator data P = E(2754452008418475544762931777380298061286322242088097042789979017337032668335152047250270118628626846112409632316814344852346179989*i + 300888031019145372993855450123312195268855753102882163072967372426589237335996165853668912727448513477444811191182550244111536735, 4048396253042221946332182039831591283289177370092736377609511682595711744157657185583897171948127827836521892887941707373829426385*i + 5574313210012278375687658199880462698154719575075630281638825753946911987283962578905520742770486366773314976292767579688991668535, 1) Q = E(1914292834750542008365772941838940247194316211832948370075234167086803005671626788818592170824160266813720050022811399854833864570*i + 675917976944321956275103708696442108160242821688340828072457829850507425923003867371226253254729899087222613984510532839645132037, 1353413969699500553835259943514301405386193613479830974260135363453012387178560466458631824224103775101292443946360403995987929735*i + 2642848780435012471695812611372313188888541702956899224702856112971832879700145426992696527731460150858414996112482220450878252755, 1) def json_to_points(data): p = 37 * 2**64 * lcm - 1 F.<i> = GF((p, 2), modulus=[1,0,1]) E = EllipticCurve(F, [0,1]) E.set_order((p+1)**2) P_x2, P_x1 = data['P']['x'] P_y2, P_y1 = data['P']['y'] Q_x2, Q_x1 = data['Q']['x'] Q_y2, Q_y1 = data['Q']['y'] P_x = P_x1*i+P_x2 P_y = P_y1*i+P_y2 Q_x = Q_x1*i+Q_x2 Q_y = Q_y1*i+Q_y2 A = ((P_y**2 - Q_y**2) - (P_x**3 - Q_x**3)) / (P_x - Q_x) B = P_y**2 - P_x**3 - A*P_x E0 = EllipticCurve(F, [A,B]) phi_P = E0(P_x,P_y) phi_Q = E0(Q_x,Q_y) return phi_P,phi_Q flag = "" for i in tqdm(range(len(data))): phi_P,phi_Q = json_to_points(data[i]) n = p + 1 e_PQ = P.weil_pairing(Q, n) e_phi_PQ = phi_P.weil_pairing(phi_Q, n) for b in range(256): e_fake = e_PQ ** (2**64*b) if e_fake == e_phi_PQ: flag += chr(b) print(flag) ``` * **Flag:crypto{weil_pairings_and_isogenies_are_best_friends}** * Việc tìm lại **degree** $d$ có lẽ là dùng **discrete_log** hoặc phương thức **.log()** được. Nhưng brute từng kí tự trong khoảng $(0,256)$ thì cũng không phải không là 1 cách. * Thực ra lúc đầu mình có hướng làm như `7Rocky`![image](https://hackmd.io/_uploads/SkCejevA0.png) * Ý tưởng là làm như tương tự bài `What's My Kernel` sau 1 chút biến đổi. Nhưng 1 hồi lâu mình không **discrete_log** được nên quit. ### Better than Linear * Lúc đầu bài này mà tính **j-invariant** như những bài trước, tức là thêm **algorithm='factored'** để tính **phi = E.isogeny(K,algorithm='factored')** nhanh hơn, thì vẫn rất lâu. * Dựa vào [resource](https://velusqrt.isogeny.org/velusqrt-20200616.pdf) họ cho kết hợp với [doc này của sagemath](https://doc.sagemath.org/html/en/reference/arithmetic_curves/sage/schemes/elliptic_curves/hom_velusqrt.html), thì mình tìm được algorithm mới. * Script: ```py from Crypto.Util.number import * # from pwn import * from tqdm import tqdm from gmpy2 import * from Crypto.PublicKey import ECC from Crypto.Cipher import AES from Crypto.Hash import SHA256 p = 92935740571 F.<i> = GF(p^2, modulus=[1,0,1]) E = EllipticCurve(F, [1,0]) K = E(11428792286*i+6312697112,78608501229*i+30552079595) phi = E.isogeny(K,algorithm="velusqrt") print(phi.codomain().j_invariant()) ``` * **Flag:48495725269*i + 91493879515** * Hoặc thay đổi nhỏ : ```py from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt p = 92935740571 F.<i> = GF(p^2, modulus=[1,0,1]) E = EllipticCurve(F, [1,0]) K = E(11428792286*i+6312697112,78608501229*i+30552079595) phi = EllipticCurveHom_velusqrt(E, K) print(phi.codomain().j_invariant()) ``` ### Abelian SIDH * Bài này thì mình phân tích qua chút nhé. * **Alice** tạo 1 **kernel** là $R_A = P_A + r_A*Q_A$, với $r_A = randint(0,2^{216})$; $P_A,Q_A$ là 2 điểm **torsion** trong $E$ có **order** là $2^{216}$. Dùng **kernel** $R_A$ tạo 1 **isogeny** $ϕ_A: E→E_A$. * **Bob** tạo 1 **kernel** là $R_B = P_B + r_B*Q_B$, với $r_B = randint(0,3^{137})$; $P_B,Q_B$ là 2 điểm **torsion** trong $E$ có **order** là $3^{137}$. Dùng **kernel** $R_A$ tạo 1 **isogeny** $ϕ_B: E→E_B$. * Bước đầu là thế, lúc này phát sinh thêm 1 khái niệm nữa là [Dual Isogeny](https://www.johannesloher.com/assets/files/the_dual_isogeny.pdf) * Mình đọc [cái này](https://github.com/defeo/MathematicsOfIBC/blob/popayan-temp/poly.pdf) thì tìm thấy thứ cần tìm, nói chung là chỉ cần tập trung vào : * ![image](https://hackmd.io/_uploads/SkkWIVn00.png) * Bởi vì ngoài điểm $G$, mình biết thêm 2 điểm nữa là $G_A = \hat{\phi}_A(ϕ_A(G))$ và $G_B = \hat{\phi}_B(ϕ_B(G))$ * **secret** hay là **key** cần tìm là $\hat{\phi}_A(ϕ_A(G_B)) == \hat{\phi}_B(ϕ_B(G_A))$ * Thì dựa vào lí thuyết ở ảnh trên kia, ta có $\hat{\phi}∘\phi = deg(\phi)$, tức là $\hat{\phi}(ϕ(G))$ là 1 **isogeny**: $G→deg(ϕ)*G$ * Vậy $ss_A = \hat{\phi}_A(ϕ_A(G_B)) = deg_{E_A}*G_B$ và $ss_B = \hat{\phi}_B(ϕ_B(G_A)) = deg_{E_B}*G_A$ * Các thông số $G_A,G_B$ ta đã có hết rồi, và $deg_{E_A} = 2^{216}$, $deg_{E_B} = 3^{137}$ * Hướng đi thi có rồi, nhưng code đầu tiên của mình như sau: ```py from Crypto.Util.number import * from gmpy2 import * import math # from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from hashlib import md5 import json import time from Crypto.Hash import SHA256 import os def decrypt_flag(iv,ct,shared_secret): key = SHA256.new(data=str(shared_secret).encode()).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag q = 66755491218549620204451278063200785887258235588279474221852899550437797658031 l_a = 2 l_b = 3 e_a = 216 e_b = 137 p = (l_a^e_a)*(l_b^e_b)*q - 1 F2.<i> = GF(p^2, name="i", modulus=x^2 + 1) E = EllipticCurve(F2, [1, 0]) assert E.order() == ((l_a^e_a)*(l_b^e_b)*q)^2 G = E(284525705055227305209171433354262326583483353932890396374082376039620293167347284486476175866841636870352172611140798770638607860891148421274491414781769460579269366987499711492224975722383693724998945327430*i + 1505732117301446306244456647291108872440150309218670346804336845746049737306166624890419953190750991745319605357809802251161115415632644954176702934116791663302337496487008464465331505899884564177127436954255, 625472529935654036223921391438157288453365901129930947294363367235040059593079687415091508012127435124029308811603045594581554202553372496400288438224966890629676977438466598167329973052918856464699791832852*i + 47601474380995597168525750969588840445021207583064647146015202571081375996653285276671081590297360068195022075982179758981719629604575131347969613756306189238588058541679309930305808586991507704997154990697) G_A = E(145915727530405194858501908067004149119271866229283785406975016416179519229602788568667141925183452266785838962540142812802147635625676101175514899849797493269264848223662441310624736419729402940497825120237*i + 1533748059308705144310714561393710915956614748853476862053333584077154898976924918286121829477204193114912648083440468146059927048252528425215385159537835867940099628635191907783117733548735865422276573695218, 656719674393860055266665093366417148918321963096698533123174717175987252626915060808605094735026806850329522327826652620966999851633490540865097265418253240684956616735962641073964036624910359405679313489708*i + 490302124124650386804784052586997518905538316252777543910951879620272030423624850466647845982720942016728314763501909461493913896702433702500754925468172028448946982120646755275577481920301078776794371168597) G_B = E(1209705123825944797424706693204961126627572686151810742258347886081638625448638105497078403972632468257771560008199660366766403135521387848010440242607303076906252061506260708306731383701626933759996941173340*i + 537724590651311821177236261188649254487089251825308677486473659020113976583058198202321844403441053376201647207507235530947009033534278463164627138966921647250586671610313221081772734966962022394188211283003, 1367872476957834066472473151204630310870430022944990391495388571666029738678807875104101405110532815608844154365211129918575751133383943176057655115213401058573552182736144971039914106924532010795316022407484*i + 141463228846250275591417528244274312774237222581958487884803845362529881760909336379832032450680371520382692035731593791400586793166353461101345019528851538537956908603557945503241721149516929742657035792134) iv = bytes.fromhex("f81ce520adb3e49a834f711f8dbf5903") ct = bytes.fromhex("2309ddcea639f3acf2503470ad44a33144afed4b8a76c06cca4eb0d01e2aa4a4bb76035f778b179421d59b5449786a994455b3a4638f4d58759be1a515dc950a") ss_A = (l_a ** e_a) * G_B ss_B = (l_b ** e_b) * G_A assert ss_A == ss_B flag = decrypt_flag(iv,ct,ss_A) print(flag) ``` * Thì không ra flag, sau khi xem lại [Abelian_group](https://en.wikipedia.org/wiki/Abelian_group) và trong **Elliptic Curve** có tồn tại khái niệm là điểm đối xứng. Vậy thì có thể việc ánh xạ ngược lại sẽ không phải là **isogeny**: $G→G$ mà còn có thể là $G→-G$ chăng :question: :::spoiler Có lẽ lí do là **isogeny** bảo toàn cấu trúc nhóm `Abel`, và do tính chất đối xứng của đường cong **elliptic**, cả hai điểm **G** và **−G** đều có vai trò tương đương trong nhóm `Abel` . ::: * Script: ```python from Crypto.Util.number import * from gmpy2 import * import math # from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from hashlib import md5 import json import time from Crypto.Hash import SHA256 import os def decrypt_flag(iv,ct,shared_secret): key = SHA256.new(data=str(shared_secret).encode()).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag q = 66755491218549620204451278063200785887258235588279474221852899550437797658031 l_a = 2 l_b = 3 e_a = 216 e_b = 137 p = (l_a^e_a)*(l_b^e_b)*q - 1 F2.<i> = GF(p^2, name="i", modulus=x^2 + 1) E = EllipticCurve(F2, [1, 0]) assert E.order() == ((l_a^e_a)*(l_b^e_b)*q)^2 G = E(284525705055227305209171433354262326583483353932890396374082376039620293167347284486476175866841636870352172611140798770638607860891148421274491414781769460579269366987499711492224975722383693724998945327430*i + 1505732117301446306244456647291108872440150309218670346804336845746049737306166624890419953190750991745319605357809802251161115415632644954176702934116791663302337496487008464465331505899884564177127436954255, 625472529935654036223921391438157288453365901129930947294363367235040059593079687415091508012127435124029308811603045594581554202553372496400288438224966890629676977438466598167329973052918856464699791832852*i + 47601474380995597168525750969588840445021207583064647146015202571081375996653285276671081590297360068195022075982179758981719629604575131347969613756306189238588058541679309930305808586991507704997154990697) G_A = E(145915727530405194858501908067004149119271866229283785406975016416179519229602788568667141925183452266785838962540142812802147635625676101175514899849797493269264848223662441310624736419729402940497825120237*i + 1533748059308705144310714561393710915956614748853476862053333584077154898976924918286121829477204193114912648083440468146059927048252528425215385159537835867940099628635191907783117733548735865422276573695218, 656719674393860055266665093366417148918321963096698533123174717175987252626915060808605094735026806850329522327826652620966999851633490540865097265418253240684956616735962641073964036624910359405679313489708*i + 490302124124650386804784052586997518905538316252777543910951879620272030423624850466647845982720942016728314763501909461493913896702433702500754925468172028448946982120646755275577481920301078776794371168597) G_B = E(1209705123825944797424706693204961126627572686151810742258347886081638625448638105497078403972632468257771560008199660366766403135521387848010440242607303076906252061506260708306731383701626933759996941173340*i + 537724590651311821177236261188649254487089251825308677486473659020113976583058198202321844403441053376201647207507235530947009033534278463164627138966921647250586671610313221081772734966962022394188211283003, 1367872476957834066472473151204630310870430022944990391495388571666029738678807875104101405110532815608844154365211129918575751133383943176057655115213401058573552182736144971039914106924532010795316022407484*i + 141463228846250275591417528244274312774237222581958487884803845362529881760909336379832032450680371520382692035731593791400586793166353461101345019528851538537956908603557945503241721149516929742657035792134) iv = bytes.fromhex("f81ce520adb3e49a834f711f8dbf5903") ct = bytes.fromhex("2309ddcea639f3acf2503470ad44a33144afed4b8a76c06cca4eb0d01e2aa4a4bb76035f778b179421d59b5449786a994455b3a4638f4d58759be1a515dc950a") for t_A in [-1,1]: for t_B in [-1,1]: ss_A = t_A*(l_a ** e_a) * G_B ss_B = t_B*(l_b ** e_b) * G_A try: assert ss_A == ss_B flag = decrypt_flag(iv,ct,ss_A) if b'crypto' in flag: print(flag) except: continue ``` * **Flag:crypto{wait_I_thought_this_was_the_post_quantum_section}** ### Dual Masters * Ở đây đề bài có gợi ý về việc dùng **dual**. * Mình có sử dụng và chỉnh sửa hàm `dual` của chall **Abelian SIDH** để tìm phép ánh xạ đối ngẫu , từ đó có $phi_A$, kết hợp bài đã cho $phi_B$ thì lại **discrete_log** như bài **What's My Kernel**. * Script: ```py from Crypto.Util.number import * from gmpy2 import * import math from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from hashlib import md5 import json import time from Crypto.Hash import SHA256 import os import random import os import Crypto.Util.number as cun # Define Curve params l_a, l_b = 2, 3 e_a = 216 e_b = 137 p = (l_a ^ e_a) * (l_b ^ e_b) - 1 F = GF(p ^ 2, name="i", modulus=[1, 0, 1]) E = EllipticCurve(F, [0, 1]) i = F.gen() assert E.order() == ((l_a**e_a) * (l_b**e_b)) ** 2 # Cofactors c_a = l_b ^ e_b c_b = l_b ^ e_b P_a = E(20136586736114246501835971171967338480311516382940373533952481525296357878861783566151781007236212556832934545674854612011626195700*i + 17814453364935074861383131667083124325683937346696984368949852778058904726511787029450818202901070694186758274352723265991464877103 , 1271771361808985304712159481749593042413672298743174888306246138805367680531766658529560708954342114713164882433446311394561215114*i + 2458042406822607338623223129871544282558479021229618369701845857877994575575856044528094950443550864121494484286802649880921739708 , 1) Q_a = E(21803015129321483945529412215838779132813252821045317544411028404348688068008744729098809691017674062502155948983091074705848044135*i + 13082212877505354759913698066147486474050693412889330975858763468321298287624363965619656101414455426060476491447329452631424163500 , 19506407896382533931237830326416853938397569146136935331864946437347935359679645132170606348699875016125904420682336351889485252410*i + 15043791945562477356073952628550508300788370803355011888730813281302739269433285656956641625470216196554392274696427278619285685068 , 1) E_A = EllipticCurve(F, [(15647464439428927741867845580767694684222523016251895093602328385380654388145388778115672931157828708127948851349755210959710992823*i+18519701628642104957465933782270611113379500730906473687377196168245135786408360125497897711207297387671119962606217672095858390120), (7633645641925656224162547557120979142458288939027826632263622435013191750759424333538814072346087746161472785937079595640620414463*i+19378290131372561524504737884022331022393059506539098406878225963551347653933679233858710469551163367570754633517180433892819440342)]) phi_a_Q_b = E_A(15299431986544116565289603141128936730054296135592165196922848777121350286377485832136840240125657196209757793781083819580249068968*i + 17051219994281676546361123230640448840845340329176128231730176689245736814461968605143814113803812890865243421542880279156556244461 , 3998868285015588876268560462444888175225579707550791385515097131316476642530563104222732513021020953406505731356727753586817062110*i + 19576513237814293888726624230603574617889471255277258991796653332639293951210409486398735758705505503362959553658315915727351406457 , 1) K = phi_a_Q_b phi_hat = E_A.isogeny(K, algorithm="factored") phi_hat = phi_hat.codomain().isomorphism_to(E) * phi_hat phi_hat = phi_hat.dual() phi_a_P_b = phi_hat(P_a) for t_a in [-1,1]: for t_b in [-1,1]: n = discrete_log(t_a*phi_a_P_b,t_b*phi_a_Q_b,operation='+',ord=2**216) flag = b'crypto{'+ long_to_bytes(n)+ b'}' print(flag) ``` * **Flag:crypto{but_I_only_gave_one_point?!}** ### Meet me in the Claw * Source: ```py import os from Crypto.Cipher import AES from Crypto.Hash import SHA256 from Crypto.Util.Padding import pad FLAG = b"crypto{??????????????????????????}" # Base field ea = 35 eb = 29 p = 2**ea * 3**eb - 1 F.<i> = GF(p**2, modulus=[1,0,1]) # Public parameters E0 = EllipticCurve(F, [1, 0]) P2 = E0(1956194174015565770794336*i + 1761758151759977040301838, 2069089015584979134622338*i + 203179590296749797202321) Q2 = E0(2307879706216488835068177*i + 525239361975369850140518, 1834477572646982833868802*i + 733730165545948547648966) P3 = E0(2162781291023757368295120*i + 1542032609308508307064948, 1130418491160933565948899*i + 904285233345649302734471) Q3 = E0(365294178628988623980343*i + 1867216057142335172490873, 2141125983272329025279178*i + 1860108401614981479394873) # Alice's public key EAa = 2336060373130772918448023*i+63223462935813026254900 EAb = 202739861418983960259548*i+525917254309082638166498 EA = EllipticCurve(F, [EAa, EAb]) phiA_P3 = None # Missing!! phiA_Q3 = None # Missing!! # Bob's secret sB = 495832856 def encrypt_flag(shared_secret): key = SHA256.new(data=str(shared_secret).encode()).digest()[:128] iv = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC, iv) ct = cipher.encrypt(pad(FLAG, 16)) return iv.hex(), ct.hex() iv = '9a030e6824e7ec5d66b3443920ea76cb' ct = '7f11a2ca0359cc5f3a81d5039643b1208ac7eb17f8bd42600d1f67e474cd664dcb8624c94175e167acfe856f48be34bd' ``` * Bài này lúc đầu mình bế tắc khi không có 1 hướng gì khả thi, hoặc là dựa vào các bài trước :crying_cat_face: * Lúc đầu mình có hướng là từ $E_0,E_A$, tức là từ **đường cong nguồn, đường cong đích** thì tìm lại phép isogeny giữa nó, hay đại loại là tìm **K** từ `phi = E_0.isogeny(K, algorithm="factored")`. Kiểu kiểu vậy. * Nhưng sau 1 hồi đọc các `doc,paper` mà **cryptohack** cho, kết hợp với cả tìm thêm bên **sagemath** thì mình không tìm được 1 code hay hàm build sẵn nào có thể thực hiện ý tưởng kia của mình. ~~Hoặc do mình gà~~:hand_with_index_and_middle_fingers_crossed: * Ờ thì theo như kinh nghiệm chơi **cryptohack** của mình thì đôi lúc hint nằm ngay ở tên challenge. * Mình thấy cái `Claw` gg dịch nó không ra nghĩa nên mình nghĩ đây là 1 tên cách attack. Tìm thử **claw attack in isogeny** thì mút được [github này](https://github.com/LearningToSQI/SQISign-SageMath/blob/main/mitm.py) có vẻ legit. * Nôm na là code này họ sẽ từ 2 đường cong $E_0,E_A$ trong đẳng cấu $2^k$, (trong bài này là $2^{35}$), sử dụng thuật toán DFS để tính các **j_invariant_path** của cả 2 đường cong, sau đó đối chiếu với nhau , nếu 2 **j_invariant_path** mà khớp ( tạo ra 1 **path** để nối 2 đường cong $E_0,E_A$) thì lúc đó sẽ tìm được phép **isogeny** giữa 2 đường cong. * Code này của họ còn khá tối ưu khi sử dụng [Man in the middle](https://vi.wikipedia.org/wiki/T%E1%BA%A5n_c%C3%B4ng_xen_gi%E1%BB%AFa), để làm giảm thiểu thời gian thực thi. * Clone git về rồi chạy thôi, file **mitm.py** là file mình dùng để tìm phép **isogeny**, có rồi thì thêm các thông số của challenge cho để tính nốt **share_secret** là có :triangular_flag_on_post: * Script: ```python # Sage imports # from sage.all import ( # floor, # PolynomialRing, # factor, # EllipticCurveIsogeny, # EllipticCurve, # ) from sage.all import * from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite def sqrt_Fp2(a): """ Efficiently computes the sqrt of an element in Fp2 using that we always have a prime p such that p ≡ 3 mod 4. """ p = Fp2.characteristic() i = Fp2.gens()[0] # i = √-1 a1 = a ** ((p - 3) // 4) x0 = a1 * a α = a1 * x0 if α == -1: x = i * x0 else: b = (1 + α) ** ((p - 1) // 2) x = b * x0 return x def quadratic_roots(b, c): """ Computes roots to the quadratic polynomial f = x^2 + b * x + c Using the quadratic formula Just like in school! """ d2 = b**2 - 4 * c d = sqrt_Fp2(d2) return ((-b + d) * Fp2_inv_2, -(b + d) * Fp2_inv_2) def generic_modular_polynomial_roots(j1): """ Compute the roots to the Modular polynomial Φ2, setting x to be the input j-invariant. When only one j-invariant is known, we find up to three new j-invariant values. This is fairly slow, but is only done once per graph. """ R = PolynomialRing(j1.parent(), "y") y = R.gens()[0] Φ2 = ( j1**3 - j1**2 * y**2 + 1488 * j1**2 * y - 162000 * j1**2 + 1488 * j1 * y**2 + 40773375 * j1 * y + 8748000000 * j1 + y**3 - 162000 * y**2 + 8748000000 * y - 157464000000000 ) return Φ2.roots(multiplicities=False) def quadratic_modular_polynomial_roots(jc, jp): """ When we have the current node's value as well as the parent node value then we can find the remaining roots by solving a quadratic polynomial following https://ia.cr/2021/1488 """ jc_sqr = jc**2 α = -jc_sqr + 1488 * jc + jp - 162000 β = ( jp**2 - jc_sqr * jp + 1488 * (jc_sqr + jc * jp) + 40773375 * jc - 162000 * jp + 8748000000 ) # Find roots to x^2 + αx + β return quadratic_roots(α, β) def generate_kernels_prime_power(E, l, e): """ Compute a list of points of order exactly l**e which should generate all isogenies of degree l**e """ D = l**e # P, Q = torsion_basis(E, D) P, Q = E.torsion_basis(l**e) # Send points of the form # [x]P + Q K = Q yield K for _ in range(D - 1): K += P K._order = ZZ(D) yield K # Send points of the form # P + [lx]Q K = P yield K lQ = l * Q for _ in range(D // l - 1): K += lQ K._order = ZZ(D) yield K def generate_kernels_division_polynomial(E, l): """ Generate all kernels which generate cyclic isogenies of degree l from the curve E. Kernel generators are found by computing the roots of the l-th division polynomial and lifting these values to points on the elliptic curve. """ f = E.division_polynomial(l) xs = [x for x, _ in f.roots()] for x in xs: K = E.lift_x(x) K._order = ZZ(l) yield K def kernel_from_isogeny_prime_power(ϕ): """ Given a prime-power degree isogeny ϕ computes a generator of its kernel """ E = ϕ.domain() D = ϕ.degree() # Deal with isomorphisms if D == 1: return E(0) # Generate a torsion basis of E[D] # P, Q = torsion_basis(E, D) P, Q = E.torsion_basis(D) # Compute the image of P,Q imP, imQ = ϕ(P), ϕ(Q) # Ensure we can use Q as a base # for the discrete log # TODO: # Here we assume D is a prime power, # this could fail for the general case if not has_order_D(imQ, D): P, Q = Q, P imP, imQ = imQ, imP # Solve the discrete log such that # imP = -[x]imQ. `DLP` uses Weil # pairing to shift the dlog to Fp4. x = DLP(-imP, imQ, D) return P + x * Q def find_j_invs(j1, l, j_prev=None): """ Compute the j-invariants of the elliptic curves 2-isogenous to the elliptic curve with j(E) = j1 The optional param j_prev is used to pass through the parent node in the graph. This is important to stop backtracking, but we can also use it to reduce the degree of the modular polynomial to make root derivation more efficient. """ # TODO: only l=2 supported if l != 2: raise ValueError("Currently, `find_j_invs` is only implemented for l=2") if j_prev: roots = quadratic_modular_polynomial_roots(j1, j_prev) else: roots = generic_modular_polynomial_roots(j1) # Dont include the the previous node to avoid backtracking return [j for j in roots if j != j_prev] def j_invariant_isogeny_graph(j1, l, e, middle_j_vals=None): """ A depth-first search of the isogeny graph. For G1 where we compute the whole graph, dfs or bfs is the same. But when we are looking for a collision in the leaves, we can supply the end values from G1 as middle_j_vals, and stop as soon as we find a collision. This means for a isogeny of degree 2^k, the best case for G2 would be computing only (k/2) nodes rather than the whole graph! Actual graph is a list of dictionaries, with levels as elements of the list and nodes in the dictionary with node : parent_node as data pairs. """ isogeny_graph = [{} for _ in range(e + 1)] # Set the root of the graph isogeny_graph[0][j1] = None # Set stack for DFS stack = [(j1, 0)] while stack: # Get a new element from the stack node, level = stack.pop() # Parent of node (for backtracking) node_parent = isogeny_graph[level][node] # Where we will store the next nodes child_level = level + 1 # Set a bool to see if we should check the middle check_middle = child_level == e and middle_j_vals is not None # Compute all child nodes, except the node which # goes back down the tree node_children = find_j_invs(node, l, j_prev=node_parent) # Compute a dictionary of child : parent nodes for this # level in the tree. for child in node_children: # Add children node to graph isogeny_graph[child_level][child] = node # Return early when DFS finds the middle j_invariant if check_middle and child in middle_j_vals: return isogeny_graph, child # New nodes to search through if child_level != e: stack.append((child, child_level)) return isogeny_graph, None def j_invariant_path(isogeny_graph, j1, j2, e, reversed_path=False): """ Compute a path through a graph with root j1 and last child j2. This is efficient because of our data structure for the graph (simply e look ups in a dictionary). """ # Make sure the end node is where we expect assert j1 in isogeny_graph[0] assert j2 in isogeny_graph[e] j_path = [j2] j = j2 for k in reversed(range(1, e + 1)): j = isogeny_graph[k][j] j_path.append(j) if not reversed_path: j_path.reverse() return j_path def isogeny_from_j_invariant_path(E1, j_invariant_path, l): """ Given a starting curve E1 and a path of j-invariants of elliptic curves l-isogenous to its neighbour, compute an isogeny ϕ with domain E1 and codomain En with j(En) equal to the last element of the path """ # Check we're starting correctly assert E1.j_invariant() == j_invariant_path[0] # We will compute isogenies linking # Ei, Ej step by step ϕ_factors = [] Ei = E1 for j_step in j_invariant_path[1:]: # Compute the isogeny between nodes ϕij = brute_force_isogeny_jinv(Ei, j_step, l, 1) # Store the factor ϕ_factors.append(ϕij) # Update the curve Ei Ei = ϕij.codomain() # Composite isogeny from factors ϕ = EllipticCurveHom_composite.from_factors(ϕ_factors) return ϕ # ======================================== # # Brute force ell isogenies between nodes # # ======================================== # def brute_force_isogeny_jinv(E1, j2, l, e): """ Finds an isogeny of degree l^e, between two curves E1, and an unknown curve with j-invariant j2 TODO: write this to be combined with `BruteForceSearch` so we don't have so much code duplication? """ # Compute the j-invariants of the end # points jE1 = E1.j_invariant() # Degree of isogeny we search for D = l**e # Handle case when the curves are # isomorphic if D == 1: if jE1 == j2: F = E1.base() E2 = EllipticCurve(F, j=j2) return E1.isomorphism_to(E2) else: raise ValueError( f"A degree 1 isogeny cannot be found, as the curves are not isomorphic" ) # Enumerate through kernel generators if e == 1: kernels = generate_kernels_division_polynomial(E1, l) else: kernels = generate_kernels_prime_power(E1, l, e) for K in kernels: ϕ = EllipticCurveIsogeny(E1, K, degree=D, check=False) Eϕ = ϕ.codomain() jEϕ = Eϕ.j_invariant() if jEϕ == j2: return ϕ raise ValueError( f"No degree {D} isogeny found linking the curves E1 and curve with invariant j2" ) def brute_force_isogeny(E1, E2, l, e): """ Finds an isogeny of degree l^e, between two curves E1, and E2 TODO: Currently only implemented for prime power degrees """ # Degree of isogeny we search for D = l**e # Handle case when the curves are # isomorphic if D == 1: if E1.is_isomorphic(E2): return E1.isomorphism_to(E2), E1(0) else: raise ValueError( f"A degree 1 isogeny cannot be found, as the curves are not isomorphic" ) # Enumerate through kernel generators if e == 1: kernels = generate_kernels_division_polynomial(E1, l) else: kernels = generate_kernels_prime_power(E1, l, e) for K in kernels: ϕ = EllipticCurveIsogeny(E1, K, degree=D, check=False) Eϕ = ϕ.codomain() if Eϕ.is_isomorphic(E2): iso = Eϕ.isomorphism_to(E2) ϕ = iso * ϕ return ϕ, K raise ValueError( f"[-] BruteForceSearch failed. No degree {D} isogeny found linking the curves E1 and E2" ) # ============================================ # # Claw-finding attack to recover mitm isogeny # # ============================================ # def claw_finding_attack(E1, E2, l, e): """ Finds a meet in the middle isogeny of degree D, between two curves E1, E2 by searching along two halves of a graph of j invariants """ # We'll search sqrt(l**e) from E1 # and E2 and find a curve in the # middle. # Let e2 >= e1, as dfs approach means # we'll probably not compute all of # the second graph. e1 = floor(e / 2) e2 = e - e1 # Coerce the elements from Fp4 to Fp2 # as Ei are supersingular, this is always # possible. j1 = Fp2(E1.j_invariant()) j2 = Fp2(E2.j_invariant()) # Compute the isogeny graph with root j1 isogeny_graph_e1, _ = j_invariant_isogeny_graph(j1, l, e1) # The top level of the graph are the middle # j-invariants we want to find a collision with e1_middle_vals = set(isogeny_graph_e1[e1].keys()) # Compute the isogeny graph with root j1 # `j_invariant_isogeny_graph` will terminate as soon as # it finds a j invariant with j_middle in e1_middle_vals isogeny_graph_e2, j_middle = j_invariant_isogeny_graph( j2, l, e2, middle_j_vals=e1_middle_vals ) # If we didn't find a value in the middle, then there's # no isogeny, or we have a bug! if not j_middle: raise ValueError(f"No isogeny of degree {factor(l**e)} linking E1 and E2.") # Given the middle j-inv, compute paths from Ei → Middle j1_path = j_invariant_path(isogeny_graph_e1, j1, j_middle, e1) j2_path = j_invariant_path(isogeny_graph_e2, j2, j_middle, e2, reversed_path=True) # Make sure both paths end in the middle assert j1_path[-1] == j2_path[0] # Construct a path from j(E1) to j(E2) j_path = j1_path + j2_path[1:] # Compute the isogeny from the j-inv path ϕ = isogeny_from_j_invariant_path(E1, j_path, l) # Fix the end of the isogeny with an isomorphism E2ϕ = ϕ.codomain() iso = E2ϕ.isomorphism_to(E2) return iso * ϕ def meet_in_the_middle_with_kernel(E1, E2, l, e): """ Wrapper for the Claw finding algorithm. Additionally computes the kernel of the recovered isogeny, which is needed in SQISign for other computations. Edge cases: When e = 0 the curves are isomorphic, so just compute the isomorphism and exit. When e = 1, we can't do a mitm, so just brute force all l-isogenies from one end. """ # Deal with isomorphisms if e == 0: ϕ = E1.isomorphism_to(E2) return ϕ, E1(0) # Mitm doesn't work if there's no middle to meet in if e == 1: ϕ, K = brute_force_isogeny(E1, E2, l, e) return ϕ, K ϕ = claw_finding_attack(E1, E2, l, e) if ϕ == None: raise ValueError( f"[-] ClawFindingAttack failed. No isogeny of degree {factor(l**e)} linking E1 and E2." ) K = kernel_from_isogeny_prime_power(ϕ) return ϕ, K import os from Crypto.Cipher import AES from Crypto.Hash import SHA256 from Crypto.Util.Padding import pad iv = bytes.fromhex('9a030e6824e7ec5d66b3443920ea76cb') ct = bytes.fromhex('7f11a2ca0359cc5f3a81d5039643b1208ac7eb17f8bd42600d1f67e474cd664dcb8624c94175e167acfe856f48be34bd') def decrypt_flag(iv,ct,shared_secret): key = SHA256.new(data=str(shared_secret).encode()).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag ea = 35 eb = 29 p = 2**ea * 3**eb - 1 Fp2 = GF(p**2, modulus=[1,0,1], name = 'i') Fp2_inv_2 = Fp2(1) / 2 (i,) = Fp2.gens() E0 = EllipticCurve(Fp2, [1, 0]) P2 = E0(1956194174015565770794336*i + 1761758151759977040301838, 2069089015584979134622338*i + 203179590296749797202321) Q2 = E0(2307879706216488835068177*i + 525239361975369850140518, 1834477572646982833868802*i + 733730165545948547648966) P3 = E0(2162781291023757368295120*i + 1542032609308508307064948, 1130418491160933565948899*i + 904285233345649302734471) Q3 = E0(365294178628988623980343*i + 1867216057142335172490873, 2141125983272329025279178*i + 1860108401614981479394873) EAa = 2336060373130772918448023*i+63223462935813026254900 EAb = 202739861418983960259548*i+525917254309082638166498 EA = EllipticCurve(Fp2, [EAa, EAb]) sB = 495832856 phi = claw_finding_attack(E0, EA, 2, ea) print(phi) print(E0) print(EA) phiA_P3 = phi(P3) phiA_Q3 = phi(Q3) K = phiA_P3 + sB * phiA_Q3 E = EA.isogeny(K, algorithm="factored").codomain() shared_secret = E.j_invariant() flag = decrypt_flag(iv,ct,shared_secret) print(flag) ``` * **Flag:crypto{clawing_our_way_to_victory}** * Mình sẽ không nói là mình mất cả nửa ngày để run code này lấy flag chỉ vì hàm `decrypt_flag` của mình vẫn để là ~~`flag = cipher.encrypt(ct)`~~. :baby_chick: ### A True Genus * Đọc qua source code của bài , thì **key** đem đi mã hóa **AES** cho **flag** chuyển sang bin, sau đó check nếu: * bit = 1 : trả về 3 giá trị $E_A = [a]*E_0,E_B = [b]*E_0,E_{AB} = [a]*[b]*E_0$ * bit = 0 : trả về 3 giá trị $E_A = [a]*E_0,E_B = [b]*E_0,E_C = [c]*E_0$ với các giá trị $[a],[b],[c]$ là private . * Thoạt đầu thì đọc source code xong thấy việc phần biệt 2 đường cong $[a]*[b]*E_0$ và $[c]*E_0$ rất khó, khi những gì đề bài cho rất mờ nhạt, với hàm **action()** thì random tùm lum, hàm **backdoor()** thì không biết giá trị **secret_marks**. * Cũng giống như bài **Meet me in the Claw**, có lẽ hint nằm ngay ở tiêu đề thử thách. Nên sau khi tìm kiếm vài thứ đại loại liên quan tới từ **Genus** thì mình kiếm được [cái này](https://eprint.iacr.org/2020/151.pdf) . * Chính xác thứ chúng ta cần đọc bắt đầu từ mục **Definition 4 (DDH-CGA)** * ![image](https://hackmd.io/_uploads/BkpiiLwyke.png) * Ở đây người ta hướng dẫn mình cần tìm 1 phép $χ$ sao cho $χ(E_0,E_A) = χ(E_B,E_{AB})$ hoặc $χ(E_0,E_B) = χ(E_A,E_{AB})$, đồng nghĩa với việc không tồn tại 1 phép $χ$ nào để $χ(E_0,E_A) = χ(E_B,E_{C})$. * Đến đây có thể tự code hay sao đó cũng được nhưng **paper** có đưa 1 [github](https://github.com/KULeuven-COSIC/group_action_DDH/blob/main/elliptic_DDH.m) viết bằng **Magma** cách triển khai hàm này nên mình mút luôn. * ![image](https://hackmd.io/_uploads/HyMqaLDJkx.png) * Script: ```py from Crypto.Util.number import * from gmpy2 import * import math # from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from hashlib import md5 import json import time from Crypto.Hash import SHA256 import os import random import os import Crypto.Util.number as cun from hashlib import sha512 from sympy.ntheory.residue_ntheory import discrete_log from output import data def decrypt_flag(iv,ct,secret): key = SHA256.new(int.to_bytes(int(secret), 8,'big')).digest()[:128] cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(ct) return flag def compute_supersingular_delta(E_0, E_test): Fp.<x> = PolynomialRing(E_0.base_field()) a = E_0.a4() r = (x^3 + a * x + E_0.a6()).roots()[0][0] riso = (x^3 + E_test.a4() * x + E_test.a6()).roots()[0][0] char = ((E_test.a4() + 3*riso^2)/(a + 3*r^2))^((p - 1) / 4) return 1 if char == 1 else -1 ls = list(primes(3, 112)) + [139] p = 2 * prod(ls) - 1 max_exp = ceil((sqrt(p) ** (1 / len(ls)) - 1) / 2) Fp2 = GF(p**2, names="w", modulus=[3, 0, 1]) w = Fp2.gen() base = EllipticCurve(Fp2, [0, 1]) iv = bytes.fromhex(data['iv']) ct = bytes.fromhex(data['ct']) challenge_data = data['challenge_data'] key = "" for i in tqdm(range(len(challenge_data))): EA = challenge_data[i]['EA'] a4_EA,a6_EA = EA['a4'][0],EA['a6'][0] EA = EllipticCurve(Fp2, [a4_EA, a6_EA]) EB = challenge_data[i]['EB'] a4_EB,a6_EB = EB['a4'][0],EB['a6'][0] EB = EllipticCurve(Fp2, [a4_EB, a6_EB]) EC = challenge_data[i]['EC'] a4_EC,a6_EC = EC['a4'][0],EC['a6'][0] EC = EllipticCurve(Fp2, [a4_EC, a6_EC]) if compute_supersingular_delta(base,EA) == compute_supersingular_delta(EB,EC): key += "1" else: key += "0" secret = int(key[::-1],2) flag = decrypt_flag(iv,ct,secret) print(flag) ``` * **Flag:crypto{Gauss_knew_how_to_break_CSIDH???}** :::info * **Note** : Giải thích vì sao đoạn này là `secret = int(key[::-1],2)`, tức là phải đảo ngược **key** * Lúc đầu mình cũng không phát hiện ra nhưng có lẽ do **for** đoạn này nó lmao:![image](https://hackmd.io/_uploads/B1oJY_DJ1x.png) * Ví dụ nhé : ![image](https://hackmd.io/_uploads/S1Z-Ydw1kg.png) :::