Back to writeups
FlagYardcryptoMediumsolved

Convergent Cipher

12 Jul 2026 · by 0xIDA

Solvers

Convergent Cipher

Author: 0xIDA
Platform: FlagYard · Category: crypto · Difficulty: Medium

Solvers

Summary

Custom 6-byte block cipher: each 3-byte half is XORed with a key half, inverted in GF(P) with P = 2^24+43, then XORed with SHA256(key)[:6]. Two chosen plaintexts cancel the final keystream so each 3-byte key half is recovered by a 2^24 search; pairs are filtered with the full encrypt oracle.

Challenge

Server (TCP): 2 plaintext→ciphertext queries under a random 6-byte key, then guess the key to get the flag.

P = 2**24 + 43
def sub(x):  # 3 bytes
    return (pow(int.from_bytes(x,'big'), -1, P) % 2**24).to_bytes(3,'big')

def encrypt(pt, key):  # |pt|=|key|=6
    k0, k1 = key[:3], key[3:]
    k2 = hashlib.sha256(key).digest()[:6]
    v = sub(xor(pt[:3], k0)) + sub(xor(pt[3:], k1))
    return xor(v, k2)

Solution

Differential that kills k2

ct1 XOR ct2 = (sub(pt1L XOR k0) || sub(pt1R XOR k1))
            XOR (sub(pt2L XOR k0) || sub(pt2R XOR k1))

So each half satisfies an independent relation:

sub(a XOR k) XOR sub(b XOR k) = d

with known a,b,d and k ∈ [0, 2^24).

Brute force + filter

  1. Precompute modular inverses for all nonzero 24-bit inputs (~20s, 64MB table).
  2. Enumerate all k0 matching the left differential (~2–6 collisions).
  3. Enumerate all k1 matching the right differential (~2–4 collisions).
  4. For each pair form key = k0||k1 and keep the unique key that re-encrypts both plaintexts (verifies k2 = SHA256(key)[:6]).

Chosen plaintexts used live: 00*6 and 01*6.

Live run

Plaintext: 000000000000 → Ciphertext: efd8f54db21c
Plaintext: 010101010101 → Ciphertext: 8516cc5d1b76
Recovered key: e533b40ed8f5
FlagY{m33t_1n_th3_m1ddl3_0r_d1ff3r3n714l?}

Flag

FlagY{m33t_1n_th3_m1ddl3_0r_d1ff3r3n714l?}

Tools

  • Python 3 (socket client + inv table)
  • Challenge source: Convergent_Cipher_server.py

Key takeaways

  • Final whitening with a key-derived mask cancels under ciphertext XOR → pure differential on the S-box layer.
  • 24-bit halves are small enough for exhaustive search; multi-collisions on the differential need the full encrypt check (SHA256-derived k2).
  • Name/flag hint “meet in the middle or differential” matches this structure.