Human-server -- Union CTF 2021
Human Server – Union CTF 2021
Points: 100 (N+ solves)
Given: human_server.py
TLDR
- 
    
We are given an implementation of a Diffie-Hellman key-exchange over Elliptic Curves with a weird
XORwhen computing the key. - 
    
Exploit this extra information to try to “get rid of the discrete log”.
 - 
    
You might have heard of this recently Cryptography Dispatches: The Most Backdoor-Looking Bug I’ve Ever Seen
 
Program
The relevant part of the code is the one below.
CURVE = secp256k1
ORDER = CURVE.q
G = CURVE.G
class EllipticCurveKeyExchange():
    def __init__(self):
        self.private = random.randint(0,ORDER)
        self.public = self.get_public_key()
        self.recieved = None
        self.nonce = None
        self.key = None
    def get_public_key(self):
        A = G * self.private
        return A
    def send_public(self):
        return print(json.dumps({"Px" : self.public.x, "Py" : self.public.y}))
    def receive_public(self, data):
        """
        Remember to include the nonce for ultra-secure key exchange!
        """
        Px = int(data["Px"])
        Py = int(data["Py"])
        self.recieved = Point(Px, Py, curve=secp256k1)
        self.nonce = int(data['nonce'])
    def get_shared_secret(self):
        """
        Generates the ultra secure secret with added nonce randomness
        """
        assert self.nonce.bit_length() > 64
        self.key = (self.recieved * self.private).x ^ self.nonce
    def check_fingerprint(self, h2: str):
        """
        If this is failing, remember that you must send the SAME
        nonce to both Alice and Bob for the shared secret to match
        """
        h1 = hashlib.sha256(long_to_bytes(self.key)).hexdigest()
        return h1 == h2
    def send_fingerprint(self):
        return hashlib.sha256(long_to_bytes(self.key)).hexdigest()
Attack
Looking at the weird XOR in the computation of get_shared_secret reminded us of the recent post stated above Cryptography Dispatches: The Most Backdoor-Looking Bug I’ve Ever Seen. Looking there the attack is quite simple
- Alice generates \( a \), computes \( g^a \), and sends \( g^a \) to Bob;
 - Eve intercepts this message, generates \( t \) and a nonce \( n \), and sends \( (g^t, n) \) to Bob;
 - Bob receives this message, generates \( b \), computes \( g^b \), and sends \( g^b \) to Alice;
 - Bob’s shared key is now \( (g^t)^b \oplus n \);
 - Eve intercepts the message by Bob and sends to Alice \( (g^t, (g^a)^t \oplus (g^b)^t \oplus n) \)instead;
 - Alice receives this message and computes the shared key as
 
In our case, we have this same problem but over EC and the computation of the shared key only uses the x component
self.key = (self.recieved * self.private).x ^ self.nonce
The attack is thus as follows
- Alice generates \( a \), computes \( pkey_A = G\ast a \), and sends \( G\ast a \) to Bob;
 - Eve intercepts this message, generates \( t \) and a nonce \( n \), and sends \( (G\ast t, n) \) to Bob;
 - Bob receives this message, generates \( b \), computes \( pkey_B = G\ast b \), and sends \( G\ast b \) to Alice;
 - Bob’s shared key is now \( ((G\ast t)\ast b).x \oplus n \);
 - Eve intercepts the message by Bob and sends to Alice \( (G\ast t, (pkey_A\ast t).x \oplus (pkey_B\ast t).x \oplus n) \) instead;
 - Alice receives this message and computes the shared key as
 
In our exploit we used the nonce \( n = t \). And the flag is :man_facepalming:
b'union{https://buttondown.email/cryptography-dispatches/archive/cryptography-dispatches-the-most-backdoor-looking/}\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
Resources
The code for the solution can be downloaded here.
