SECCON Beginners CTF 2022 - OmniRSA
TL;DR
- Multi-Prime RSAの問題でを法としたの下位bitとをで割ったものをくれる
- が比較的小さいことから、となるを総当りし、で法をとると、をで割った余りが与えられているので、未知数がだけになる
- の下位bitは十分な量分かっているのでCoppersmith's Attackで求められる
- であるから、をで割った際の法も小さく、これを総当りすれば求めたとその時のを利用してが根である2次方程式が現れるのでこれを解いて素因数分解する
Prerequisite
- Coppersmith's Attack
Writeup
次のようなスクリプトとその実行結果が与えられる
from Crypto.Util.number import *
from flag import flag
p, q, r = getPrime(512), getPrime(256), getPrime(256)
n = p * q * r
phi = (p - 1) * (q - 1) * (r - 1)
e = 2003
d = inverse(e, phi)
flag = bytes_to_long(flag.encode())
cipher = pow(flag, e, n)
s = d % ((q - 1)*(r - 1)) & (2**470 - 1)
assert q < r
print("rq =", r % q)
print("e =", e)
print("n =", n)
print("s =", s)
print("cipher =", cipher)
3つの素数でMulti-Prime RSAによる暗号化が行われており、それに加えてヒントパラメータが次のように与えられている。
rq
: 、以下ではとするs
: の下位470bit
が成り立つので当然が成り立つ。を法として考えているのでを使って次のような関係がある。
ここでは、となるような高々42bit程度の数である。
ある整数を用いて合同から等号に直すと次のようになる。
については、がだいたい512bitで、もだいたい512bitなのでとしてよい。よって、は2000程度の総当りで当てれば良い。
更に、 であり、どちらもgetPrime(256)
で生成されているのでと表した時には小さい整数であることが期待できる。よって、これは後に必要になったら総当たりすればいいので実質はの関数で表す事ができる。加えて、で法をとるとは消えてくれる。
これを踏まえて両辺で法をとると次のようになり、を総当りするなら、次の式は未知数がだけになる。
はの約数なのでCoppersmith's Attackでを求められそうな予感がするし、実際試してみると求められる。を総当りしながら求めたのでついでにも求まっている。
元々考えていたのは次のような式であり、Coppersmith's Attackによってとが判明した。。
既に上で説明したようにであるから、を総当りしながらこれをに代入すると未知数はだけであり、しかも整数であるから、2次方程式の整数解を求めることに帰着される。よって、を総当りしながらこれを解けばが求まり、ついでにも求まる。加えてなのでも求まってを完全に素因数分解出来たから復号出来る。
Code
from tqdm import tqdm
from Crypto.Util.number import long_to_bytes
rq = 7062868051777431792068714233088346458853439302461253671126410604645566438638
e = 2003
n = 140735937315721299582012271948983606040515856203095488910447576031270423278798287969947290908107499639255710908946669335985101959587493331281108201956459032271521083896344745259700651329459617119839995200673938478129274453144336015573208490094867570399501781784015670585043084941769317893797657324242253119873
s = 1227151974351032983332456714998776453509045403806082930374928568863822330849014696701894272422348965090027592677317646472514367175350102138331
cipher = 82412668756220041769979914934789463246015810009718254908303314153112258034728623481105815482207815918342082933427247924956647810307951148199551543392938344763435508464036683592604350121356524208096280681304955556292679275244357522750630140768411103240567076573094418811001539712472534616918635076601402584666
# PR.<d> = PolynomialRing(Zmod(n))
# candidates = []
# for k in tqdm(range(100, e)):
# f = e * (d*2**470 + s) - 1 + k*rq - k
# f = f.monic()
# roots = f.small_roots(X=2**43, beta=0.2, epsilon=1/20)
# for x in roots:
# candidates.append((k, x))
# print(candidates)
k, _d = (1576, 3248825676044)
d = _d*2**470+s
PR.<q> = PolynomialRing(ZZ)
for c in range(1, 11):
f = k*(q*(q*c+rq) - q - (q*c+rq) + 1) - (e*d - 1)
roots = f.roots()
for x, _ in roots:
if n % x == 0:
_q = x
r = _q*c + rq
p = n // (_q*r)
phi = (p-1) * (_q-1) * (r-1)
d = inverse_mod(e, phi)
m = power_mod(cipher, d, n)
print(long_to_bytes(m))
Flag
ctf4b{GoodWork!!!YouAreTrulyOmniscientAndOmnipotent!!!}
Thanks for reading! Read other posts?