# 2
```python=
def read_file(filename):
'''
Read File
'''
f = open(filename, "rb")
txt = f.read()
f.close()
return txt
def save_file(filename,content):
f = open(filename, "wb")
f.write(content)
f.close()
def xor_items(ptxt, ctxt):
'''
'''
xor = bytes([ptxt[i]^ctxt[i] for i in range(len(ptxt))])
return xor
def main():
ptxt = read_file('mssd.txt')
ctxt = read_file('mssd.txt.enc')
partial_keystream = xor_items(ptxt, ctxt) # partial 511 bytes are generated in chunkcs
print(f'Partial key stream : {partial_keystream}')
```
Basically what this is doing is to see the partial keystream that was used to encrypt the text by XOR-ing the ptxt and the ctxt with each other.
Results:
```
Partial key stream : b'Q\xbb\x98\xf4\x11\xe4\xf4\xe1\xe1\x85|,#\x02\xc0Ve\xb5\xd8\xa7Hu\x7f\xfdr\xf5<\xd3\x9d+oy\xf5\xcd\x8a\x1c\xd4%\x1c,,\xb6\x045\xa9\xfa_\xfb\xa7\xdfj\xe0\xca\x9f\x81\x07\x93\x1f\x04q\x86<\xb1\x89\x1eF\xaf\xc4}\xa6\xc5\xb5\xb5\xdat\x1f\xf9\x0f\xe0\r\xe0`?/O\x80\xbcp\x91\xc0u\x15\xb2\x04\xd5\xa8\xc3\xd2\xf1T\x06\xe2V\xde\xdeo\x9cA\t\xe0/\xe6/\xae\x011A\xbf\xc4\x96\x95^\x9e\xdf\xd2t\x7f\xfaXs\x14|\xf2*{bc0\x85\xdc\xe9/\xb0"\xb0\xf2|\x15\xdd\xc1T\x9b\x9ec\x82`s\x9c\x01\x0e\xeb\x11\xdd\x05\x12?\r\xaa)\x17\xb7d8\xb0\xd7\xab\xd7\x13\x84_g\\\xfc\x8d\x83(\xba\xae\x11\x85\xfc\xe2<Tg\xf6R\x01\xe7\xfe8Y\xd9$\x8b\xd7x\xfcx\xd0\xb5\xe0!e\x05\xa6\xb8\xba\xce\xf3T\xb7\x04+\x85\xfd!\x1a\xf3}!\x03\n\xe9i$\xadx\n\x05\nw\xde.,\xa7\xf7\xe2\xca\xcfC\x10\xfc\xd9t\xbd\xb6\x06\xacN\x11\x86-\xf9n98%\xf7\nn\xf7n\x19b\xb35\xe1\x18+O@X\xd7\x05h\x9c\xc7\xdb\xf3\xf5\xc3\xd5\xb26\t\xb3\x89\x0b\'\x19\xef\xb2\x19\xb3\xc9\xaa\xd1\x1e-J=A_\xeby\xf6;\x85Pm\x10\x1eX~\xd3\x1bi\xd1\xa8l!I1\xd3H\xd1H\xfftC\xb7\xce\x07\xdd\xe1=\x08\x1a\x0c\xb7\xf66\xd7\xc2k\x82qL\xb9u\xfa\xb4-\xc9\x15q\xcau\xca\x01\x9c\xd9\xd9Cpg,\x86\xeb\xcee\xd9\x1b\x1ay[<\xba\x14\xc5\xc9\x1e\x0e\xdc6I^\x15N\x1eN\xfc\x85hhX\x97!\xb5\xb2<C\xa6i\xccO\x89m\x85\xce\\WH\xc3\xe2e\x1bI\xe2\xdfB\xc2B\x05\xb6:;\xeb\x99,\xdf\xd3\x04Y\xe28E\xc1\xa9\xb6\xb7C\xe4y\xcbY+\xa6MI*`[Z\xdb\xf7\xda\x0f\r=\x884apti*\n\xd7\\\xf8\xdb\xd8X%\x88M\xe8\xbd\xe2\xc6I\xbe\xafm\xeem\x18o`g\x87\xab\x1c-\x17\x9d9\xben'
```
# 3
Using the obtained partial keystream, we decode `super_cipher.py.enc`
```python
def read_file(filename):
'''
Read File
'''
f = open(filename, "rb")
txt = f.read()
f.close()
return txt
def save_file(filename,content):
f = open(filename, "wb")
f.write(content)
f.close()
def xor_items(ptxt, ctxt):
'''
'''
xor = bytes([ptxt[i]^ctxt[i] for i in range(len(ptxt))])
return xor
def main():
ptxt = read_file('mssd.txt')
ctxt = read_file('mssd.txt.enc')
partial_keystream = xor_items(ptxt, ctxt) # partial 511 bytes are generated in chunkcs
print(f'Partial key stream : {partial_keystream}')
super_cipher = read_file('super_cipher.py.enc')
res = xor_items(partial_keystream,super_cipher)
print(f'Partial text : {res}')
# Save file to make it more easily readable
save_file('super_cipher.py', res)
```
This is the result in `super_cipher.py`
We can then use the same method to partially decrypt the gif, although it would be openable:
Using this line in the code:
```
gif_cipher = read_file('hint.gif.enc')
gif_res = xor_items(partial_keystream,gif_cipher)
save_file('partial_hint.gif',gif_res)
```
Once the partial files are decrypted and saved, we can then move on to fully decrypting the enc files:
This is the function to do it:
```python
def generate_keystream(prelim, length):
keystream = prelim[:N_BYTES]
while len(keystream) < length:
int_stream = next(int.from_bytes(keystream[-N_BYTES:], 'little'))
keystream += int_stream.to_bytes(N_BYTES, 'little')
return keystream[:length]
```
This is the code snippet that executes it.
```python
full_super_keystream = generate_keystream(partial_keystream, len(super_cipher))
print(f'Length Full key stream : {len(full_super_keystream)}')
full_result = xor_items(full_super_keystream,super_cipher)
full_gif_keystream = generate_keystream(partial_keystream, len(gif_cipher))
full_gif_result = xor_items(full_gif_keystream,gif_cipher)
save_file('full_super_cipher.py', full_result)
save_file('full_hint.gif',full_gif_result)
```
The following are the obtained files after decrypting:


The `full_super_cipher.py` seem to be incomplete but since the original file is 512 bytes and the decrypted file is 512 bytes, it seems that there is all there is.
File sizes:

# 4
```python3
#!/usr/bin/env python3
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument("key")
args = parser.parse_args()
RULE = [101 >> i & 1 for i in range(8)]
N_BYTES = 32
N = 8 * N_BYTES
def next(x):
x = (x & 1) << N+1 | x << 1 | x >> N-1
y = 0
for i in range(N):
y |= RULE[(x >> i) & 7] << i
return y
# Bootstrap the PNRG
keystream = int.from_bytes(args.key.encode(),'little')
for i in range(N//2):
keystream = next(keystream)
# Encrypt / decrypt stdin to stdout
plaint
```
What the code is doing:
The code generates the keystream from the previous 32 bytes by performing 3 bitwise transformations, and then performing a lookup on an array named RULE.
Based on the hint:

The given hint:
```
Initial: AXX…XXB (256-bit) , where x is 0 or 1, A is the MSB, B is the LSB.
After: BAXX…XXBA (258-bit)
```
Using the hint given, we can now start to think about how we can use the Reversed rule table to generate the possible message. The following is the function:
```python
def reverse(y):
possible = [i for i in range(len(RULE)) if RULE[i] == (y & 1)]
for j in range(1, N):
new_possible = []
for k in possible:
bit = (y >> j) & 1
bit2 = (k >> j) & 0b11
for i in range(len(RULE)):
if RULE[i] == bit and (i & 0b11) == bit2:
new_possible.append(k | (i << j))
possible = new_possible
actual = list(filter(lambda k: (k >> 256) == (k & 0b11), possible))
if len(actual) != 1:
print("No result!")
print(len(actual))
exit(1)
x = actual[0]
x = (x >> 1) & ((1 << N) - 1)
return x
```
In the reverse function, a list named `possible` (as a list) is created first. Next, it checks each element of the "RULE" list to see if it matches the least significant bit of `y` using bitwise & `( y & 1 )`. The index of the element in the `RULE` list is added to the `possible` list if a match is found.
Then, the function is used to iterate over each value `k` in the `possible` list and extracts the bit value at the current bit position of `y` and the `bit2` value at the current bit position of the current value `k` in the "possible" list.
The `bit2` value is obtained by shifting the current value `k` right by the current bit position and then bitwise & with the binary value `0b11`.
The code then iterates over the length of the `RULE` list and checks if the extracted bit value from `y` matches the current element and if the extracted two-bit value from `k` matches the two least significant bits of the current element.
If the conditions are true, it constructs a new value by bitwise | the current value "k" with the current element left-shifted by the current bit position. This new value is then added to the `new_possible` list.
Using `filter` and `lambda` function to create a new list `actual` that filter values of k from the `possible` list that satisfied the specified condition
The value of `x` is then shifted right by one bit, which removes the LSB that was added during the rule application process. Finally, the value is bitwise & with a value created by left-shifting 1 by N bits and subtracting 1.
The `reverse` function is iterated `N//2` times, where N equals to 256 (32 * 8). In each iteration, the reverse function reversed the bits of the integer.
This is python snippet:
```python
keystream = partial_keystream[0:N_BYTES]
k_stream = int.from_bytes(keystream, 'little')
for i in range(N//2):
k_stream = reverse(k_stream)
print(f"Secret Seed : {k_stream.to_bytes(N_BYTES, 'little')}")
```
Results:
```
Secret Seed = b'{Remember2alwayshanginthere}\x00\x00\x00\x00'
```

