The source code of the server code was given.
It just generates an RSA key and sends us some information:
N, delta, gamma = gen_key()
m = int(FLAG.encode('hex'), 16)
c = powmod(m, 0x10001, N)
print introduction
print 'N = {}'.format(N)
print 'delta = {}'.format(delta)
print 'gamma = {}'.format(gamma)
print 'ciphertext = {}'.format(c)
Here is the function gen_key:
def gen_key():
t1 = randint(768, 928)
t2 = 1024 - t1
if t1 > t2:
t1, t2 = t2, t1
assert t1 < t2
p2 = pad(getrandbits(1024 - t2) << t2, 1024)
p0 = pad(getrandbits(t1), t1)
q2 = pad(getrandbits(1024 - t2) << t2, 1024)
q0 = pad(getrandbits(t1), t1)
r = pad(getrandbits(t2 - t1) << t1, t2)
p = next_prime((p2 ^ r ^ p0))
q = next_prime((q2 ^ r ^ q0))
N = p * q
return N, t2 - t1, p0 - q0
So, we are given N and (p0-q0). Knowing p and q share the same middle bits r,
we can deduce this relation:
Note:
gamma is different from (p-q) mod 2**t2 because of the calls to next_prime.
We need to brute force a small offset.
Here I chose the offset to have a maximum of 1000.
Next, we use this identity to get a square modulo power of two:
You can read about the problem of "square root modulo power of two" in section 2.3 of this document: LSBSRSA.pdf
I found a C++ implement for the more general problem "square root modulo N": Square-Root-Modulo-N
Then, I translated the part where it does power of two to python: power_of_two
def sqrt_mod_power_of_two(a, k):
if a % min(2**k, 8) == 1:
if k == 1:
return [1]
elif k == 2:
return [1, 3]
elif k == 3:
return [1, 3, 5, 7]
else:
# Small optimization for the case of a == 1.
if a == 1:
return [1, 2**(k-1) - 1, 2**(k-1) + 1, 2**k - 1]
else:
roots = []
for x in sqrt_mod_power_of_two(a, k-1):
i = 1 if ((x*x - a) >> (k - 1)) % 2 == 1 else 0
r = x + i*(1 << (k - 2))
roots.append(r)
roots.append(2**k - r)
return list(set(roots))
return []
The quantity N+((p+q)/2)**2 is not always congruent to 1 modulo 8.
But we are sure to get a nice problem in less than 10 tries.
Now, after solving the "square root modulo power of 2" problem, we have (p+q)/2 and (p-q)/2 mod 2**t2.
We take the sum and get the value of p mod 2**t2.
We are left with only t1 bits missing from p.
Knowing that t2 is at least 768 and t1 is at most 1024-768=256,
we can simply use Coppersmith's method (the function small_roots in SageMath):
def solve_problem(N, delta, gamma):
t2 = (delta + 1024)/2
t1 = 1024-t2
tt2 = 2**t2
F.<x> = PolynomialRing(Zmod(N), implementation='NTL')
for i in xrange(-512, 512):
diff = gamma//2 + i
d = (N+diff**2)%tt2
for r in sqrt_mod_power_of_two(d, t2):
p0 = r + diff
# Coppersmith
pol = x*tt2 + p0
for root in pol.monic().small_roots(X=2**t1, beta=0.5):
p = int(root)*tt2 + p0
q = N // p
if p<N and q<N and p*q == N:
return p, q
return 0, 0
When the problem fits the criteria for solving the "square root modulo power of two" part,
we get p and q in less than a minute.
The flag for this task was:
MeePwnCTF{It_is_the_time_to_move_to_Post_Quantum_Cryptography_DAGS}