---
# System prepended metadata

title: STL 2 Assingment 4

---



# 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:

![](https://hackmd.io/_uploads/HklU4k1un.gif)

![](https://hackmd.io/_uploads/ryPwVkkdh.png)

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:
![](https://hackmd.io/_uploads/ryM56ARvn.png)



# 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:

![](https://hackmd.io/_uploads/r1e1ahCDn.png)


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'
```
![](https://hackmd.io/_uploads/S1u-ZG1uh.png)
![](https://hackmd.io/_uploads/r1ogNGyd3.png)
