Rating:

Highlevel details

=================

1. Using the `secp256k1` curve, the `EllipticCurveKeyExchange` class has a private key (```random.randint``` to get an integer in the order of the curve) and a public key (which is a known point `G` multiplied by the private key).

2. The `send_public` method simply outputs the public key's coordinates.

3. The `receive_public` method receives JSON data representing a point and then saves the point as the `recieved` member. It will also extract a nonce integer from the JSON and will keep the nonce as the `nonce` member.

4. The `get_shared_secret` method validates the nonce is at least 65-bit wide, and then calculates the key: `(received*private).x ^ nonce`.

5. The `send_fingerprint` method publishes SHA256 of the shared key.

6. The `check_fingerprint` gets a fingerprint and validates that it's equal to own's key SHA256.

7. Finally, the `encrypt_flag` method does an AES-CBC cipher. It uses random 16 bytes as the IV (from `os.urandom`) and the first 16 bytes from the SHA1 digest of the shared key as the AES key.

Obviously, the goal is abusing the `nonce` and the `received` point to get a known key.

The shared secret is calculated as such:

```

shared_secret = (received * private).x ^ nonce

```

So, if we set `received = G`, we effectively set the shared secret to `public.x ^ nonce` (due to the multiplication of the point with `private`).

We can control difference `nonce` values for Alice and Bob to coordinate the secret.

Sending to Bob

==============

1. At this point we do not know Bob's public key.

2. Set `recevied=G` and `Nonce` to be `2**65` (just to be over 64 bits).

3. This makes the shared secret: `bob.public.x ^ 2**65`.

Sending to Alice

================

1. At this point we know everyone's public keys.

2. Set `received=G`, so now we must satisfy: `alice.public.x ^ n = bob.public.x ^ 2**65`, where `n` is the Nonce sent to Alice from Bob.

3. The solution is `n = bob.public.x ^ alice.public.x ^ 2**65`.

Solution code

=============

```python

import json, hashlib

from Crypto.Cipher import AES

from Crypto.Util.Padding import pad, unpad

from Crypto.Util.number import getPrime, long_to_bytes

from fastecdsa.curve import secp256k1

from fastecdsa.point import Point

CURVE = secp256k1

ORDER = CURVE.q

G = CURVE.G

DUMMY_NONCE = 2**65

alice_pub = json.loads(input('alice.pub?'))

print('{"Px": %d, "Py": %d, "nonce": %d}' % (G.x, G.y, DUMMY_NONCE))

bob_pub = json.loads(input('bob.pub?'))

shared_secret = bob_pub['Px'] ^ DUMMY_NONCE

alice_nonce = bob_pub['Px'] ^ alice_pub['Px'] ^ DUMMY_NONCE

print('{"Px": %d, "Py": %d, "nonce": %d}' % (G.x, G.y, alice_nonce))

aes_key = hashlib.sha1(long_to_bytes(shared_secret)).digest()[:16]

aes_data = json.loads(input('aes.data?'))

aes_iv = bytes.fromhex(aes_data['iv'])

cipher = AES.new(aes_key, AES.MODE_CBC, aes_iv)

ciphertext = bytes.fromhex(aes_data['encrypted_flag'])

print(cipher.decrypt(ciphertext))

```

I ended up with the flag: `union{https://buttondown.email/cryptography-dispatches/archive/cryptography-dispatches-the-most-backdoor-looking/}`

Original writeup (https://thegoonies.github.io/2021/02/21/unionctf-2021-human-server/).