#!/bin/env python

import random
from math import gcd

# the modulus for the lcg prng
n = 256
# the header of the pdf v1.5 file
header = [ 0x25, 0x50, 0x44, 0x46, 0x2d, 0x31, 0x2e ]

def get_factors(ct:bytes, n:int=256) -> (int, int):
    ''' find a and b for n and ct '''
    # first generate a list of all numbers without common divisor with 256
    nogcds = [ x for x in range(1, n) if gcd(x, n) == 1 ]
    for b in range(1, n):
        for a in nogcds:
            for i, enc in enumerate(header):
                candidate = (a * enc + b) % n
                if candidate != ct[i]: break
                if(i+1 == len(header)):
                  return (a,b)
    print('[-] No solution found.')
    return (0,0)

def generate_lookuptable(a:int, b:int) -> dict[int,int]:
    ''' generate a lookuptable for the translation of the enc file'''
    out = {}
    for i in range(0, 256):
        lt = (i * a + b) % 256
        out[lt] = i
    return out

def decrypt(ct:bytes, lut:dict) -> bytes:
    ''' decrypt the file using the lookup table '''
    res = b''
    for byte in ct:
        dec = lut[byte]
        res += bytes([dec])
    return res

if __name__ == '__main__':
    ct = open('encrypted.bin', 'rb').read()
    a, b = get_factors(ct)
    print(f'[+] Factors solved: a={a}, b={b}')
    lut = generate_lookuptable(a, b)
    print(f'[+] Lookuptable generated: \nlookup = {lut}')
    dec = decrypt(ct,lut)
    print(f'[+] Encrypted file decrypted')
    with open('letter.pdf', 'wb') as f:
      f.write(dec)
