ElGamal (Subgroup and Quadratic-Residue Leakage)
- •
What it is
- •
An ElGamal mask confined to the quadratic-residue subgroup preserves whether the encoded plaintext is a square. In Bit by Bit, that one-bit subgroup distinction is exactly the flag bit.
- •
- •
When to apply
- •
Inspect multiplicative encryption when messages can occupy different cosets of the mask’s subgroup. Fresh keys and randomness do not erase a property shared by every mask.
- •
- •
Symbols and assumptions
- •
is an odd prime; is 1 for nonzero squares and q−1 (meaning −1) for nonsquares. Public key ; shared mask ; ciphertext component .
- •
- •
Math — step by step
- •
- In the supplied parameters, with r prime and , . Thus g generates the order-r square subgroup. Every and is a square, so .
- •
- The code shifts
padding << (1 + bit), wherepadding = pow(256, e, q). As a congruence, . The already-reduced integer padding need not literally equal .
- The code shifts
- •
- Multiplicativity gives . Here , so 2 is a nonsquare and . The even exponent 8e disappears, leaving .
- •
- Therefore character −1 means b=0, and character +1 means b=1. Read ciphertexts in source order: the loop extracts least-significant bits first. Reconstruct , then convert that integer back to big-endian bytes. Public h and c1 are unnecessary for this particular extraction.
- •
- •
- •
Checks and pitfalls
- •
Check primality, nonzero ciphertexts, and the actual subgroup before applying the rule. Character 0 means a zero residue and is not either encoded case.
- •
This c2-only rule is specific to a square-valued mask. Proper subgroup message encoding prevents this particular message-coset leak; it does not by itself supply integrity or prove the whole implementation secure.
- •
Offline checks confirmed both supplied primes, q mod8=3, g^r=1 and the toy calculations. The attached text did not include output.txt, so ciphertext-to-flag recovery was not replayed.
- •
- •
- •
Python
- •
def recover(c2_values, q): bits = [] for c2 in c2_values: chi = pow(c2, (q - 1) // 2, q) if chi not in (1, q - 1): raise ValueError('zero or invalid residue') bits.append(int(chi == 1)) value = sum(bit << i for i, bit in enumerate(bits)) return value.to_bytes((value.bit_length() + 7) // 8, 'big')
- •