2 minutes
DDC 2024 Qualification - Affinity
We are given the following file:
The task description introduces the affine cipher
that works by encrypting cleartext x
to ciphertext y
in the following way: y = (ax + b) mod m
for some constants a
, b
and m
.
We can see this in the gen.py
script:
#!/usr/bin/env python
from math import gcd
import random
import binascii
# Encrypt the text using the affine cipher y = (ax + b) mod m
def affine_encrypt(text, a, b, m):
result = 0
for letter in text:
c = (a * letter + b) % m
result = (result << 8) + c
return hex(result)
# Generate keys for the affine cipher and encrypt the flag
if __name__ == '__main__':
with open('flag.txt', 'rb') as file:
plaintext = file.read().strip(b'\n')
m = 256
a = random.randint(1, m)
while gcd(a, m) != 1:
a = random.randint(1, m)
b = random.randint(0, m)
print(f'Key = {(a, b)}')
ciphertext = affine_encrypt(plaintext, a, b, m)
with open('output.txt', 'w') as file:
file.write(ciphertext)
We are also given the hex ciphertext in output.txt
:
0xe9e962ea422bdc1d39965e2b341de5dc260f5050c9b2260f960f016c0f50342b6c39dcf8
We can notice that the ciphertext starts with e9e962
which would map the the flag prefix of DDC
, so lets find constants a
and b
that satisfice this:
from math import gcd
m = 256
ciphertext = bytes.fromhex('e9e962')
crib = b'DDC'
for a in range(1, m+1):
if gcd(a, m) != 1:
continue
for b in range(0, m+1):
candidate = True
for y, x in zip(ciphertext, crib):
if (a * y + b) % m != x:
candidate = False
break
if candidate: print(f"Pair found: ({a}, {b})")
When we run this we get Pair found: (55, 53)
We can then decrypt the cipher in the following way:
m = 256
a = 55
b = 53
cipher = bytes.fromhex('e9e962ea422bdc1d39965e2b341de5dc260f5050c9b2260f960f016c0f50342b6c39dcf8')
result = 0
for c in cipher:
d = (a * c + b) % m
result = (result << 8) + d
print(hex(result))
Which gives us: 0x4444437b63727970746f6772617068795f6e656564735f6e6f6e6c696e6561726974797d
which from hex gives is DDC{cryptography_needs_nonlinearity}
.