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=============```pythonimport json, hashlib
from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom Crypto.Util.number import getPrime, long_to_bytes
from fastecdsa.curve import secp256k1from fastecdsa.point import Point
CURVE = secp256k1ORDER = CURVE.qG = 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_NONCEalice_nonce = bob_pub['Px'] ^ alice_pub['Px'] ^ DUMMY_NONCEprint('{"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/}`