[πŸ₯ˆ Silver 1] μ†Œμˆ˜ p 의 ν•˜μœ„ 280λΉ„νŠΈλ§Œ μ§€μ›Œμ‘Œλ‹€ β€” DreamHack tiny roots 풀이

2026-08-19Β·1λΆ„ 읽기·

[πŸ₯ˆ Silver 1] μ†Œμˆ˜ p 의 ν•˜μœ„ 280λΉ„νŠΈλ§Œ μ§€μ›Œμ‘Œλ‹€ β€” DreamHack tiny roots 풀이

RSA μ†Œμˆ˜ p 의 μƒμœ„ 744λΉ„νŠΈκ°€ κ·ΈλŒ€λ‘œ λ…ΈμΆœλœ 문제. Coppersmith 의 known-MSB μΈμˆ˜λΆ„ν•΄λ₯Ό 3x3 격자둜 직접 μ§œμ„œ ν•˜μœ„ 280λΉ„νŠΈλ₯Ό 되찾고, 순수 파이썬 LLL 만으둜 0.4초 λ§Œμ— n 을 μͺΌκ° λ‹€. SageMath small_roots 와 openssl 둜 κ΅μ°¨κ²€μ¦κΉŒμ§€.

문제: DreamHack β€” tiny roots λΆ„λ₯˜: Crypto λ‚œμ΄λ„: πŸ₯ˆ Silver 1 FLAG: DH{yeaaaahhh!!_C00p3rsM1th_1s_G0d_><}

문제 μ„€λͺ…은 이렇닀. λ“œλ¦Όμ΄κ°€ RSA ν‚€λ₯Ό λ§Œλ“€λ‹€ 컀피λ₯Ό μŸμ•„ 정보가 μ§€μ›Œμ‘Œκ³ , μƒˆλ‘œ λ§Œλ“€λ €λ˜ 쀑에 ν‚€ 일뢀가 λ…ΈμΆœλœ κ±Έ λ°œκ²¬ν–ˆλ‹€λŠ” 것. 컀피λ₯Ό μŸμ•˜λ‹€λŠ” 게 κ³§ "λΉ„νŠΈκ°€ μ§€μ›Œμ‘Œλ‹€"λŠ” 뜻이고, κ·Έ μ§€μ›Œμ§„ μžλ¦¬κ°€ μ •ν™•νžˆ μ–΄λ””κΉŒμ§€μΈμ§€κ°€ 이 문제의 μ „λΆ€λ‹€.

DreamHack tiny roots 문제 νŽ˜μ΄μ§€ β€” 크립토 싀버 1, 컀피λ₯Ό μŸμ•„ RSA ν‚€ 정보가 μ§€μ›Œμ‘Œλ‹€λŠ” μ„€λͺ…κ³Ό 문제 파일 λ°›κΈ° λ²„νŠΌμ΄ 보인닀
DreamHack tiny roots 문제 νŽ˜μ΄μ§€ β€” 크립토 싀버 1, 컀피λ₯Ό μŸμ•„ RSA ν‚€ 정보가 μ§€μ›Œμ‘Œλ‹€λŠ” μ„€λͺ…κ³Ό 문제 파일 λ°›κΈ° λ²„νŠΌμ΄ 보인닀


문제 κ°œμš”

ν•­λͺ©λ‚΄μš©
문제λͺ…tiny roots
λ‚œμ΄λ„πŸ₯ˆ Silver 1
λΆ„λ₯˜Crypto
제곡 파일tiny roots.py (생성 슀크립트), output.txt (좜λ ₯)
μ„œλ²„μ—†μŒ β€” μ˜€ν”„λΌμΈ 문제
μŠ€νƒPython 3 + PyCryptodome, RSA-2048
핡심 기법Coppersmith known-MSB μΈμˆ˜λΆ„ν•΄ (May) + Howgrave-Graham 격자 + LLL

풀이 흐름은 μ§§λ‹€. p 의 μƒμœ„ 744λΉ„νŠΈκ°€ κ·ΈλŒ€λ‘œ 곡개돼 μžˆμœΌλ‹ˆ λ‚˜λ¨Έμ§€ 280λΉ„νŠΈλ₯Ό λ―Έμ§€μˆ˜ x 둜 두고 f(x) = MSB + x 의 μž‘μ€ 근을 격자둜 μ°ΎλŠ”λ‹€. p κ°€ λ‚˜μ˜€λ©΄ κ·Έλ‹€μŒμ€ κ΅κ³Όμ„œ RSAλ‹€.



πŸ”¬ 배포본 μ •μ°°

νŒŒμΌμ€ λ”± 두 κ°œλ‹€. 생성 μŠ€ν¬λ¦½νŠΈμ™€ κ·Έ 좜λ ₯.

ls -l extracted && file extracted/* && head -18 "extracted/tiny roots.py"

ls 와 file 둜 ν™•μΈν•œ 배포본 β€” output.txt 1712λ°”μ΄νŠΈμ™€ tiny roots.py 591λ°”μ΄νŠΈλΏμ΄κ³ , μ†ŒμŠ€ μ•žλΆ€λΆ„μ— 1024λΉ„νŠΈ μ†Œμˆ˜ 두 κ°œμ™€ 128λΉ„νŠΈ μ§€μˆ˜ e λ₯Ό λ§Œλ“œλŠ” μ½”λ“œκ°€ 보인닀
ls 와 file 둜 ν™•μΈν•œ 배포본 β€” output.txt 1712λ°”μ΄νŠΈμ™€ tiny roots.py 591λ°”μ΄νŠΈλΏμ΄κ³ , μ†ŒμŠ€ μ•žλΆ€λΆ„μ— 1024λΉ„νŠΈ μ†Œμˆ˜ 두 κ°œμ™€ 128λΉ„νŠΈ μ§€μˆ˜ e λ₯Ό λ§Œλ“œλŠ” μ½”λ“œκ°€ 보인닀

슀크립트 전문은 이게 λ‹€λ‹€.

from Crypto.Util.number import *
 
FLAG = open('../../flag.txt','rb').read()
FLAG = bytes_to_long(FLAG)
 
p = getPrime(1024)
q = getPrime(1024)
assert p > q
 
n = p * q
e = getPrime(128)
 
beta = 0.4
epsilon = beta ** 2 / 7
 
upper_bound = p.bit_length()
lower_bound = int(n.bit_length() * (beta ** 2 - epsilon))
 
MSB = p & (pow(2,upper_bound) - pow(2,lower_bound))
 
# encryption
ciphertext = pow(FLAG,e,n)
 
print(f'Here is the flag : {ciphertext}\n')
print(f'I don\'t know the secret key... except some bits of p...\n{MSB}\n')
print(f'But I know the public key !! Can You make it??\n({n},{e})')

e κ°€ 128λΉ„νŠΈ μ†Œμˆ˜λΌ μž‘μ€ μ§€μˆ˜ 곡격은 μ²˜μŒλΆ€ν„° λ§‰ν˜€ μžˆλ‹€. μ†Œμˆ˜ 두 κ°œλ„ λ…λ¦½μ μœΌλ‘œ λ½‘μ•˜μœΌλ‹ˆ κ·Όμ ‘ μ†Œμˆ˜ 곡격도 μ•ˆ λœλ‹€. λ‚¨λŠ” 건 MSB ν•œ 쀄뿐이닀.

MSB = p & (2^upper_bound - 2^lower_bound) β€” μ—¬κΈ°μ„œ upper_bound λŠ” p.bit_length() λ‹ˆκΉŒ 1024κ³ , lower_bound λŠ” n 의 λΉ„νŠΈμˆ˜ 2048에 beta^2 - epsilon 을 κ³±ν•œ 값이닀. λ§ˆμŠ€ν¬κ°€ λͺ‡ λΉ„νŠΈλ₯Ό 살리고 λͺ‡ λΉ„νŠΈλ₯Ό μ§€μš°λŠ”μ§€λŠ” 눈으둜 μ„Έμ§€ 말고 직접 κ³„μ‚°ν•˜λŠ” 게 λΉ λ₯΄λ‹€.

#!/usr/bin/env python3
"""output.txt μ—μ„œ νŒŒλΌλ―Έν„°λ₯Ό 뽑고, 문제 μ½”λ“œμ˜ λ§ˆμŠ€ν¬κ°€ μ‹€μ œλ‘œ λͺ‡ λΉ„νŠΈλ₯Ό μ§€μ› λŠ”μ§€ μ„Όλ‹€."""
import re
 
txt = open('extracted/output.txt').read()
c = int(re.search(r'Here is the flag : (\d+)', txt).group(1))
msb = int(re.search(r'bits of p\.\.\.\s*\n(\d+)', txt).group(1))
n, e = (int(v) for v in re.search(r'\((\d+),(\d+)\)', txt).groups())
 
beta = 0.4
epsilon = beta ** 2 / 7
upper_bound = 1024                       # p.bit_length()
lower_bound = int(n.bit_length() * (beta ** 2 - epsilon))
mask = pow(2, upper_bound) - pow(2, lower_bound)
 
print(f'n           : {n.bit_length()}λΉ„νŠΈ')
print(f'e           : {e.bit_length()}λΉ„νŠΈ  = {e}')
print(f'ciphertext  : {c.bit_length()}λΉ„νŠΈ')
print(f'MSB         : {msb.bit_length()}λΉ„νŠΈ')
print()
print(f'beta        = {beta}')
print(f'epsilon     = beta^2/7 = {epsilon:.9f}')
print(f'lower_bound = int({n.bit_length()} * ({beta**2:.2f} - {epsilon:.6f})) '
      f'= int({n.bit_length()*(beta**2-epsilon):.4f}) = {lower_bound}')
print()
print(f'mask        = 2^{upper_bound} - 2^{lower_bound}')
print(f'mask μ•ˆμ—μ„œ 1 인 λΉ„νŠΈ = {bin(mask).count("1")}개  (λΉ„νŠΈ {lower_bound}..{upper_bound-1})')
print(f'-> p 의 μƒμœ„ {upper_bound-lower_bound}λΉ„νŠΈλ₯Ό μ•Œκ³ , ν•˜μœ„ {lower_bound}λΉ„νŠΈλ₯Ό λͺ¨λ₯Έλ‹€')
print()
print(f'MSB 의 λ’€μͺ½ 0 λΉ„νŠΈ = {(msb & -msb).bit_length()-1}개  '
      f'(ν•˜μœ„ {lower_bound}λΉ„νŠΈκ°€ μ§€μ›Œμ‘Œκ³ , 마침 κ·Έ μœ„ 6λΉ„νŠΈλ„ 0μ΄μ—ˆλ‹€)')
print(f'MSB % 2^{lower_bound} == 0 ?  {msb % (1 << lower_bound) == 0}')
print()
print(f'λ―Έμ§€μˆ˜ 후보 = 2^{lower_bound} = {1 << lower_bound}')
print(f'μ΄ˆλ‹Ή 10^9 회 검사해도 {(1<<lower_bound)/1e9/3.15e7:.3e} λ…„ -> μ „μˆ˜λŠ” λΆˆκ°€λŠ₯')
python3 recon.py

recon.py μ‹€ν–‰ κ²°κ³Ό β€” n 2048λΉ„νŠΈ, e 128λΉ„νŠΈ, lower_bound κ°€ int(2048 x 0.137142) = 280 으둜 ν™•μ •λ˜κ³  λ§ˆμŠ€ν¬κ°€ p 의 μƒμœ„ 744λΉ„νŠΈλ§Œ λ‚¨κΈ΄λ‹€λŠ” 것이 숫자둜 ν™•μΈλœλ‹€
recon.py μ‹€ν–‰ κ²°κ³Ό β€” n 2048λΉ„νŠΈ, e 128λΉ„νŠΈ, lower_bound κ°€ int(2048 x 0.137142) = 280 으둜 ν™•μ •λ˜κ³  λ§ˆμŠ€ν¬κ°€ p 의 μƒμœ„ 744λΉ„νŠΈλ§Œ λ‚¨κΈ΄λ‹€λŠ” 것이 숫자둜 ν™•μΈλœλ‹€

μˆ«μžκ°€ λ‹€ λ‚˜μ™”λ‹€. lower_bound = 280, 즉 p 의 ν•˜μœ„ 280λΉ„νŠΈκ°€ μ§€μ›Œμ§€κ³  μƒμœ„ 744λΉ„νŠΈκ°€ κ·ΈλŒ€λ‘œ κ³΅κ°œλλ‹€.

MSB 의 λ’€μͺ½ 0λΉ„νŠΈκ°€ 286개둜 λ‚˜μ˜€λŠ” 게 잠깐 λˆˆμ— κ±Έλ¦¬λŠ”λ°, 이건 μ§€μ›Œμ§„ 280λΉ„νŠΈ μœ„μͺ½ 6λΉ„νŠΈκ°€ μš°μ—°νžˆ 0μ΄μ—ˆμ„ 뿐이닀. λ§ˆμŠ€ν¬κ°€ μ§€μš΄ 건 μ •ν™•νžˆ 280λΉ„νŠΈλ‹€.

p 의 λΉ„νŠΈ 배치 β€” μƒμœ„ 744λΉ„νŠΈλŠ” MSB 둜 곡개되고 ν•˜μœ„ 280λΉ„νŠΈλ§Œ λ―Έμ§€μˆ˜ x 이며, μ „μˆ˜ 탐색은 6.2x10^67 λ…„μ΄μ§€λ§Œ Coppersmith 이둠 ν•œκ³„μΈ 512λΉ„νŠΈμ—λŠ” 232λΉ„νŠΈ μ—¬μœ κ°€ μžˆλ‹€
p 의 λΉ„νŠΈ 배치 β€” μƒμœ„ 744λΉ„νŠΈλŠ” MSB 둜 곡개되고 ν•˜μœ„ 280λΉ„νŠΈλ§Œ λ―Έμ§€μˆ˜ x 이며, μ „μˆ˜ 탐색은 6.2x10^67 λ…„μ΄μ§€λ§Œ Coppersmith 이둠 ν•œκ³„μΈ 512λΉ„νŠΈμ—λŠ” 232λΉ„νŠΈ μ—¬μœ κ°€ μžˆλ‹€

μ •λ¦¬ν•˜λ©΄ 이렇닀.

p = MSB + x 이고 λ―Έμ§€μˆ˜ λ²”μœ„λŠ” 0 <= x < 2^280 이닀.

2^280 은 λŒ€λž΅ 1.9x10^84 λ‹€. μ „μˆ˜λŠ” λ…Όμ™Έλ‹€.


🧩 λ°°κ²½ β€” 인수의 μƒμœ„ λΉ„νŠΈλ₯Ό μ•Œλ©΄ λš«λ¦°λ‹€

Coppersmith λŠ” 1996년에 "λͺ¨λ“ˆλŸ¬ λ‹€ν•­μ‹μ˜ μž‘μ€ 근은 λ‹€ν•­μ‹œκ°„μ— 찾을 수 μžˆλ‹€"λŠ” κ²°κ³Όλ₯Ό λƒˆκ³ , May κ°€ κ·Έκ±Έ μΈμˆ˜λΆ„ν•΄ μͺ½μœΌλ‘œ μ •λ¦¬ν–ˆλ‹€. μš°λ¦¬κ°€ μ“Έ ν˜•νƒœλŠ” 이거닀.

N 의 λ―Έμ§€ 인수 p >= N^beta 와 차수 delta 인 monic 닀항식 f(x) 에 λŒ€ν•΄, f(x0) κ°€ mod p μ—μ„œ 0이 λ˜λŠ” x0 κ°€ |x0| < N^(beta^2/delta) λ₯Ό λ§Œμ‘±ν•˜λ©΄ κ·Έ x0 λŠ” N 만 μ•Œκ³ λ„ λ‹€ν•­μ‹œκ°„μ— 찾을 수 μžˆλ‹€.

μ—¬κΈ° λŒ€μž…ν•˜λ©΄ f(x) = MSB + x, 차수 1, f(x0) = p μ΄λ―€λ‘œ mod p μ—μ„œ 0이닀. p 와 q κ°€ λ‘˜ λ‹€ 1024λΉ„νŠΈλΌ p λŠ” λŒ€λž΅ N^0.5 이고, 그러면 감당 κ°€λŠ₯ν•œ λ―Έμ§€μˆ˜λŠ” N^0.25 = 512λΉ„νŠΈλ‹€. μ§€μ›Œμ§„ 건 280λΉ„νŠΈλΏμ΄λΌ 232λΉ„νŠΈλ‚˜ λ‚¨λŠ”λ‹€. 문제 이름 tiny roots λŠ” 이 "μž‘μ€ κ·Ό"이자 SageMath 의 small_roots λ₯Ό 가리킨닀.

핡심 λ„κ΅¬λŠ” Howgrave-Graham 의 보쑰정리닀. mod p μ—μ„œ 근을 κ°–λŠ” μ •μˆ˜ λ‹€ν•­μ‹μ˜ κ³„μˆ˜κ°€ μΆ©λΆ„νžˆ μž‘μœΌλ©΄, κ·Έ κ·Όμ—μ„œ μ •μˆ˜ μœ„μ—μ„œλ„ 값이 0이 λœλ‹€λŠ” 것. κ·Έλž˜μ„œ mod p 둜 0이 λ˜λŠ” 닀항식듀을 μž”λœ© λ§Œλ“€μ–΄ 놓고, κ·Έ μ •μˆ˜ κ²°ν•© 쀑 κ³„μˆ˜κ°€ μž‘μ€ 것을 찾으면 λœλ‹€. κ·Έ "κ³„μˆ˜κ°€ μž‘μ€ κ²°ν•© μ°ΎκΈ°"κ°€ κ³§ 격자 μ΅œλ‹¨λ²‘ν„° 문제고, LLL 이 κ·Έκ±Έ κ·Όμ‚¬λ‘œ ν’€μ–΄ μ€€λ‹€.

Wikimedia Commons 의 격자 κΈ°μ € μΆ•μ•½ κ·Έλ¦Ό β€” κΈΈκ³  거의 ν‰ν–‰ν•œ κΈ°μ € v1, v2 λ₯Ό 같은 격자λ₯Ό μƒμ„±ν•˜λŠ” μ§§κ³  직ꡐ에 κ°€κΉŒμš΄ u1, u2 둜 λ°”κΎΈλŠ” 것이 LLL 이 ν•˜λŠ” 일이닀
Wikimedia Commons 의 격자 κΈ°μ € μΆ•μ•½ κ·Έλ¦Ό β€” κΈΈκ³  거의 ν‰ν–‰ν•œ κΈ°μ € v1, v2 λ₯Ό 같은 격자λ₯Ό μƒμ„±ν•˜λŠ” μ§§κ³  직ꡐ에 κ°€κΉŒμš΄ u1, u2 둜 λ°”κΎΈλŠ” 것이 LLL 이 ν•˜λŠ” 일이닀

좜처: Wikimedia Commons, public domain

그림의 v1, v2 처럼 κΈΈκ³  거의 ν‰ν–‰ν•œ κΈ°μ €λ₯Ό u1, u2 처럼 μ§§κ³  직ꡐ에 κ°€κΉκ²Œ λ°”κΎΈλŠ” 것이 LLL 이닀. 같은 격자λ₯Ό μƒμ„±ν•˜λ˜ λ²‘ν„°λ§Œ μ§§μ•„μ§„λ‹€. μš°λ¦¬κ°€ μ›ν•˜λŠ” "μž‘μ€ κ³„μˆ˜ 닀항식"이 μ •ν™•νžˆ κ·Έ 짧은 벑터닀.


πŸ’£ 핡심 β€” 3Γ—3 격자면 μΆ©λΆ„ν•˜λ‹€

m = 1, t = 1 둜 작으면 mod p μ—μ„œ 0이 λ˜λŠ” 닀항식이 μ…‹ λ‚˜μ˜¨λ‹€.

닀항식mod p μ—μ„œ 0인 이유
g0(x) = np κ°€ n 을 λ‚˜λˆˆλ‹€
g1(x) = f(x)f(x0) = p
h1(x) = xΒ·f(x)μœ„μ— x λ₯Ό κ³±ν–ˆμ„ 뿐

이 셋을 x β†’ XΒ·x 둜 μΉ˜ν™˜ν•΄ κ³„μˆ˜ λ²‘ν„°λ‘œ λŠ˜μ–΄λ†“μœΌλ©΄ 삼각행렬이 λœλ‹€. λŒ€κ° 성뢄은 n, X, X^2 이고 행렬식은 nΒ·X^3 이닀.

Howgrave-Graham 쑰건은 LLL μ΅œλ‹¨λ²‘ν„°μ˜ 노름이 p^m / √dim 보닀 μž‘μ•„μ•Ό ν•œλ‹€λŠ” 것이고, LLL 은 μ΅œλ‹¨λ²‘ν„° 노름이 2^((d-1)/4) * det^(1/d) μ΄ν•˜μž„μ„ 보μž₯ν•œλ‹€. 숫자λ₯Ό λ„£μœΌλ©΄

log2(det) = 2048 + 3 x 280 = 2888 이고, 이걸 차원 3으둜 λ‚˜λˆ„λ©΄ 962.7 이닀.

μ—¬μœ κ°€ 2^60 이상 λ‚¨λŠ”λ‹€. μ΄λ‘ κ°’λ§Œ 믿을 게 μ•„λ‹ˆλΌ (m, t) λ₯Ό ν›‘μ–΄ μ–΄λ””λΆ€ν„° 쑰건이 μ„œκ³  μ‹€μ œλ‘œ μ–Όλ§ˆλ‚˜ κ±Έλ¦¬λŠ”μ§€ 재 λ΄€λ‹€.

#!/usr/bin/env python3
"""격자 크기 (m, t) λ₯Ό λ°”κΏ” κ°€λ©° Howgrave-Graham 쑰건이 μ„±λ¦½ν•˜λŠ”μ§€ κ³„μ‚°ν•˜κ³ ,
μž‘μ€ κ²©μžλŠ” μ‹€μ œλ‘œ LLL 을 돌렀 κ±Έλ¦° μ‹œκ°„κ³Ό μ΅œλ‹¨λ²‘ν„° 길이λ₯Ό μž°λ‹€.
 
  이둠:  2^((dim-1)/4) * det^(1/dim) < p^m / sqrt(dim)
  μ—¬κΈ°μ„œ det = X^(m(m+1)/2 + t*m + t(t+1)/2) * n^(m(m+1)/2)
"""
import math
import re
import sys
import time
 
from lll import lll
 
txt = open('extracted/output.txt').read()
msb = int(re.search(r'bits of p\.\.\.\s*\n(\d+)', txt).group(1))
n = int(re.search(r'\((\d+),(\d+)\)', txt).group(1))
LOGX, LOGN, LOGP = 280, 2048, 1024        # X=2^280, n 2048λΉ„νŠΈ, p 1024λΉ„νŠΈ
 
 
def build(m, t):
    dim = m + t + 1
    fp = [[1]]
    for _ in range(m):
        a, b = fp[-1], [msb, 1]
        out = [0] * (len(a) + len(b) - 1)
        for i, x in enumerate(a):
            for j, y in enumerate(b):
                out[i + j] += x * y
        fp.append(out)
    polys = [[c * pow(n, m - i) for c in fp[i]] for i in range(m + 1)]
    polys += [[0] * j + fp[m] for j in range(1, t + 1)]
    X = 1 << LOGX
    return [[polys[r][k] * pow(X, k) if k < len(polys[r]) else 0
             for k in range(dim)] for r in range(dim)]
 
 
print(f'{"m":>2} {"t":>2} {"dim":>4} {"log2(det)":>10} {"LLL μƒν•œ":>9} '
      f'{"ν•„μš” p^m":>9}  νŒμ •   μ‹€μΈ‘')
print('-' * 68)
for m in range(1, 5):
    for t in range(0, 4):
        dim = m + t + 1
        logdet = LOGX * (m * (m + 1) // 2 + t * m + t * (t + 1) // 2) + LOGN * (m * (m + 1) // 2)
        lhs = logdet / dim + (dim - 1) / 4 + 0.5 * math.log2(dim)
        rhs = LOGP * m
        ok = '톡과' if lhs < rhs else '미달'
        measured = ''
        if dim <= 4 and lhs < rhs:
            st = time.time()
            R = lll(build(m, t))
            el = time.time() - st
            norm2 = sum(c * c for c in R[0])
            measured = f'{el:6.2f}s  ||v||=2^{(norm2.bit_length()-1)/2:.0f}'
        print(f'{m:>2} {t:>2} {dim:>4} {logdet:>10} {lhs:>9.1f} {rhs:>9} '
              f'  {ok}   {measured}')
        sys.stdout.flush()
python3 lattice_scan.py

lattice_scan.py μ‹€ν–‰ κ²°κ³Ό β€” m=1 t=0 인 2차원 κ²©μžλŠ” 1164.8 이 ν•„μš”μΉ˜ 1024 λ₯Ό λ„˜κ²¨ 미달이고, m=1 t=1 인 3차원뢀터 ν†΅κ³Όν•˜λ©° μ‹€μΈ‘ 0.36μ΄ˆμ— μ΅œλ‹¨λ²‘ν„° 노름이 2^931 둜 λ‚˜μ˜¨λ‹€
lattice_scan.py μ‹€ν–‰ κ²°κ³Ό β€” m=1 t=0 인 2차원 κ²©μžλŠ” 1164.8 이 ν•„μš”μΉ˜ 1024 λ₯Ό λ„˜κ²¨ 미달이고, m=1 t=1 인 3차원뢀터 ν†΅κ³Όν•˜λ©° μ‹€μΈ‘ 0.36μ΄ˆμ— μ΅œλ‹¨λ²‘ν„° 노름이 2^931 둜 λ‚˜μ˜¨λ‹€

ν‘œμ—μ„œ μ½νžˆλŠ” 게 두 κ°€μ§€λ‹€.

첫째, t = 0 인 쀄은 m 이 아무리 컀도 μ „λΆ€ 미달이닀. x λ₯Ό κ³±ν•œ 이동 닀항식이 ν•˜λ‚˜λŠ” μžˆμ–΄μ•Ό 쑰건이 μ„ λ‹€.

λ‘˜μ§Έ, 쑰건이 μ„œλŠ” κ°€μž₯ μž‘μ€ κ²©μžκ°€ (m, t) = (1, 1) 의 3차원이닀. dim 을 ν‚€μš°λ©΄ μ΅œλ‹¨λ²‘ν„°λŠ” 더 μ§§μ•„μ§€μ§€λ§Œ(2^931 β†’ 2^900) μ–΄μ°¨ν”Ό ν•„μš”ν•œ 건 2^1023 μ΄ν•˜λΌ μ˜λ―Έκ°€ μ—†κ³ , 순수 파이썬 LLL λ‘œλŠ” λΉ„μš©λ§Œ κΈ‰κ²©νžˆ λŠ”λ‹€. 이 λ¬Έμ œμ—μ„œ tiny λŠ” 근만이 μ•„λ‹ˆλΌ κ²©μžμ—λ„ ν•΄λ‹Ήν•œλ‹€.

Coppersmith νŒŒμ΄ν”„λΌμΈ 도식 β€” f(x)=MSB+x 둜 μ‹œμž‘ν•΄ p 둜 λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€λŠ” 닀항식 셋을 X 둜 μŠ€μΌ€μΌν•œ 3x3 μ‚Όκ°ν–‰λ ¬λ‘œ λ§Œλ“€κ³ , LLL 둜 뽑은 짧은 벑터가 Howgrave-Graham 쑰건을 λ„˜κ²¨ μ •μˆ˜κ·Ό x0 λ₯Ό μ£Όλ©΄ n 이 μͺΌκ°œμ§„λ‹€
Coppersmith νŒŒμ΄ν”„λΌμΈ 도식 β€” f(x)=MSB+x 둜 μ‹œμž‘ν•΄ p 둜 λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€λŠ” 닀항식 셋을 X 둜 μŠ€μΌ€μΌν•œ 3x3 μ‚Όκ°ν–‰λ ¬λ‘œ λ§Œλ“€κ³ , LLL 둜 뽑은 짧은 벑터가 Howgrave-Graham 쑰건을 λ„˜κ²¨ μ •μˆ˜κ·Ό x0 λ₯Ό μ£Όλ©΄ n 이 μͺΌκ°œμ§„λ‹€



🧱 LLL 을 직접 짜기

fpylll 이 이 머신에 μ—†μ—ˆκ³ , μ–΄μ°¨ν”Ό 3차원이라 ꡳ이 뢙일 μ΄μœ λ„ μ—†μ—ˆλ‹€. ν‘œμ€€ 라이브러리만으둜 μ§°λ‹€.

μ£Όμ˜ν•  게 ν•˜λ‚˜ μžˆλ‹€. 그람-슈미트λ₯Ό λΆ€λ™μ†Œμˆ˜μ μœΌλ‘œ ν•˜λ©΄ μ•ˆ λœλ‹€. 성뢄이 2900λΉ„νŠΈμ§œλ¦¬λΌ float λ‘œλŠ” μœ νš¨μˆ«μžκ°€ ν†΅μ§Έλ‘œ λ‚ μ•„κ°„λ‹€. Fraction 으둜 μ •ν™•νžˆ 계산해야 ν•œλ‹€.

#!/usr/bin/env python3
"""ν‘œμ€€ 라이브러리만으둜 λ§Œλ“  LLL(Lenstra-Lenstra-Lovasz) 격자 μΆ•μ•½.
 
Fraction 으둜 그람-슈미트λ₯Ό μ •ν™•νžˆ κ³„μ‚°ν•œλ‹€. λΆ€λ™μ†Œμˆ˜μ μ„ μ“°λ©΄ 2000λΉ„νŠΈκ°€ λ„˜λŠ”
μ„±λΆ„μ—μ„œ μœ νš¨μˆ«μžκ°€ ν†΅μ§Έλ‘œ λ‚ μ•„κ°€λ―€λ‘œ μ •μˆ˜/유리수 μ‚°μˆ μ΄ ν•„μˆ˜λ‹€.
차원이 10 μ΄ν•˜λΌ O(n^4) μž¬κ³„μ‚°μ΄μ–΄λ„ μΆ©λΆ„νžˆ λΉ λ₯΄λ‹€.
"""
from fractions import Fraction
 
 
def _dot(u, v):
    return sum(a * b for a, b in zip(u, v))
 
 
def _gram_schmidt(B):
    n = len(B)
    Bs, mu = [], [[Fraction(0)] * n for _ in range(n)]
    for i in range(n):
        v = [Fraction(x) for x in B[i]]
        for j in range(i):
            mu[i][j] = _dot(B[i], Bs[j]) / _dot(Bs[j], Bs[j])
            v = [v[k] - mu[i][j] * Bs[j][k] for k in range(len(v))]
        Bs.append(v)
    return Bs, mu
 
 
def lll(basis, delta=Fraction(99, 100)):
    """ν–‰ 벑터 리슀트λ₯Ό λ°›μ•„ LLL μΆ•μ•½λœ κΈ°μ €λ₯Ό λŒλ €μ€€λ‹€."""
    B = [list(map(int, row)) for row in basis]
    n = len(B)
    Bs, mu = _gram_schmidt(B)
    k = 1
    while k < n:
        for j in range(k - 1, -1, -1):
            if abs(mu[k][j]) > Fraction(1, 2):
                r = int(round(mu[k][j]))
                B[k] = [B[k][i] - r * B[j][i] for i in range(len(B[k]))]
                Bs, mu = _gram_schmidt(B)
        lhs = _dot(Bs[k], Bs[k])
        rhs = (delta - mu[k][k - 1] ** 2) * _dot(Bs[k - 1], Bs[k - 1])
        if lhs >= rhs:
            k += 1
        else:
            B[k], B[k - 1] = B[k - 1], B[k]
            Bs, mu = _gram_schmidt(B)
            k = max(k - 1, 1)
    return B

직접 μ§  κ±Έ κ·ΈλŒ€λ‘œ λ―Ώκ³  2000λΉ„νŠΈ κ²©μžμ— λ°€μ–΄ λ„£κΈ° 전에, 닡을 μ•„λŠ” μž‘μ€ μž…λ ₯μ—μ„œ ν™•μΈν–ˆλ‹€. Cohen 의 κ΅κ³Όμ„œ μ˜ˆμ œμ™€, 거의 ν‰ν–‰ν•΄μ„œ μΆ•μ•½ νš¨κ³Όκ°€ λšœλ ·ν•œ 2차원 κΈ°μ € ν•˜λ‚˜.

#!/usr/bin/env python3
"""μžμž‘ lll.py κ°€ 정말 LLL 좕약을 ν•˜λŠ”μ§€ ν™•μΈν•œλ‹€.
Cohen 의 κ΅κ³Όμ„œ 예제 기저와, κ·Έ κΈ°μ €κ°€ μ‹€μ œλ‘œ μΆ•μ•½ 쑰건(size-reduced + Lovasz)을
λ§Œμ‘±ν•˜λŠ”μ§€κΉŒμ§€ 같이 κ²€μ‚¬ν•œλ‹€.
"""
from fractions import Fraction
 
from lll import lll, _gram_schmidt
 
 
def check(B):
    Bs, mu = _gram_schmidt(B)
    size_ok = all(abs(mu[i][j]) <= Fraction(1, 2)
                  for i in range(len(B)) for j in range(i))
    lovasz_ok = all(sum(x * x for x in Bs[k]) >=
                    (Fraction(99, 100) - mu[k][k - 1] ** 2) * sum(x * x for x in Bs[k - 1])
                    for k in range(1, len(B)))
    return size_ok, lovasz_ok
 
 
for name, B in [('Cohen 예제', [[1, 1, 1], [-1, 0, 2], [3, 5, 6]]),
                ('거의 ν‰ν–‰ν•œ 2차원 κΈ°μ €', [[201, 37], [140, 26]])]:
    R = lll(B)
    s, l = check(R)
    before = min(sum(x * x for x in r) for r in B)
    after = min(sum(x * x for x in r) for r in R)
    print(f'{name}')
    print(f'  μž…λ ₯  {B}')
    print(f'  좜λ ₯  {R}')
    print(f'  μ΅œλ‹¨λ²‘ν„° 노름^2  {before} -> {after}')
    print(f'  size-reduced {s},  Lovasz 쑰건 {l}')
python3 lll_check.py

lll_check.py μ‹€ν–‰ κ²°κ³Ό β€” Cohen κ΅κ³Όμ„œ μ˜ˆμ œκ°€ μ•Œλ €μ§„ μΆ•μ•½ κΈ°μ €λ‘œ λ–¨μ–΄μ§€κ³ , 거의 ν‰ν–‰ν•œ 2차원 κΈ°μ €λŠ” μ΅œλ‹¨λ²‘ν„° 노름 제곱이 20276 μ—μ„œ 50 으둜 쀄며 size-reduced 와 Lovasz 쑰건이 λ‘˜ λ‹€ True 둜 λ‚˜μ˜¨λ‹€
lll_check.py μ‹€ν–‰ κ²°κ³Ό β€” Cohen κ΅κ³Όμ„œ μ˜ˆμ œκ°€ μ•Œλ €μ§„ μΆ•μ•½ κΈ°μ €λ‘œ λ–¨μ–΄μ§€κ³ , 거의 ν‰ν–‰ν•œ 2차원 κΈ°μ €λŠ” μ΅œλ‹¨λ²‘ν„° 노름 제곱이 20276 μ—μ„œ 50 으둜 쀄며 size-reduced 와 Lovasz 쑰건이 λ‘˜ λ‹€ True 둜 λ‚˜μ˜¨λ‹€

κ΅κ³Όμ„œ μ˜ˆμ œλŠ” μ•Œλ €μ§„ μΆ•μ•½ κΈ°μ € κ·ΈλŒ€λ‘œ λ‚˜μ™”κ³ , 두 λ²ˆμ§ΈλŠ” 노름 제곱이 20276μ—μ„œ 50으둜 λ–¨μ–΄μ‘Œλ‹€. μΆ•μ•½ 쑰건 두 κ°œλ„ 사후 κ²€μ‚¬λ‘œ ν†΅κ³Όν•œλ‹€.


πŸ› μ‚½μ§ˆ

β–ΆπŸ› μ‚½μ§ˆ 1 β€” κ²©μžλŠ” 클수둝 쒋은 쀄 μ•Œμ•˜λ‹€

μ²˜μŒμ—” (m, t) = (3, 2) 둜 6차원 격자λ₯Ό μ§°λ‹€. Coppersmith μ„€λͺ…에 항상 "m 을 μΆ©λΆ„νžˆ 크게"κ°€ λΆ™μœΌλ‹ˆ μ—¬μœ λ₯Ό λ‘λŠ” 게 μ•ˆμ „ν•˜λ‹€κ³  μƒκ°ν–ˆλ‹€.

2뢄이 μ§€λ‚˜λ„ μ•ˆ 끝났닀. 순수 파이썬 Fraction 그람-슈미트λ₯Ό λ§€ 단계 λ‹€μ‹œ κ³„μ‚°ν•˜λŠ” κ΅¬ν˜„μ΄λΌ 차원이 였λ₯΄λ©΄ λΉ„μš©μ΄ ν­λ°œν•œλ‹€. μœ„μ˜ lattice_scan.py 둜 μ‹€μΈ‘ν•œ 게 κ·Έλž˜μ„œμ˜€λ‹€.

m  t  dim  log2(det)    LLL μƒν•œ    ν•„μš” p^m  νŒμ •   μ‹€μΈ‘
 1  1    3       2888     964.0      1024   톡과     0.36s
 1  2    4       3728     933.8      1024   톡과     1.28s
 2  1    4       7824    1957.8      2048   톡과     5.50s
 2  2    5       8944    1791.0      2048   톡과    77.53s
 3  1    5      15088    3019.8      3072   톡과    28.49s

m 을 ν‚€μš°λ©΄ 쑰건은 더 μ—¬μœ λ‘­κ²Œ μ„œμ§€λ§Œ 이 λ¬Έμ œμ—” 이미 μ—¬μœ κ°€ μΆ©λΆ„ν•˜λ‹€. λ―Έμ§€μˆ˜κ°€ 280λΉ„νŠΈλ‘œ 이둠 ν•œκ³„ 512λΉ„νŠΈμ˜ 절반 남짓이라, μ΅œμ†Œ κ²©μžμ—μ„œ λ°”λ‘œ 쑰건이 μ„ λ‹€. "m 을 크게"λŠ” λ―Έμ§€μˆ˜κ°€ ν•œκ³„μ— 바짝 λΆ™μ—ˆμ„ λ•Œ ν•˜λŠ” μ–˜κΈ°λ‹€.

β–ΆπŸ› μ‚½μ§ˆ 2 β€” 닀항식 GCD κ°€ μƒμˆ˜μ—μ„œ λ¬΄ν•œλ£¨ν”„μ— λΉ μ‘Œλ‹€

LLL 은 0.36μ΄ˆμ— λλ‚˜λŠ”λ° solve.py κ°€ 5λΆ„ νƒ€μž„μ•„μ›ƒκΉŒμ§€ μ•ˆ λŒμ•„μ™”λ‹€. 범인은 λ‚΄κ°€ μ§  poly_gcd μ˜€λ‹€.

처음 κ΅¬ν˜„μ€ λ‚˜λˆ—μ…ˆ ν•œ 단계λ₯Ό λ°˜λ³΅ν•˜λ©΄μ„œ while len(b) > 1 or b[0] != 0 둜 λŒμ•˜λ‹€. 그런데 두 닀항식이 μ„œλ‘œμ†Œλ©΄ λ‚˜λ¨Έμ§€κ°€ 0이 μ•„λ‹Œ μƒμˆ˜λ‘œ λ–¨μ–΄μ§„λ‹€. κ·Έλ•Œ a λŠ” 맀번 0이 되고 b λŠ” κ·ΈλŒ€λ‘œλΌ μ’…λ£Œ 쑰건에 영영 λͺ» λ‹ΏλŠ”λ‹€.

LLL κ²°κ³Ό ν–‰ μ„Έ 개 쀑 쑰건을 λ§Œμ‘±ν•˜μ§€ λͺ»ν•˜λŠ” 행이 μ„žμ—¬ 있으면 κ·Έ μ‘°ν•©μ˜ GCD κ°€ μƒμˆ˜κ°€ λœλ‹€. 즉 이 κ²½λ‘œλŠ” 정상 λ™μž‘μ—μ„œλ„ λ°˜λ“œμ‹œ λ°ŸλŠ”λ‹€.

고친 건 κ΅κ³Όμ„œ μœ ν΄λ¦¬λ“œ ν˜•νƒœλ‘œ 되돌린 것뿐이닀.

while not (len(b) == 1 and b[0] == 0):
    a, b = b, poly_mod(a, b)

poly_mod μ•ˆμ—μ„œλ„ λ‚˜λˆ„λŠ” μͺ½μ΄ μƒμˆ˜λ©΄(차수 0) λ‚˜λ¨Έμ§€λ₯Ό λ°”λ‘œ [0] 으둜 λŒλ €μ€€λ‹€. 이 두 μ€„λ‘œ 5뢄이 0.42μ΄ˆκ°€ 됐닀.

β–ΆπŸ› μ‚½μ§ˆ 3 β€” 쑰용히 ν‹€λ¦¬λŠ” μ„Έ κ°€μ§€

격자 ν’€μ΄λŠ” 틀렸을 λ•Œ μ˜ˆμ™Έλ₯Ό μ•ˆ λ‚Έλ‹€. LLL 은 λ©€μ©‘νžˆ 돌고 μ‹œκ°„λ„ 짧은데 근만 μ•ˆ λ‚˜μ˜¨λ‹€. κ·Έλž˜μ„œ "이둠이 μ•ˆ λ§žλ‚˜" λ₯Ό μ˜μ‹¬ν•˜κ²Œ λ˜λŠ”λ°, μ‹€μ œλ‘œλŠ” μ…‹ λ‹€ μ‚¬μ†Œν•œ κ΅¬ν˜„ μ‹€μˆ˜μ˜€λ‹€. μž¬ν˜„ 슀크립트둜 남겨 λ’€λ‹€.

#!/usr/bin/env python3
"""풀이 도쀑 μ‹€μ œλ‘œ λ°Ÿμ•˜λ˜ μ„Έ κ°€μ§€ 함정을 κ·ΈλŒ€λ‘œ μž¬ν˜„ν•œλ‹€.
μ…‹ λ‹€ '쑰용히 ν‹€λ¦°λ‹€' - μ˜ˆμ™Έλ„ μ—†κ³  였래 걸리지도 μ•ŠλŠ”λ° 근이 μ•ˆ λ‚˜μ˜¨λ‹€.
"""
import re
 
from lll import lll
from solve import coppersmith_known_msb, poly_gcd, poly_trim, _newton_int_root
 
txt = open('extracted/output.txt').read()
msb = int(re.search(r'bits of p\.\.\.\s*\n(\d+)', txt).group(1))
n = int(re.search(r'\((\d+),(\d+)\)', txt).group(1))
TRUE_X0 = 781182359928297838659720821147531543713572092829500480647209554762229923863457600593
 
 
def report(tag, roots):
    ok = [r for r in roots if n % (msb + r) == 0]
    print(f'  -> 찾은 κ·Ό {len(roots)}개, n 을 λ‚˜λˆ„λŠ” 것 {len(ok)}개'
          f'{"  <== 성곡" if ok else "  <== μ‹€νŒ¨"}')
 
 
print('[함정 1] x -> X*x μŠ€μΌ€μΌλ§μ„ λΉΌλ¨Ήκ³  격자λ₯Ό 짜면')
dim = 3
polys = [[n], [msb, 1], [0, msb, 1]]
B = [[polys[r][k] if k < len(polys[r]) else 0 for k in range(dim)] for r in range(dim)]
R = lll(B)
cand = [poly_trim(list(row)) for row in R]
roots = []
for i in range(dim):
    for j in range(i + 1, dim):
        g = poly_gcd(cand[i], cand[j])
        if len(g) == 2 and (-g[0] / g[1]).denominator == 1:
            roots.append(int(-g[0] / g[1]))
    r = _newton_int_root(cand[i], 1 << 280)
    if r is not None:
        roots.append(r)
print(f'  LLL μ΅œλ‹¨λ²‘ν„° = {[hex(v)[:12] + ".." if abs(v) > 2**40 else v for v in R[0]]}')
report('scale', roots)
 
print()
print('[함정 2] t=0 - x λ₯Ό κ³±ν•œ 이동 닀항식을 λ„£μ§€ μ•ŠμœΌλ©΄ (격자 2x2)')
print('  Howgrave-Graham 쑰건: 2^1164.8 < 2^1024 이어야 ν•˜λŠ”λ° 미달')
try:
    roots = coppersmith_known_msb(n, msb, 1 << 280, m=1, t=0, verbose=False)
except Exception as exc:
    roots = []
    print(f'  μ˜ˆμ™Έ: {exc!r}')
report('t0', roots)
 
print()
print('[함정 3] lower_bound λ₯Ό n(2048λΉ„νŠΈ) 이 μ•„λ‹ˆλΌ p(1024λΉ„νŠΈ) κΈ°μ€€μœΌλ‘œ 읽으면')
wrong = int(1024 * (0.4 ** 2 - 0.4 ** 2 / 7))
print(f'  X = 2^{wrong} 인데 μ°Έκ°’ x0 λŠ” {TRUE_X0.bit_length()}λΉ„νŠΈλ‹€')
roots = coppersmith_known_msb(n, msb, 1 << wrong, m=1, t=1, verbose=False)
report('smallX', roots)
 
print()
print('[λŒ€μ‘°] μ œλŒ€λ‘œ μ§  경우')
roots = coppersmith_known_msb(n, msb, 1 << 280, m=1, t=1, verbose=False)
report('ok', roots)
print(f'  x0 = {roots[0] if roots else "-"}')
python3 traps.py

traps.py μ‹€ν–‰ κ²°κ³Ό β€” μŠ€μΌ€μΌλ§ λˆ„λ½, t=0 인 2x2 격자, X λ₯Ό 2^140 으둜 μž‘μ€ 경우 μ…‹ λ‹€ μ˜ˆμ™Έ 없이 κ·Ό 0개둜 쑰용히 μ‹€νŒ¨ν•˜κ³  λ§ˆμ§€λ§‰ λŒ€μ‘°κ΅°λ§Œ x0 λ₯Ό ν•˜λ‚˜ μ°ΎλŠ”λ‹€
traps.py μ‹€ν–‰ κ²°κ³Ό β€” μŠ€μΌ€μΌλ§ λˆ„λ½, t=0 인 2x2 격자, X λ₯Ό 2^140 으둜 μž‘μ€ 경우 μ…‹ λ‹€ μ˜ˆμ™Έ 없이 κ·Ό 0개둜 쑰용히 μ‹€νŒ¨ν•˜κ³  λ§ˆμ§€λ§‰ λŒ€μ‘°κ΅°λ§Œ x0 λ₯Ό ν•˜λ‚˜ μ°ΎλŠ”λ‹€

첫째, x β†’ XΒ·x μΉ˜ν™˜μ„ λΉΌλ¨ΉλŠ” 것. 이게 제일 ν—·κ°ˆλ¦°λ‹€. μŠ€μΌ€μΌλ§ 없이도 κ²©μžλŠ” 잘 짜이고 LLL 도 λˆλ‹€. ν•˜μ§€λ§Œ LLL 이 μ€„μ΄λŠ” 건 λ²‘ν„°μ˜ μœ ν΄λ¦¬λ“œ 노름인데, Howgrave-Graham 이 μš”κ΅¬ν•˜λŠ” 건 "근의 ν¬κΈ°κΉŒμ§€ κ°μ•ˆν•œ κ³„μˆ˜ 크기"λ‹€. X λ₯Ό κ³±ν•΄ 두지 μ•ŠμœΌλ©΄ 이 λ‘˜μ΄ μ„œλ‘œ λ‹€λ₯Έ 양을 μž¬λŠ” μ…ˆμ΄λΌ 짧은 벑터가 λ‚˜μ™€λ„ μ“Έλͺ¨κ°€ μ—†λ‹€.

λ‘˜μ§Έ, 이동 닀항식 없이 t = 0. 2차원 κ²©μžλ‘œλŠ” 2^1164.8 < 2^1024 λ₯Ό λ§Œμ‘±ν•  방법이 μ—†λ‹€. μ‹€ν–‰ μ‹œκ°„μ΄ μ§§μ•„μ„œ "빨리 λλ‚¬μœΌλ‹ˆ λ­”κ°€ 됐겠지" ν•˜κ³  λ„˜μ–΄κ°€κΈ° 쉽닀.

μ…‹μ§Έ, lower_bound λ₯Ό p κΈ°μ€€μœΌλ‘œ μ½λŠ” 것. 문제 μ½”λ“œλŠ” n.bit_length() λ₯Ό μ“°λŠ”λ° μŠ΅κ΄€μ μœΌλ‘œ p 의 1024둜 읽으면 X = 2^140 이 λœλ‹€. μ°Έκ°’ x0 λŠ” 279λΉ„νŠΈλΌ μƒν•œ 밖이고, 격자 쑰건은 였히렀 더 μ—¬μœ λ‘œμ›Œμ§€λ‹ˆ 아무 경고도 μ•ˆ λœ¬λ‹€.



πŸš€ Full Exploit

μœ„ 쑰각듀을 ν•©μΉœ μžμ²΄μ™„κ²° 풀이닀. μ˜μ‘΄μ„±μ€ ν‘œμ€€ λΌμ΄λΈŒλŸ¬λ¦¬μ™€ 같은 ν΄λ”μ˜ lll.py 뿐이고, 배포본 output.txt 만 있으면 λŒμ•„κ°„λ‹€.

#!/usr/bin/env python3
"""tiny roots (DreamHack, Silver 1, crypto) μžμ²΄μ™„κ²° 풀이.
 
p 의 ν•˜μœ„ 280λΉ„νŠΈλ§Œ μ§€μ›Œμ§„ RSA κ³΅κ°œν‚€μ—μ„œ p λ₯Ό ν†΅μ§Έλ‘œ λ³΅κ΅¬ν•œλ‹€.
Coppersmith 의 'known MSB of a factor'(May) λ₯Ό Howgrave-Graham 격자둜 직접 κ΅¬μ„±ν–ˆλ‹€.
μ™ΈλΆ€ μ˜μ‘΄μ„± μ—†μŒ - ν‘œμ€€ λΌμ΄λΈŒλŸ¬λ¦¬μ™€ 같은 ν΄λ”μ˜ lll.py 뿐이닀.
 
    python3 solve.py extracted/output.txt
"""
import re
import sys
from fractions import Fraction
 
from lll import lll
 
 
# ---------------------------------------------------------------- 닀항식 μœ ν‹Έ
def poly_mul(a, b):
    out = [0] * (len(a) + len(b) - 1)
    for i, x in enumerate(a):
        if x:
            for j, y in enumerate(b):
                out[i + j] += x * y
    return out
 
 
def poly_eval(c, x):
    acc = 0
    for coeff in reversed(c):
        acc = acc * x + coeff
    return acc
 
 
def poly_trim(c):
    while len(c) > 1 and c[-1] == 0:
        c.pop()
    return c
 
 
def poly_mod(a, b):
    """a mod b (유리수 κ³„μˆ˜). b λŠ” 0 이 μ•„λ‹ˆμ–΄μ•Ό ν•œλ‹€."""
    a, db = list(a), len(b) - 1
    if db == 0:
        return [Fraction(0)]
    while len(a) - 1 >= db:
        shift, factor = len(a) - 1 - db, a[-1] / b[-1]
        for i, coeff in enumerate(b):
            a[i + shift] -= factor * coeff
        poly_trim(a)
        if len(a) == 1 and a[0] == 0:
            break
    return a
 
 
def poly_gcd(a, b):
    """유리수 κ³„μˆ˜ 닀항식 GCD. κ³„μˆ˜λ₯Ό Fraction 으둜 올렀 μ •ν™•νžˆ κ³„μ‚°ν•œλ‹€."""
    a = poly_trim([Fraction(x) for x in a])
    b = poly_trim([Fraction(x) for x in b])
    while not (len(b) == 1 and b[0] == 0):
        a, b = b, poly_mod(a, b)
    return [c / a[-1] for c in a] if a[-1] != 0 else a
 
 
# --------------------------------------------------- Coppersmith (known MSB)
def coppersmith_known_msb(n, a0, X, m=1, t=1, verbose=True):
    """f(x) = a0 + x κ°€ n 의 λ―Έμ§€ 인수 p 둜 λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€λŠ” |x0| < X λ₯Ό μ°ΎλŠ”λ‹€."""
    dim = m + t + 1
 
    # g_i(x) = n^(m-i) * f(x)^i  (i = 0..m)  /  h_j(x) = x^j * f(x)^m  (j = 1..t)
    fpow = [[1]]
    for _ in range(m):
        fpow.append(poly_mul(fpow[-1], [a0, 1]))
    polys = [[c * pow(n, m - i) for c in fpow[i]] for i in range(m + 1)]
    polys += [[0] * j + fpow[m] for j in range(1, t + 1)]
 
    # x -> X*x 둜 μΉ˜ν™˜ν•΄ 격자λ₯Ό μ§ λ‹€. 이 μŠ€μΌ€μΌλ§μ΄ μ—†μœΌλ©΄ LLL 이 "μ§§λ‹€"κ³  λ³΄λŠ”
    # 벑터와 Howgrave-Graham 이 μš”κ΅¬ν•˜λŠ” "μž‘μ€ κ³„μˆ˜"κ°€ μ„œλ‘œ λ‹€λ₯Έ 뜻이 λœλ‹€.
    B = [[polys[r][k] * pow(X, k) if k < len(polys[r]) else 0
          for k in range(dim)] for r in range(dim)]
 
    det = 1
    for r in range(dim):
        det *= B[r][r]
    if verbose:
        print(f'[lattice] dim={dim} (m={m}, t={t})')
        print(f'[lattice] log2(det)      = {det.bit_length()}')
        print(f'[lattice] log2(det)/dim  = {det.bit_length() / dim:.1f}')
        print(f'[lattice] λͺ©ν‘œ p^m       = 2^{1024 * m}  (p λŠ” 1024λΉ„νŠΈ)')
 
    R = lll(B)
 
    roots = []
    cand = []
    for row in R:
        coeffs = [row[k] // pow(X, k) for k in range(dim)]
        norm2 = sum(c * c for c in row)
        if verbose:
            print(f'[lll] ||v|| = 2^{(norm2.bit_length() - 1) / 2:.1f}'
                  f'   (Howgrave-Graham μš”κ΅¬: < p^{m}/sqrt({dim}) = 2^{1024 * m - 1})')
        cand.append(poly_trim(coeffs))
 
    # μ„œλ‘œ λ‹€λ₯Έ 짧은 벑터 두 개의 μ΅œλŒ€κ³΅μ•½λ‹€ν•­μ‹μ΄ κ³§ (x - x0) λ‹€.
    for i in range(len(cand)):
        for j in range(i + 1, len(cand)):
            g = poly_gcd(cand[i], cand[j])
            if len(g) == 2:
                r = -g[0] / g[1]
                if r.denominator == 1:
                    roots.append(int(r))
    # 보쑰 경둜: κ°€μž₯ 짧은 벑터 ν•˜λ‚˜λ§ŒμœΌλ‘œ μ •μˆ˜ 뉴턴법
    for c in cand[:2]:
        r = _newton_int_root(c, X)
        if r is not None:
            roots.append(r)
 
    out = []
    for r in roots:
        if 0 <= r < X and r not in out and n % (a0 + r) == 0:
            out.append(r)
    return out
 
 
def _newton_int_root(coeffs, X):
    d = len(coeffs) - 1
    if d < 1:
        return None
    dc = [i * coeffs[i] for i in range(1, d + 1)]
    x = X
    for _ in range(4000):
        fx = poly_eval(coeffs, x)
        if fx == 0:
            return x
        dfx = poly_eval(dc, x)
        if dfx == 0:
            return None
        nx = x - fx // dfx
        if nx == x:
            return None
        x = nx
    return None
 
 
# ------------------------------------------------------------------- λ³Έ 풀이
def main(path='extracted/output.txt'):
    txt = open(path).read()
    c = int(re.search(r'Here is the flag : (\d+)', txt).group(1))
    msb = int(re.search(r'bits of p\.\.\.\s*\n(\d+)', txt).group(1))
    n, e = (int(v) for v in re.search(r'\((\d+),(\d+)\)', txt).groups())
 
    # 문제 μ½”λ“œκ°€ μ§€μš΄ λΉ„νŠΈ 수λ₯Ό κ·ΈλŒ€λ‘œ μž¬ν˜„ν•œλ‹€.
    beta = 0.4
    epsilon = beta ** 2 / 7
    lower_bound = int(n.bit_length() * (beta ** 2 - epsilon))
    X = 1 << lower_bound
 
    print(f'[param] n  {n.bit_length()}λΉ„νŠΈ,  e  {e.bit_length()}λΉ„νŠΈ')
    print(f'[param] μ§€μ›Œμ§„ ν•˜μœ„ λΉ„νŠΈ = {lower_bound}  ->  X = 2^{lower_bound}')
    print(f'[param] μ•Œλ €μ§„ p 의 λΉ„νŠΈ = {1024 - lower_bound}')
 
    roots = coppersmith_known_msb(n, msb, X, m=1, t=1)
    if not roots:
        print('μ‹€νŒ¨: 근을 μ°Ύμ§€ λͺ»ν–ˆλ‹€')
        return 1
    x0 = roots[0]
    p = msb + x0
    q = n // p
    print(f'[root] x0 = {x0}')
    print(f'[root] x0 λŠ” {x0.bit_length()}λΉ„νŠΈ  (μƒν•œ {lower_bound}λΉ„νŠΈ)')
    print(f'[check] n % p == 0  ->  {n % p == 0}')
    print(f'[check] p * q == n  ->  {p * q == n}')
 
    d = pow(e, -1, (p - 1) * (q - 1))
    m_int = pow(c, d, n)
    flag = m_int.to_bytes((m_int.bit_length() + 7) // 8, 'big')
    print(f'FLAG: {flag.decode()}')
    return 0
 
 
if __name__ == '__main__':
    sys.exit(main(*sys.argv[1:]))

근을 μ°ΎλŠ” λ§ˆμ§€λ§‰ λ‹¨κ³„λ§Œ ν•œ 쀄 덧뢙인닀. LLL 이 λŒλ €μ€€ μ„Έ 행을 각각 X^k 둜 λ˜λ‚˜λˆ  μ •μˆ˜ λ‹€ν•­μ‹μœΌλ‘œ 되돌리고, 그쀑 두 개의 μ΅œλŒ€κ³΅μ•½λ‹€ν•­μ‹μ„ 작으면 (x - x0) 만 λ‚¨λŠ”λ‹€. κ³„μˆ˜κ°€ 2000λΉ„νŠΈκΈ‰μ΄λΌ 유리근 μ •λ¦¬λ‘œ μΈμˆ˜λΆ„ν•΄ν•˜λŠ” 건 ν˜„μ‹€μ μ΄μ§€ μ•Šμ•„μ„œ, GCD 둜 1μ°¨ 인자λ₯Ό 직접 λ½‘λŠ” μͺ½μ„ νƒν–ˆλ‹€. μ‹€νŒ¨ν•  λ•Œλ₯Ό λŒ€λΉ„ν•΄ μ •μˆ˜ 뉴턴법 κ²½λ‘œλ„ ν•˜λ‚˜ 두고, μ΅œμ’… νŒμ •μ€ n % p == 0 이 ν•œλ‹€.

python3 solve.py

solve.py μ‹€ν–‰ κ²°κ³Ό β€” 3x3 격자의 log2(det) 2888, LLL μ΅œλ‹¨λ²‘ν„° 2^931 이 Howgrave-Graham μš”κ΅¬μΉ˜ 2^1023 을 ν†΅κ³Όν•˜κ³ , x0 279λΉ„νŠΈκ°€ λ‚˜μ™€ n % p == 0 True 와 ν•¨κ»˜ ν”Œλž˜κ·Έκ°€ λ³΅ν˜Έλœλ‹€
solve.py μ‹€ν–‰ κ²°κ³Ό β€” 3x3 격자의 log2(det) 2888, LLL μ΅œλ‹¨λ²‘ν„° 2^931 이 Howgrave-Graham μš”κ΅¬μΉ˜ 2^1023 을 ν†΅κ³Όν•˜κ³ , x0 279λΉ„νŠΈκ°€ λ‚˜μ™€ n % p == 0 True 와 ν•¨κ»˜ ν”Œλž˜κ·Έκ°€ λ³΅ν˜Έλœλ‹€

0.42초. n % p == 0 이 True 둜 λ–¨μ–΄μ‘Œκ³  ν”Œλž˜κ·Έκ°€ κ·ΈλŒ€λ‘œ λ‚˜μ™”λ‹€.

DH{yeaaaahhh!!_C00p3rsM1th_1s_G0d_><}

βœ… ꡐ차검증 β€” λ„€ 갈래둜

직접 μ§  κ²©μžμ™€ 직접 μ§  LLL 둜 λ‚˜μ˜¨ 닡이라 ν•œ 번 더 ν™•μΈν–ˆλ‹€.

SageMath 의 small_roots

같은 일을 ν•˜λŠ” ν‘œμ€€ κ΅¬ν˜„κ³Ό λŒ€μ‘°ν•œλ‹€. Sage 의 small_roots λŠ” beta 와 epsilon 을 λ°›μ•„ λ‚΄λΆ€μ—μ„œ m, t λ₯Ό μ •ν•˜λ―€λ‘œ, 문제 μ½”λ“œκ°€ μ“΄ 값을 κ·ΈλŒ€λ‘œ λ„˜κΈ°λ©΄ λœλ‹€.

# tiny roots (DreamHack, Silver 1, crypto) - SageMath cross-check
# p 의 μƒμœ„ 744λΉ„νŠΈκ°€ 곡개 -> Coppersmith(May) 'known MSB of factor'
import re
from Crypto.Util.number import long_to_bytes
 
txt = open('extracted/output.txt').read()
c   = Integer(re.search(r'Here is the flag : (\d+)', txt).group(1))
msb = Integer(re.search(r'bits of p\.\.\.\s*\n(\d+)', txt).group(1))
n, e = [Integer(v) for v in re.search(r'\((\d+),(\d+)\)', txt).groups()]
 
beta    = 0.4
epsilon = beta ** 2 / 7
X       = 2 ** int(n.nbits() * (beta ** 2 - epsilon))     # = 2^280, λ―Έμ§€ ν•˜μœ„λΉ„νŠΈ μƒν•œ
 
P.<x> = PolynomialRing(Zmod(n))
f = msb + x                                                # f(x0) = p = 0 mod p
roots = f.monic().small_roots(X=X, beta=beta, epsilon=epsilon)
print('small_roots ->', roots)
 
x0 = Integer(roots[0])
p  = msb + x0
print('p  =', p)
print('n % p == 0 ?', n % p == 0)
q = n // p
d = inverse_mod(e, (p - 1) * (q - 1))
m = power_mod(c, d, n)
print('FLAG:', long_to_bytes(int(m)).decode())
sage solve_sage.sage

SageMath 10.9 μ—μ„œ solve_sage.sage λ₯Ό 돌린 ν™”λ©΄ β€” small_roots κ°€ μžμž‘ κ²©μžμ™€ 같은 x0 λ₯Ό λ°˜ν™˜ν•˜κ³  p 전체 κ°’κ³Ό n % p == 0 True, λ™μΌν•œ ν”Œλž˜κ·Έκ°€ μ°νžŒλ‹€
SageMath 10.9 μ—μ„œ solve_sage.sage λ₯Ό 돌린 ν™”λ©΄ β€” small_roots κ°€ μžμž‘ κ²©μžμ™€ 같은 x0 λ₯Ό λ°˜ν™˜ν•˜κ³  p 전체 κ°’κ³Ό n % p == 0 True, λ™μΌν•œ ν”Œλž˜κ·Έκ°€ μ°νžŒλ‹€

x0 κ°€ μžλ¦Ώμˆ˜κΉŒμ§€ λ˜‘κ°™μ΄ λ‚˜μ™”λ‹€.

openssl 이 μΈμ •ν•˜λŠ” κ°œμΈν‚€μΈκ°€

λ³΅κ΅¬ν•œ p, q 둜 PKCS#1 κ°œμΈν‚€λ₯Ό μ†μœΌλ‘œ 쑰립해 openssl μ—κ²Œ νŒμ •μ„ λ§‘κ²Όλ‹€. openssl rsa -check λŠ” p 와 q 의 μ†Œμˆ˜μ„±, d, CRT νŒŒλΌλ―Έν„°μ˜ μ •ν•©μ„±κΉŒμ§€ λ³Έλ‹€. 우리 파이썬 μ½”λ“œμ™€ μ™„μ „νžˆ λ…λ¦½λœ 검증이닀.

#!/usr/bin/env python3
"""λ³΅κ΅¬ν•œ p, q 둜 PKCS#1 RSAPrivateKey(PEM)λ₯Ό 직접 μ‘°λ¦½ν•œλ‹€.
DER 을 μ†μœΌλ‘œ μ§œμ„œ 라이브러리 도움 없이 λ§Œλ“€κ³ , 검증은 openssl μ—κ²Œ λ§‘κΈ΄λ‹€.
"""
import base64
import re
 
from solve import coppersmith_known_msb
 
txt = open('extracted/output.txt').read()
msb = int(re.search(r'bits of p\.\.\.\s*\n(\d+)', txt).group(1))
n, e = (int(v) for v in re.search(r'\((\d+),(\d+)\)', txt).groups())
 
p = msb + coppersmith_known_msb(n, msb, 1 << 280, m=1, t=1, verbose=False)[0]
q = n // p
assert p * q == n and p > q
d = pow(e, -1, (p - 1) * (q - 1))
 
 
def der_len(l):
    if l < 0x80:
        return bytes([l])
    b = l.to_bytes((l.bit_length() + 7) // 8, 'big')
    return bytes([0x80 | len(b)]) + b
 
 
def der_int(v):
    b = v.to_bytes((v.bit_length() + 8) // 8, 'big') if v else b'\x00'
    return b'\x02' + der_len(len(b)) + b
 
 
body = b''.join(der_int(v) for v in
                (0, n, e, d, p, q, d % (p - 1), d % (q - 1), pow(q, -1, p)))
der = b'\x30' + der_len(len(body)) + body
b64 = base64.encodebytes(der).decode().replace('\n', '')
pem = ('-----BEGIN RSA PRIVATE KEY-----\n'
       + '\n'.join(b64[i:i + 64] for i in range(0, len(b64), 64))
       + '\n-----END RSA PRIVATE KEY-----\n')
open('tiny_roots_priv.pem', 'w').write(pem)
print(f'wrote tiny_roots_priv.pem  ({len(der)} bytes DER)')
#!/usr/bin/env bash
# λ³΅κ΅¬ν•œ p, q 둜 λ§Œλ“  κ°œμΈν‚€λ₯Ό openssl 이 μΈμ •ν•˜λŠ”μ§€ λ³Έλ‹€.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
python3 make_key.py
openssl rsa -in tiny_roots_priv.pem -check -noout
echo "openssl modulus : $(openssl rsa -in tiny_roots_priv.pem -noout -modulus | cut -c9-88)"
python3 -c "
import re
n = int(re.search(r'\((\d+),(\d+)\)', open('extracted/output.txt').read()).group(1))
print('output.txt 의 n :', format(n, 'X')[:80])"
./verify_openssl.sh

verify_openssl.sh μ‹€ν–‰ κ²°κ³Ό β€” μ†μœΌλ‘œ μ‘°λ¦½ν•œ 1206λ°”μ΄νŠΈ DER κ°œμΈν‚€μ— λŒ€ν•΄ openssl rsa -check κ°€ RSA key ok λ₯Ό λ‚΄κ³ , openssl 이 읽은 modulus 와 output.txt 의 n 이 μ•ž 80μžλ¦¬κΉŒμ§€ λ™μΌν•˜λ‹€
verify_openssl.sh μ‹€ν–‰ κ²°κ³Ό β€” μ†μœΌλ‘œ μ‘°λ¦½ν•œ 1206λ°”μ΄νŠΈ DER κ°œμΈν‚€μ— λŒ€ν•΄ openssl rsa -check κ°€ RSA key ok λ₯Ό λ‚΄κ³ , openssl 이 읽은 modulus 와 output.txt 의 n 이 μ•ž 80μžλ¦¬κΉŒμ§€ λ™μΌν•˜λ‹€

RSA key ok. λͺ¨λ“ˆλŸ¬μŠ€λ„ output.txt 의 n κ³Ό μΌμΉ˜ν•œλ‹€.

이 μΈμŠ€ν„΄μŠ€μ—λ§Œ 맞좘 풀이가 μ•„λ‹Œκ°€

문제 μ½”λ“œμ™€ λ˜‘κ°™μ€ λ°©μ‹μœΌλ‘œ RSA μΈμŠ€ν„΄μŠ€λ₯Ό μƒˆλ‘œ λ§Œλ“€μ–΄ 볡ꡬ 루틴을 κ·ΈλŒ€λ‘œ λŒλ Έλ‹€. 이 문제 ν•˜λ‚˜μ— μš°μ—°νžˆ λ§žμ€ 게 μ•„λ‹ˆλΌλŠ” 확인이닀.

#!/usr/bin/env python3
"""문제 μ½”λ“œμ™€ λ˜‘κ°™μ€ λ°©μ‹μœΌλ‘œ RSA μΈμŠ€ν„΄μŠ€λ₯Ό μƒˆλ‘œ λ§Œλ“€μ–΄ solve.py 의 볡ꡬ 루틴을 λŒλ¦°λ‹€.
이 문제 ν•˜λ‚˜μ—λ§Œ 맞좘 풀이가 μ•„λ‹ˆλΌλŠ” 것을 ν™•μΈν•˜λŠ” μš©λ„λ‹€.
"""
import time
 
from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes
 
from solve import coppersmith_known_msb
 
ROUNDS = 5
ok = 0
for r in range(1, ROUNDS + 1):
    plain = f'DH{{selftest_round_{r}}}'.encode()
    m = bytes_to_long(plain)
 
    p = getPrime(1024)
    q = getPrime(1024)
    if p < q:
        p, q = q, p
    n, e = p * q, getPrime(128)
 
    beta = 0.4
    epsilon = beta ** 2 / 7
    upper_bound = p.bit_length()
    lower_bound = int(n.bit_length() * (beta ** 2 - epsilon))
    MSB = p & (pow(2, upper_bound) - pow(2, lower_bound))      # 문제 μ½”λ“œ κ·ΈλŒ€λ‘œ
    ct = pow(m, e, n)
 
    st = time.time()
    roots = coppersmith_known_msb(n, MSB, 1 << lower_bound, m=1, t=1, verbose=False)
    el = time.time() - st
 
    if not roots:
        print(f'round {r}: μ‹€νŒ¨ ({el:.2f}s)')
        continue
    pp = MSB + roots[0]
    qq = n // pp
    d = pow(e, -1, (pp - 1) * (qq - 1))
    got = long_to_bytes(pow(ct, d, n))
    good = (pp == p and got == plain)
    ok += good
    print(f'round {r}: {el:.2f}s  p 볡ꡬ {pp == p}  볡호 {got.decode()}  '
          f'{"OK" if good else "NG"}')
 
print(f'\n{ok}/{ROUNDS} 성곡')
python3 selftest.py

selftest.py μ‹€ν–‰ κ²°κ³Ό β€” 문제 μ½”λ“œμ™€ 같은 λ°©μ‹μœΌλ‘œ μƒˆλ‘œ λ§Œλ“  RSA μΈμŠ€ν„΄μŠ€ 5κ°œμ—μ„œ λͺ¨λ‘ 0.4초 μ•ˆμ— p λ₯Ό μ •ν™•νžˆ λ³΅κ΅¬ν•˜κ³  ν‰λ¬ΈκΉŒμ§€ 되돌렀 5/5 μ„±κ³΅μœΌλ‘œ λλ‚œλ‹€
selftest.py μ‹€ν–‰ κ²°κ³Ό β€” 문제 μ½”λ“œμ™€ 같은 λ°©μ‹μœΌλ‘œ μƒˆλ‘œ λ§Œλ“  RSA μΈμŠ€ν„΄μŠ€ 5κ°œμ—μ„œ λͺ¨λ‘ 0.4초 μ•ˆμ— p λ₯Ό μ •ν™•νžˆ λ³΅κ΅¬ν•˜κ³  ν‰λ¬ΈκΉŒμ§€ 되돌렀 5/5 μ„±κ³΅μœΌλ‘œ λλ‚œλ‹€

5회 μ „λΆ€ p λ₯Ό μ •ν™•νžˆ λ³΅κ΅¬ν–ˆκ³  평문도 κ·ΈλŒ€λ‘œ λ‚˜μ™”λ‹€. νšŒλ‹Ή 0.4초 μ•ˆμͺ½μ΄λ‹€.

ν΄λ”λ§Œ 있으면 λ‹€μ‹œ λ‚˜μ˜€λŠ”κ°€

배포본과 슀크립트만 있으면 ν•œ λ²ˆμ— ν”Œλž˜κ·ΈκΉŒμ§€ λ‚˜μ˜€λ„λ‘ μž¬ν˜„ 슀크립트λ₯Ό 남겼닀.

#!/usr/bin/env bash
# tiny roots (DreamHack Silver 1, crypto) β€” ν•œ λ°© μž¬ν˜„.
# 배포본(extracted/output.txt)만 있으면 flag κΉŒμ§€ λ‚˜μ˜¨λ‹€. μ„œλ²„Β·ν¬λ ˆλ”§ λΆˆν•„μš”.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT=$(python3 -c "import json;print(json.load(open('문제.json'))['flag'])" 2>/dev/null || echo '')
 
OUT=$(timeout 300 python3 solve.py "$@" 2>&1) || true
 
echo "$OUT" | tail -8
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
if [ -n "$FLAG" ] && { [ -z "$EXPECT" ] || [ "$FLAG" = "$EXPECT" ]; }; then
  echo; echo "βœ… PASS  $FLAG"; exit 0
fi
echo; echo "❌ FAIL (얻은 κ°’: '${FLAG:-μ—†μŒ}' / κΈ°λŒ€: '$EXPECT')"; exit 1
./reproduce.sh

reproduce.sh μ‹€ν–‰ κ²°κ³Ό β€” solve.py λ₯Ό 돌렀 얻은 ν”Œλž˜κ·Έλ₯Ό 문제 λ©”νƒ€λ°μ΄ν„°μ˜ κΈ°λŒ€κ°’κ³Ό λŒ€μ‘°ν•˜κ³  PASS 둜 λλ‚˜λ©° μ„œλ²„λ‚˜ VM ν¬λ ˆλ”§ 없이 μ˜€ν”„λΌμΈμ—μ„œ μž¬ν˜„λœλ‹€
reproduce.sh μ‹€ν–‰ κ²°κ³Ό β€” solve.py λ₯Ό 돌렀 얻은 ν”Œλž˜κ·Έλ₯Ό 문제 λ©”νƒ€λ°μ΄ν„°μ˜ κΈ°λŒ€κ°’κ³Ό λŒ€μ‘°ν•˜κ³  PASS 둜 λλ‚˜λ©° μ„œλ²„λ‚˜ VM ν¬λ ˆλ”§ 없이 μ˜€ν”„λΌμΈμ—μ„œ μž¬ν˜„λœλ‹€



πŸ“ κ²°λ‘ 

λ…ΈμΆœ λΉ„νŠΈμ˜ "λΉ„μœ¨"이 μ•ˆμ „μ„ μ„ μ •ν•œλ‹€

RSA μ†Œμˆ˜μ˜ μƒμœ„ 절반이 μ•Œλ €μ§€λ©΄ κ·Έ μˆœκ°„ 끝이닀. p κ°€ λŒ€λž΅ N^0.5 일 λ•Œ Coppersmith κ°€ κ°λ‹Ήν•˜λŠ” λ―Έμ§€μˆ˜λŠ” N^0.25, 즉 1024λΉ„νŠΈ μ†Œμˆ˜λΌλ©΄ ν•˜μœ„ 512λΉ„νŠΈκΉŒμ§€ λͺ°λΌλ„ λ³΅κ΅¬λœλ‹€. 이 λ¬Έμ œλŠ” 280λΉ„νŠΈλ§Œ μ§€μ› μœΌλ‹ˆ μ• μ΄ˆμ— 절반 이상 μ—¬μœ κ°€ μžˆμ—ˆλ‹€.

λΆ€λΆ„ λ…ΈμΆœμ€ λΆ€λΆ„ ν”Όν•΄κ°€ μ•„λ‹ˆλ‹€

ν‚€μ˜ μΌλΆ€λ§Œ μƒˆλŠ” μ‚¬κ³ λŠ” ν”ν•˜λ‹€. λ‘œκ·Έμ— 찍힌 잘린 κ°’, λ°±μ—…μ—μ„œ 볡ꡬ된 쑰각, μ‚¬μ΄λ“œμ±„λ„λ‘œ 얻은 μƒμœ„ λΉ„νŠΈ. λŒ€μΉ­ν‚€ κ°κ°μœΌλ‘œλŠ” "λͺ‡ λΉ„νŠΈ μƒœμ„ 뿐"μ΄μ§€λ§Œ RSA μ†Œμˆ˜μ—μ„œλŠ” λ‹€λ₯΄λ‹€. 절반이 μƒˆλ©΄ μ „λΆ€ μƒŒ 것이고, μ ˆλ°˜λ³΄λ‹€ 쑰금 덜 μƒˆλ„ κ²©μžκ°€ λ‚˜λ¨Έμ§€λ₯Ό λ©”μš΄λ‹€. μ†Œμˆ˜μ˜ 일뢀라도 λ…ΈμΆœλœ ν‚€λŠ” 폐기 λŒ€μƒμœΌλ‘œ 봐야 ν•œλ‹€.

κ²©μžκ°€ 컀야 ν•œλ‹€λŠ” 감각을 μ˜μ‹¬ν•  것

Coppersmith μ„€λͺ…μ—λŠ” 늘 "μΆ©λΆ„νžˆ 큰 m"이 λΆ™μ§€λ§Œ, μ‹€μ œλ‘œ ν•„μš”ν•œ ν¬κΈ°λŠ” λ―Έμ§€μˆ˜κ°€ 이둠 ν•œκ³„μ— μ–Όλ§ˆλ‚˜ κ°€κΉŒμš΄μ§€λ‘œ μ •ν•΄μ§„λ‹€. 이번 문제처럼 μ—¬μœ κ°€ 크면 3차원 격자둜 λλ‚œλ‹€. m 을 ν‚€μš°λ©΄ κ³„μ‚°λŸ‰λ§Œ ν­μ¦ν•˜κ³  닡은 κ·ΈλŒ€λ‘œλ‹€.

ν‹€λ¦° κ²©μžλŠ” μ˜ˆμ™Έλ₯Ό μ•ˆ λ‚Έλ‹€

X μŠ€μΌ€μΌλ§ λˆ„λ½, 이동 닀항식 λˆ„λ½, μƒν•œ μ˜€λ… μ…‹ λ‹€ 싀행은 정상이고 결과만 λΉ„μ–΄ λ‚˜μ˜¨λ‹€. κ·Έλž˜μ„œ 격자 ν’€μ΄μ—μ„œλŠ” "μ™œ μ•ˆ λ˜μ§€"λ₯Ό 였래 λΆ™λ“€κΈ° 전에, 격자 쑰건을 숫자둜 λ¨Όμ € ν™•μΈν•˜λŠ” 게 λ‚«λ‹€. log2(det)/dim κ³Ό p^m 을 λ‚˜λž€νžˆ 찍어 보면 κ·Έ μžλ¦¬μ—μ„œ κ°ˆλ¦°λ‹€.

이 글이 도움이 λλ‚˜μš”?

Comments

λŒ“κΈ€

0개

λŒ“κΈ€μ„ 남기렀면 둜그인이 ν•„μš”ν•΄μš”. (넀이버 Β· ꡬ글 계정)

λŒ“κΈ€ λΆˆλŸ¬μ˜€λŠ” 쀑…

Related

κ΄€λ ¨ κΈ€

3개
[πŸ’  Platinum 2] ν”Œλž˜κ·Έλ₯Ό 반으둜 μͺΌκ°œ μ„Έ 번 μ•”ν˜Έν™”ν•˜λ©΄ β€” DreamHack fl and ag 풀이
blog

[πŸ’  Platinum 2] ν”Œλž˜κ·Έλ₯Ό 반으둜 μͺΌκ°œ μ„Έ 번 μ•”ν˜Έν™”ν•˜λ©΄ β€” DreamHack fl and ag 풀이

68λ°”μ΄νŠΈ ν”Œλž˜κ·Έλ₯Ό 34λ°”μ΄νŠΈμ”© flκ³Ό ag둜 자λ₯Έ λ’€ fl, ag, 그리고 원본 flagλ₯Ό 같은 RSA ν‚€λ‘œ μ „λΆ€ μ•”ν˜Έν™”ν•΄ κ³΅κ°œν•œ 문제. 평문 사이에 남은 일차 관계λ₯Ό λΉ„ ν•˜λ‚˜λ‘œ μ ‘μ–΄ Franklin-Reiter κ΄€λ ¨ λ©”μ‹œμ§€ 곡격을 νƒœμš°κ³ , κ±°κΈ°μ„œ λ‚˜μ˜¨ λΉ„λ₯Ό 2차원 격자 μΆ•μ•½μœΌλ‘œ λ‹€μ‹œ 두 쑰각으둜 νŽ΄μ„œ 0.9초 λ§Œμ— ν”Œλž˜κ·Έλ₯Ό λ³΅μ›ν•œ 과정을 μ •λ¦¬ν–ˆλ‹€.
#dreamhack#ctf#crypto+6
2026-08-21#dreamhack +6
[πŸ₯ˆ Silver 1] nonce 절반이 μƒˆλ©΄ ECDSA κ°œμΈν‚€λŠ” 1.8초 λ§Œμ— λ‚˜μ˜¨λ‹€ β€” DreamHack [LINE CTF 2021] babycrypto4 풀이
blog

[πŸ₯ˆ Silver 1] nonce 절반이 μƒˆλ©΄ ECDSA κ°œμΈν‚€λŠ” 1.8초 λ§Œμ— λ‚˜μ˜¨λ‹€ β€” DreamHack [LINE CTF 2021] babycrypto4 풀이

μ„œλͺ… 20κ°œμ™€ 각 μ„œλͺ…μ˜ nonce μƒμœ„ 16λΉ„νŠΈκ°€ μ£Όμ–΄μ§€λŠ” ECDSA 문제. μ‚¬μ΄λ“œμ±„λ„ μœ μΆœμ΄λΌλŠ” 말에 λ°˜μ‚¬μ μœΌλ‘œ 격자λ₯Ό λ– μ˜¬λ¦¬κΈ° μ‰½μ§€λ§Œ, 데이터λ₯Ό λ¨Όμ € 재 보면 nonce μžμ²΄κ°€ 32λΉ„νŠΈλΏμ΄λΌ λ―Έμ§€μˆ˜κ°€ 16λΉ„νŠΈλ°–μ— μ•ˆ λ‚¨λŠ”λ‹€. μ„œλͺ… ν•˜λ‚˜λ‘œ 65,536개의 κ°œμΈν‚€ 후보λ₯Ό λ§Œλ“€κ³  λ‚˜λ¨Έμ§€ 19개 μ„œλͺ…μœΌλ‘œ 걸러 λ‚΄λ©΄ 후보가 μ •ν™•νžˆ ν•˜λ‚˜λ‘œ 쀄어든닀. λ³΅κ΅¬ν•œ ν‚€λŠ” 직접 κ΅¬ν˜„ν•œ secp160r1 κ³Ό SageMath 두 κ³³μ—μ„œ κ³΅κ°œν‚€λ₯Ό λ‹€μ‹œ λ§Œλ“€μ–΄ μ„œλͺ… 20개λ₯Ό μ „λΆ€ κ²€μ¦ν–ˆκ³ , 정석인 Hidden Number Problem κ²©μžλ‘œλ„ 같은 값이 λ‚˜μ˜€λŠ”μ§€ LLL 둜 ꡐ차 ν™•μΈν–ˆλ‹€.
#dreamhack#ctf#crypto+7
2026-08-20#dreamhack +5
[πŸ’Ž Diamond 4] κΉƒλ°œ μ„Έ 쑰각을 닀항식 μ†Œκ±°λ‘œ λ‹€μ‹œ λ§žμΆ”κΈ° β€” DreamHack f, l and ag 풀이
blog

[πŸ’Ž Diamond 4] κΉƒλ°œ μ„Έ 쑰각을 닀항식 μ†Œκ±°λ‘œ λ‹€μ‹œ λ§žμΆ”κΈ° β€” DreamHack f, l and ag 풀이

flag λ₯Ό fΒ·lΒ·ag μ„Έ 쑰각으둜 μͺΌκ°œ 각각·전체λ₯Ό e=257 RSA 둜 μ•”ν˜Έν™”ν•œ 문제. Franklin–Reiter 와 GrΓΆbner κ°€ λ§‰νžˆλŠ” 지점을 짚고, β„€/N μœ„μ—μ„œ monic 없이 λ³€μˆ˜λ₯Ό μ†Œκ±°ν•˜λŠ” μ˜μ‚¬ λ‚˜λˆ—μ…ˆμœΌλ‘œ λΉ„μœ¨ f/ag, l/ag λ₯Ό 뽑은 λ’€ LLL 둜 평문을 λ³΅μ›ν•œλ‹€.
#dreamhack#ctf#crypto+8
2026-05-30#dreamhack +5