Skip to main content Link Search Menu Expand Document (external link)

Generous

Category: crypto

Solves: 49

Points: 163

Overview

We are given a 1-bit oracle implemented as follows:

def gen_keypair():
	p, q = getPrime(512), getPrime(512)
	n = (p**2) * q
	while True:
		g = randrange(2, n)
		if pow(g, p-1, p**2) != 1:
			break
	h = pow(g, n, n)
	return (n, g, h), (g, p, q)

def encrypt(pubkey, m):
	n, g, h = pubkey
	r = randrange(1, n)
	c = pow(g, m, n) * pow(h, r, n) % n
	return c

def decrypt(privkey, c):
	g, p, q = privkey
	a = (pow(c, p-1, p**2) - 1) // p
	b = (pow(g, p-1, p**2) - 1) // p
	m = a * inverse(b, p) % p
	return m

def oracle(privkey, c):
	m = decrypt(privkey, c)
	return m % 2

pub, priv = gen_keypair()
n, g, h = pub
print(f"Public Key:\n{n = }\n{g = }\n{h = }")
print(f"Encrypted Flag: {encrypt(pub, bytes_to_long(flag))}")
while True:
	inp = int(input("Enter ciphertext> "))
	print(f"Oracle result: {oracle(priv, inp)}")

LSB oracle attack

The situation is typical of a LSB oracle.

See an example here: https://crypto.stackexchange.com/questions/11053/rsa-least-significant-bit-oracle-attack

In this case, the technique is well-known and relies on the following simple property: if \(0 < x < p\) where \(p\) is an odd number, then \(2x \mod p\) is odd if and only if \(x < p/2\).

Let’s see how it can be applied here: we can observe that decryption computes powers modulo p².

This is a group morphism:

\[\mathbb Z / p \mathbb Z \to (\mathbb Z / p^2 \mathbb Z)^{\times p-1}\] \[a \to 1 + ap\] \[\frac{x - 1} {p} \leftarrow x\]

So using squares modulo \(p^2\) is equivalent to computing the double modulo \(p\) through decrypt.

The following function is also a group morphism:

\[D: x \in (\mathbb Z / p^2 \mathbb Z)^\times \to \frac{x^{p-1} - 1} {p} \in \mathbb Z / p \mathbb Z\]

So we have the following identities:

decrypt(ct) = D(ct) / D(g) = flag
decrypt(ct**2) = D(ct**2) / D(g) = (2 * flag) % p
...
decrypt(ct**(2**k)) = D(ct**(2**k)) / D(g) = (2**k * flag) % p

The recovery through a LSB oracle attack can be formulated mathematically like this: write the fraction with binary digits

\[\frac k p = 0.b_0 b_1 b_2 ... b_i ...\]

Then \(b_i = 0\) means that \((2^i k) \mod p < p / 2\).

Solution

The above remarks say that bit \(b_i\) is exactly what the oracle will return for input \(\text{pow}(ct, 2^{i + 1})\).

We are now ready to perform the attack: send iterated squared of the encrypted flag to the oracle, which gives the binary digits of flag / p.

Once we have 1024 bits (twice the size of \(p\)), recovery of flag and \(p\) is instantaneous via continued fraction expansion.

As a bonus, you obtain the factorization of n, which is supposed to be secret.

from telnetlib import Telnet
from sage.all import Integer, continued_fraction, QQ
from tqdm import tqdm

c = Telnet("be.ax", 31244)
while True:
    line = c.read_until(b"\n").decode()
    if "n =" in line:
        n = int(line.strip().split()[-1])
    elif "Flag:" in line:
        ct = int(line.strip().split()[-1])
        break
c.read_until(b"ciphertext> ")
print("n =", n)
print("ct =", ct)

def oracle(x: int):
    c.write(str(x).encode() + b"\n")
    line = c.read_until(b"\n").decode()
    if " 1" in line:
        return 1
    else:
        return 0

x = ct
bits = []
for k in tqdm(range(1024)):
    x = (x*x)%n
    bit = oracle(x)
    bits.append(bit)
assert len(bits) == 1024

num = sum(b * 2**(1023-i) for i, b in enumerate(bits))
cf = continued_fraction(QQ(num) / QQ(2**1024))
for f in cf.convergents():
    a = int(f.numerator())
    b = int(f.denominator())
    if Integer(b).is_prime() and b.bit_length() == 512:
        print("p =", b)
        p = b
        print("msg =", a.to_bytes(50, "big"))
assert n % (p*p) == 0
print("factors of n")
print(p)
print(p)
print(n // (p*p))

Sample output:

n = 382117011456038221410328128694247757137353385107547801442167291131692445852014961496469548870495011643760778373587709173975160132235045978364230956321392904471020451180820926376747259091050543730341148327632684981188263791129579200368138856381929113409794029012000320474376470150687073962615837765648041859285189924224283209636208026411187069739227898055436888993780781402409999497594918931116218946800189439759957832363606985734064197275299815137081256276457029
ct = 271753346662223698656541527251751086596940663599841631819308477578779722575767529958891723115984896269515872561634903533906633582535866141422430833815043550342342875281297721342898166627108970884433029990644228835812182108300055219312582374571224646085836164020700223063875190530313800522997657148612308347775168295194074555835808881679815362111109422376032772976194513560325870259003895119412599834545250788898041644373126535831044731963613182511717154097515770

p = 7258573487882539665926275836350292713078118035457370786331051440423991926454774476573264628112748782660143038376984025079745626790021928165265635381408613
msg = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00corctf{see?1_bit_is_very_generous_of_me}'
factors of n
7258573487882539665926275836350292713078118035457370786331051440423991926454774476573264628112748782660143038376984025079745626790021928165265635381408613
7258573487882539665926275836350292713078118035457370786331051440423991926454774476573264628112748782660143038376984025079745626790021928165265635381408613
7252601513123043187053932535843576998053006908527140935039474417071226993304660873821734957206089942267083251155543184940978473308477755352245953699902541