- Source: SECCON 14 Quals
- Author: chocorusk
少し変わった形式のRSA問題で、ランダムなpが与えられ、RSA.construct()が失敗するようなqを32回連続で見つけることが出来ればflagが得られる。
chal.py
#!/usr/bin/env python3
from Crypto.PublicKey import RSA
from Crypto.Util.number import getPrime, isPrime
with open("flag.txt", "r") as f:
FLAG = f.read()
for _ in range(32):
p = getPrime(1024)
print("p =", p)
q = int(input("q: "))
assert p != q
assert q.bit_length() >= 1024
assert isPrime(q)
n = p * q
e = getPrime(64)
d = pow(e, -1, (p - 1) * (q - 1))
try:
cipher = RSA.construct((n, e, d))
except:
print("error!")
continue
print("key setup successful")
exit()
print(FLAG)
RSA.construct()のコードを見ると、RSAとして不適当な数が与えられた時にValueErrorを返していることが分かる。
RSA.construct()
def construct(rsa_components, consistency_check=True):
r"""Construct an RSA key from a tuple of valid RSA components.
The modulus **n** must be the product of two primes.
The public exponent **e** must be odd and larger than 1.
In case of a private key, the following equations must apply:
.. math::
\begin{align}
p*q &= n \\
e*d &\equiv 1 ( \text{mod lcm} [(p-1)(q-1)]) \\
p*u &\equiv 1 ( \text{mod } q)
\end{align}
Args:
rsa_components (tuple):
A tuple of integers, with at least 2 and no
more than 6 items. The items come in the following order:
1. RSA modulus *n*.
2. Public exponent *e*.
3. Private exponent *d*.
Only required if the key is private.
4. First factor of *n* (*p*).
Optional, but the other factor *q* must also be present.
5. Second factor of *n* (*q*). Optional.
6. CRT coefficient *q*, that is :math:`p^{-1} \text{mod }q`. Optional.
Keyword Args:
consistency_check (boolean):
If ``True``, the library will verify that the provided components
fulfil the main RSA properties.
Raises:
ValueError: when the key being imported fails the most basic RSA validity checks.
Returns: An RSA key object (:class:`RsaKey`).
"""
class InputComps(object):
pass
input_comps = InputComps()
for (comp, value) in zip(('n', 'e', 'd', 'p', 'q', 'u'), rsa_components):
setattr(input_comps, comp, Integer(value))
n = input_comps.n
e = input_comps.e
if not hasattr(input_comps, 'd'):
key = RsaKey(n=n, e=e)
else:
d = input_comps.d
if hasattr(input_comps, 'q'):
p = input_comps.p
q = input_comps.q
else:
# Compute factors p and q from the private exponent d.
# We assume that n has no more than two factors.
# See 8.2.2(i) in Handbook of Applied Cryptography.
ktot = d * e - 1
# The quantity d*e-1 is a multiple of phi(n), even,
# and can be represented as t*2^s.
t = ktot
while t % 2 == 0:
t //= 2
# Cycle through all multiplicative inverses in Zn.
# The algorithm is non-deterministic, but there is a 50% chance
# any candidate a leads to successful factoring.
# See "Digitalized Signatures and Public Key Functions as Intractable
# as Factorization", M. Rabin, 1979
spotted = False
a = Integer(2)
while not spotted and a < 100:
k = Integer(t)
# Cycle through all values a^{t*2^i}=a^k
while k < ktot:
cand = pow(a, k, n)
# Check if a^k is a non-trivial root of unity (mod n)
if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1:
# We have found a number such that (cand-1)(cand+1)=0 (mod n).
# Either of the terms divides n.
p = Integer(n).gcd(cand + 1)
spotted = True
break
k *= 2
# This value was not any good... let's try another!
a += 2
if not spotted:
raise ValueError("Unable to compute factors p and q from exponent d.")
# Found !
assert ((n % p) == 0)
q = n // p
if hasattr(input_comps, 'u'):
u = input_comps.u
else:
u = p.inverse(q)
# Build key object
key = RsaKey(n=n, e=e, d=d, p=p, q=q, u=u)
# Verify consistency of the key
if consistency_check:
# Modulus and public exponent must be coprime
if e <= 1 or e >= n:
raise ValueError("Invalid RSA public exponent")
if Integer(n).gcd(e) != 1:
raise ValueError("RSA public exponent is not coprime to modulus")
# For RSA, modulus must be odd
if not n & 1:
raise ValueError("RSA modulus is not odd")
if key.has_private():
# Modulus and private exponent must be coprime
if d <= 1 or d >= n:
raise ValueError("Invalid RSA private exponent")
if Integer(n).gcd(d) != 1:
raise ValueError("RSA private exponent is not coprime to modulus")
# Modulus must be product of 2 primes
if p * q != n:
raise ValueError("RSA factors do not match modulus")
if test_probable_prime(p) == COMPOSITE:
raise ValueError("RSA factor p is composite")
if test_probable_prime(q) == COMPOSITE:
raise ValueError("RSA factor q is composite")
# See Carmichael theorem
phi = (p - 1) * (q - 1)
lcm = phi // (p - 1).gcd(q - 1)
if (e * d % int(lcm)) != 1:
raise ValueError("Invalid RSA condition")
if hasattr(key, 'u'):
# CRT coefficient
if u <= 1 or u >= q:
raise ValueError("Invalid RSA component u")
if (p * u % q) != 1:
raise ValueError("Invalid RSA component u with p")
return key
問題ではRSA.construct()にn,e,dを渡しているので、dとnからp,qを復元することになる。これはRabinのアルゴリズムというらしい。
さて、ここでp,qから求められるuに着目する。$u = q\ (mod\ p)$であり、$u≦1$の時ValueErrorが発生する。
つまり、$q=kp+1$となる値を渡せば良さそうに見える。
if hasattr(input_comps, 'u'):
u = input_comps.u
else:
u = p.inverse(q)
if hasattr(key, 'u'):
# CRT coefficient
if u <= 1 or u >= q:
raise ValueError("Invalid RSA component u")
if (p * u % q) != 1:
raise ValueError("Invalid RSA component u with p")
しかし、コード内のコメントの通りRavinのアルゴリズムはnon-deterministicであり、p,qのどちらがどちらになるかは分からない。そこで、ローカルでRSA.construct()を試して落ちるようなkを探索し、そこから求めたqを送信する。
以上より、最終的なsolverはこうなる。
from pwn import *
from Crypto.Util.number import isPrime, getPrime
from Crypto.PublicKey import RSA
io = remote("yukari.seccon.games", 15809)
def solve():
p = int(io.recvline()[4:].strip())
for k in range(1, 1000000):
q = k*p + 1
try:
assert p != q
assert q.bit_length() >= 1024
assert isPrime(q)
except:
continue
n = p * q
e = getPrime(64)
d = pow(e, -1, (p - 1) * (q - 1))
try:
RSA.construct((n, e, d))
except:
break
io.sendline(str(q))
io.recvline()
for i in range(32):
print(f"[*] Round {i+1}/32")
solve()
print(io.recvall(timeout=1))
必ず成功する訳ではないが、何度か試すとflagが得られた。
SECCON{9cb27d297988cdae22deca33d5e54a6955d6f95a010c6aec737ff7509f4ac715}