This shows you the differences between two versions of the page.
sasc:laboratoare:03 [2016/03/08 17:46] marios.choudary |
sasc:laboratoare:03 [2017/03/07 15:32] (current) dan.dragan |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Lab 03 ===== | + | ===== Lab 03 - PRGs ===== |
==== Exercise 1 (4p) ==== | ==== Exercise 1 (4p) ==== | ||
- | Advantage. The purpose of this problem is to clarify the concept of advantage. Consider the following two experiments $\mathsf{EXP}(0)$ and $\mathsf{EXP}(1)$: | + | In this exercise we'll try to break a Linear Congruential Generator, that may be used to generate "poor" random numbers. |
- | * In $\mathsf{EXP}(0)$ the challenger flips a fair coin (probability $1/2$ for HEADS and $1/2$ for TAILS) and sends the result to the adversary $\mathsf{A}$. | + | We implemented such weak RNG to generate a sequence of bytes and then encrypted a plaintext message. |
- | * In $\mathsf{EXP}(1)$ the challenger always sends TAILS to the adversary. | + | The resulting ciphertext in hexadecimal is this: |
+ | <code> | ||
+ | a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8 | ||
+ | </code> | ||
- | Let r = 0 for HEADS and r = 1 for TAILS. Then we have the experiment as shown below: | + | You know that the LCG uses the following formula to produce each byte: |
- | {{:sasc:laboratoare:adversar_prg.png?400|}} | + | |
- | The adversary’s goal is to distinguish these two experiments: at the end of each experiment the adversary outputs a bit $0$ or $1$ for its guess for which experiment it is in. For $b = 0,1$ let $W_{b}$ be the event that in experiment $b$ the adversary output $1$. The adversary tries to maximize its distinguishing advantage, namely the quantity | + | s_next = (a * s_prev + b) mod p |
- | $\mathsf{Adv} = \left| \mathsf{Pr}\left[W_{0}\right] − \mathsf{Pr}\left[W_{1}\right] \right| \in \left[0, 1\right]$ . | + | |
- | The advantage $\mathsf{Adv}$ captures the adversary’s ability to distinguish the two experiments. If the advantage is $0$ then the adversary behaves exactly the same in both experiments and therefore does not distinguish between them. If the advantage is $1$ then the adversary can tell perfectly what experiment it is in. If the advantage is negligible for all efficient adversaries (as defined in class) then we say that the two experiments are indistinguishable. | + | where both s_prev and s_next are byte values (between 0 and 255) and p is 257. |
+ | Both a and b are values between 0 and 256. | ||
- | a. Calculate the advantage of each of the following adversaries: | ||
- | * A1: Always output $1$. | ||
- | * A2: Ignore the result reported by the challenger, and randomly output $0$ or $1$ with even probability. | ||
- | * A3: Output $1$ if HEADS was received from the challenger, else output $0$. | ||
- | * A4: Output $0$ if HEADS was received from the challenger, else output $1$. | ||
- | * A5: If HEADS was received, output $1$. If TAILS was received, randomly output $0$ or $1$ with even probability. | ||
- | b. What is the maximum advantage possible in distinguishing these two experiments? Explain why. | + | 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. |
- | ==== Exercise 2 (4p) ==== | + | 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: | ||
+ | <code python 'ex1_weak_rng.py'> | ||
+ | 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() | ||
+ | </code> | ||
+ | |||
+ | ==== Exercise 2 (3p) ==== | ||
Let's use the experiment defined earlier as a pseudorandom generator ($\mathsf{PRG}$) as follows: | Let's use the experiment defined earlier as a pseudorandom generator ($\mathsf{PRG}$) as follows: | ||
- Set a desired output length $n$ | - Set a desired output length $n$ | ||
- | - Obtain a random sequence $R$ of bits of length $n$ (say flipping a coin, using a Linear-congruential generator, or any other method) | + | - Obtain a random sequence $R$ of bits of length $n$ (e.g. using the Linear-congruential generator from Exercise 1) |
- For each bit $r$ in the random sequence $R$ generated in the previous step, output a bit $b$ as follows: | - For each bit $r$ in the random sequence $R$ generated in the previous step, output a bit $b$ as follows: | ||
- | * if the bit $r$ is $0$, then output a random bit $b$ (e.g. flip a coin and output either $0$ or $1$ depending on its result) | + | * if the bit $r$ is $0$, then output a random bit $b \in \{0, 1\}$ |
* if the bit $r$ is $1$, then output $1$ | * if the bit $r$ is $1$, then output $1$ | ||
Line 51: | Line 129: | ||
<note tip>Also, in Python you may find the functions sqrt, fabs and erfc from the module math useful</note> | <note tip>Also, in Python you may find the functions sqrt, fabs and erfc from the module math useful</note> | ||
- | + | ==== Exercise 3 - LFSR (3p) ==== | |
- | ==== Exercise 3 - LFSR (2p) ==== | + | |
In this exercise we'll build a simple Linear Feedback Shift Register (LFSR). LFSRs produce random bit strings with good statistical properties, but are very easy to predict. | In this exercise we'll build a simple Linear Feedback Shift Register (LFSR). LFSRs produce random bit strings with good statistical properties, but are very easy to predict. | ||
Line 66: | Line 143: | ||
</code> | </code> | ||
- | we generate a new random bit $b$ by $\mathsf{xor}$-ing bits $11$ ($0$) and $18$ ($1$), thus obtaining $b = 1$. We then shift the whole register to the right (thus dropping the right-most bit, which is the bit we add to the generated random sequence) and insert $b$ to the left. Thus, the new state is: | + | we generate a new bit $b$ by $\mathsf{xor}$-ing bits $11$ ($0$) and $18$ ($1$), thus obtaining $b = 1$. We then shift the whole register to the right (thus dropping the right-most bit, which is the bit we add to the generated random sequence) and insert $b$ to the left. Thus, the new state is: |
<code> | <code> | ||
state = '100100100100100100' | state = '100100100100100100' | ||
Line 75: | Line 152: | ||
Using the above starting state and polynomial, generate $100$ random bits and run the monobit statistical test from the previous exercise to see if their frequency seems random. | Using the above starting state and polynomial, generate $100$ random bits and run the monobit statistical test from the previous exercise to see if their frequency seems random. | ||
- |