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). We have also discussed how the CRC was a very bad idea in the design of WEP due to its linearity.
You are given the following ciphertext in hexadecimal:
021e0e061d1694c9
which you know it corresponds to the concatenation of the message “floare” with its CRC-16 (in hexa “E0AC”) obtained from this website: http://www.lammertbies.nl/comm/info/crc-calculation.html
If we need to modify the ciphertext so that a correct decryption outputs “albina” instead of “floare” and such that the CRC-16 calculation remains correct, what is the modification we need to perform?
Output the new ciphertext after the necessary modifications and show that it correctly leads to the plaintext “albina” and a correct computation of its CRC-16.
You might find this starting script useful:
import sys import random import string import operator 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(): #Plaintexts s1 = 'floare' s2 = 'albina' G = '' #To find #Obtain crc of s1 #See this site: #http://www.lammertbies.nl/comm/info/crc-calculation.html x1 = s1.encode('hex') x2 = s2.encode('hex') print "x1: " + x1 crc1 = 'E0AC' #CRC-16 #Compute delta (xor) of x1 and x2: xd = hexxor(x1, x2) print "xd: " + xd if __name__ == "__main__": main()
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 a string of consecutive bytes 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()