This is an old revision of the document!
In this lab we'll do some exercises related to OTP and stream ciphers.
Charlie manages to capture a last communication which turns out to be the most important, so it is crucial he decrypts it. However, this time Alice used the Vigenere cipher, with a key that Charlie knows has 7 characters.
The ciphertext is in the file attached. Try the method of multiplying probabilities as I explained in class and see if you can decrypt the ciphertext.
These are the known frequencies of the plaintext:
{'A': 0.07048643054277828, 'C': 0.01577161913523459, 'B': 0.012074517019319227, 'E': 0.13185372585096597, 'D': 0.043393514259429625, 'G': 0.01952621895124195, 'F': 0.023867295308187673, 'I': 0.06153403863845446, 'H': 0.08655128794848206, 'K': 0.007566697332106716, 'J': 0.0017594296228150873, 'M': 0.029657313707451703, 'L': 0.04609015639374425, 'O': 0.07679967801287949, 'N': 0.060217341306347746, 'Q': 0.0006382244710211592, 'P': 0.014357175712971482, 'S': 0.05892939282428703, 'R': 0.05765294388224471, 'U': 0.02749540018399264, 'T': 0.09984475620975161, 'W': 0.01892824287028519, 'V': 0.011148804047838086, 'Y': 0.023045078196872126, 'X': 0.0005289788408463661, 'Z': 0.00028173873045078196}
In class we explained that the one time pad is malleable (i.e. we can easily change the encrypted plaintext by simply modifying the ciphertext). Let’s see a concrete example. Suppose you are told that the one time pad encryption of the message “attack at dawn” is 09e1c5f70a65ac51626bc3d25f17 (the plaintext letters are encoded as 8-bit ASCII and the given ciphertext is written in hex). What would be the one time pad encryption of the message “attack at dusk” under the same OTP key?
In this exercise we'll try to break a Linear Congruential Generator, that may be used to generate “poor” random numbers. We implemented such weak RNG to generate a sequence of bytes and then encrypted a plaintext message. The resulting ciphertext in hexadecimal is this:
a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8
You know that the LCG uses the following formula to produce each byte:
s_next = a * s_prev + b mod p
where both s_prev and s_next are byte values (between 0 and 255) and p is 257.
You also know that the first 16 letters of the plaintext are “Let all creation” and that the ciphertext was generated by xor-ing the string generated by the LCG with the plaintext.
Can you break the LCG and predict the RNG stream so that in the end you find the entire plaintext ?
You may use this starting code:
import sys import random import string import operator #Parameters for weak LC RNG class WeakRNG: "Simple class for weak RNG" def __init__(self): self.rstate = 0 self.maxn = 255 self.a = 0 #Set this to correct value self.b = 0 #Set this to correct value self.p = 257 def init_state(self): "Initialise rstate" self.rstate = 0 #Set this to some value self.update_state() def update_state(self): "Update state" self.rstate = (self.a * self.rstate + self.b) % self.p def get_prg_byte(self): "Return a new PRG byte and update PRG state" b = self.rstate & 0xFF self.update_state() return b def strxor(a, b): # xor two strings (trims the longer input) return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)]) def hexxor(a, b): # xor two hex strings (trims the longer input) ha = a.decode('hex') hb = b.decode('hex') return "".join([chr(ord(x) ^ ord(y)).encode('hex') for (x, y) in zip(ha, hb)]) def main(): #Initialise weak rng wr = WeakRNG() wr.init_state() #Print ciphertext CH = 'a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8' print "Full ciphertext in hexa: " + CH #Print known plaintext pknown = 'Let all creation' nb = len(pknown) print "Known plaintext: " + pknown pkh = pknown.encode('hex') print "Plaintext in hexa: " + pkh #Obtain first nb bytes of RNG gh = hexxor(pkh, CH[0:nb*2]) print gh gbytes = [] for i in range(nb): gbytes.append(ord(gh[2*i:2*i+2].decode('hex'))) print "Bytes of RNG: " print gbytes #Break the LCG here: #1. find a and b #2. predict/generate rest of RNG bytes #3. decrypt plaintext # Print full plaintext p = '' print "Full plaintext is: " + p if __name__ == "__main__": main()