# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.15.2
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# # Secret-key or symmetric cryptography
#
# ## 1 DES S-box $S_3$
#
# The input to the DES S-box $S_3$ is $110111$. What’s the output? Use Wikipedia, google, a book or some other source to find the table for $S_3$.

# Source: http://celan.informatik.uni-oldenburg.de/kryptos/info/des/sbox/
# ![Des-Box3.png](img/Des-Box3.png)
#
# Output: 0011

# ## 2 3DES
#
# What is the effective key size of 3DES and why is it not 168 bits?

# + active=""
# it's 112 bits, not 168 due to meet-in-the-middle attack threat.
# -

# ## 3 Differences between AES and Rijndeal
#
# What are the differences between the AES candidate Rijndeal and AES with respect to block size, key size and number of rounds?

# As described in "[The Design of Rijandel](https://www.springer.com/us/book/9783540425809)": "The _only_ difference between Rijandel and the AES is the range of supported values for the block length and cipher key length".
#
# Rijndael is a block cipher with both a variable block length and a variable key length. The block length and the key length can be independently specified to any multiple of 32 bits, with a minimum of 128 bits and a maximum of 256 bits. It would be possible to define versions of Rijndael with a higher block length or key length, but currently there seems no need for it.
#
# The AES fixes the block length to 128 bits, and supports key lengths of 128, 192 or 256 bits only. The extra block and key lengths in Rijndael were not evaluated in the AES selection process, and consequently they are not adopted in the current FIPS standard.

# ##  4 AES S-box
#
# If we input the byte $11011101$ into the AES S-box, what’s the output? Use the table in slides!

# $1101 -> D -> row$
#
# $1101 -> D -> column$
#
# $11011101 -> C1 -> 11000001$
#
# ![AES-S-Box.png](img/AES-S-Box.png)

# ## 5 Other Block ciphers
#
# Compare DES, 3DES and AES with other block ciphers like IDEA, Blowfish, Twofisch, RC5, RC6, Serpent and three more of Your choice. Make a table that shows key size, effective key size, block size, number of rounds, relative velocity of a hard- or software implementation.

# - https://pdfs.semanticscholar.org/e684/4c748d38997bf0de71cd7d05e58b09e310f6.pdf
# - https://www.cse.wustl.edu/~jain/cse567-06/ftp/encryption_perf/
# - http://www.ijcseonline.org/pub_paper/IJCSE-00187.pdf
#
# |Ciphers|key size| effective keysize|block size| number of rounds| relative velocity|
# |:--- |:--- |:--- |:--- |:--- |:--- |
# |DES|56 bits||64bits|16|1|
# |3DES| 112 bits ||64bits|48|0.3-0.5|
# |AES|128,192 or 256||128, 192 or 256|10, 12 or 14|0.6|
# |IDEA|128 bits||64 bits|8.5
# |Blowfish|32-448 bits||64 bits|16|1.2-3|
# |Twofish|
# |RC5|
# |RC6|128,192 or 256||128 bits|20|

# ## 6 Modes of operation
#
# You should be able to produce sketches of the 5 modes of operation and You should be able to write down the equations, relating, IVs (if any), plaintext block, key, ciphertext block, encryption and decryption, XOR.
# You should also understand the influence of a one-bit error in the ciphertext block.

# | Modes of Operation | Long Name | Cipher Type |
# |:--- |:--- |:--- |
# | ECB | Electronic Code Book Mode | Block |
# | CBC | Chained Block Cipher Mode | Block |
# | CFB | Cipher FeedBack Mode | Stream |
# | OFB | Output FeedBack  Mode| Stream |
# | CTR | Counter Mode | Stream |

# ### ECB
#
# ![Electronic CodeBook Mode Diagram](img/ECB_Diagram.png)
#
# #### Encryption
# $c_k = E(k, m_k),\ k=1,2,3,...$
#
# #### Decryption
# $m_k = D(k, c_k),\ k=1,2,3,...$
#
# #### Error Propagation
# An error in the ciphertext produces garbage output but does not propagate.

# ### CBC
#
# ![Chained Block Cipher ModeDiagram](img/CBC_Diagram.png)
#
# #### Encryption
# $c_0 = IV$<br/>
# $c_k = E(k,m_k\oplus c_{k-1}),\ k = 1,2,3,...$
#
# #### Decryption
# $c_0 = IV$<br/>
# $m_k = D(k, c_k)\oplus c_{k-1},\ k = 1,2,3,...$
#
# #### Error Propagation
# An error in the ciphertext $c_k$ affects all bits of the corresponding plaintext $m_k$ and the one bit of $m_{k+1}$ with which the erroneous bit in $c_k$ is XOR-ed

# ### CFB
#
# ![Cipher FeedBack Mode Diagram](img/CFB_Diagram.png)
#
# #### Encryption
# $c_0 = IV$<br/>
# $c_i = m_i \oplus E(k, c_{i-1},\ i=1,2,3...$
#
# #### Decryption
# $c_0 = IV$<br/>
# $m_i = c_i \oplus E(k, c_{i-1},\ i=1,2,3...$
#
# #### Error Propagation
# An error in the cipher block $c_k$ produces one error in the plaintext block $m_k$ at the bit position where the error has occured (as it is XOR-ed), and produces garbage in the next plaintext block $m_{k+1}$ as $E(k,c_{k_{faulty}})$ should produce a completely different output than $E(k, c_k)$, and therefore $c_{k+1}\oplus E(k,c_{k_{faulty}})$ should be complete gibberish.

# ### OFB
#
# ![Output FeedBack Mode Diagram](img/OFB_Diagram.png)
#
# #### Encryption
# $z_0 = IV$<br/>
# $z_i = E_k(z_{i-1}),\ i=1,2,3,...$<br/>
# $c_i = m_i\oplus z_i,\ i=1,2,3,...$
#
# #### Decryption
# $z_0 = IV$<br/>
# $z_i = E_k(z_{i-1}),\ i=1,2,3,...$<br/>
# $m_i = c_i\oplus z_i,\ i=1,2,3,...$
#
# #### Error Propagation
# An error in cipher bit $c_i$ leads to an erroneous bit $m_i$ but does not propagate.

# ### CTR
#
# ![Counter Mode Diagram](img/CTR_Diagram.png)
#
# #### Encryption
# $z_0 = IV$<br/>
# $z_i = IV\oplus i,\ i=1,2,3,...$<br/>
# $y_i = x_i\oplus E_k(z_i),\ i=1,2,3,...$
#
# #### Decryption
# $z_0 = IV$<br/>
# $z_i = IV\oplus i,\ i=1,2,3,...$<br/>
# $y_i = x_i\oplus E_k(z_i),\ i=1,2,3,...$
#
# #### Note on the IV
# The IV should be a nonce, but same nonce can be used throughout the session. It's main goal is to offset the counter startpoint, so that using the same key and first message does not generate the same ciphertext (think of handshakes/authentication).
#
# #### Error Propagation
# An error in $y_0$ generates one error in the decrypted $x_0$, but does not propagate.

# ## 7 RC4
#
# Use python in Jupyter Notebook to programm RC4. Do some research on RC4 and find out, why it should not be used any more!
# Siehe auch [Webbrowser: Endgültig Schluss mit RC4](https://www.heise.de/security/meldung/Webbrowser-Endgueltig-Schluss-mit-RC4-2805770.html) und [Der Lange Abschied von RC4](https://www.golem.de/news/verschluesselung-der-lange-abschied-von-rc4-1507-114877.html).

# +
def KSA(key):
    keylength = len(key)
    S = list(range(256))
    j = 0
    
    for i in range(256):
        j = (j + S[i] + key[i % keylength]) % 256
        S[i], S[j] = S[j], S[i]
    return S

def PRGA(S):
    i = 0
    j = 0
    
    while True:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        yield S[(S[i] + S[j]) % 256]

def RC4(key):
    S = KSA(key)
    return PRGA(S)

def convert_key(s):
    return [ord(c) for c in s]


# +
key = "Key"
plaintext = "Plaintext"
# ciphertext should be BBF316E8D940AF0AD3

key = convert_key(key)
keystream = RC4(key)

import sys

for c in plaintext:
    sys.stdout.write("%02X" % (ord(c) ^ next(keystream)))
# -

# Vulnerabilities:
#
# - Pseudo Random Number Generator PRNG has higher probabilities for some numbers to appear.<br/>
#     This lets an attacker analyse some input/output-pairs and find out the key
# - No nonce as input therefore it needs a new key for each stream.<br/>
#     Since most applications just concatenate the nonce and the key, this is a problem because "over all possible RC4 keys, the statistics for the first few bytes of output keystream are strongly non-random, leaking information about the key."

# ## 8 Trivium
#
# Use python in Jupyter Notebook to programm Trivium. This is not an easy task: do it in groups of two!
#
# Use $0x00000000000000000000000000000000$ for the key, IV, and plaintext for initial testing.
#
# The expected ciphertext for this should be $0xFBE0BF265859051B517A2E4E239FC97F$.
#
# In the algorithm on slide “_Trivium — Initialization_”, the $+$ represents XOR (which in python is “^”), ·
# represents logical AND (which in python is “&”). The key-stream is
#
# $z_i = t_1 + t_2 + t_3$
#
# and the $i$th byte of the ciphertext $c_i$ of the plaintext $m_i$ is
#
# $c_i = z_i \oplus m_i$
#
# The following [site](https://asecuritysite.com/encryption/trivium) might be of some help!

# +
from collections import deque
from itertools import repeat
from sys import version_info


class Trivium:
    def __init__(self, key, iv):
        """in the beginning we need to transform the key as well as the IV.
        Afterwards we initialize the state."""
        self.state = None
        self.counter = 0
        self.key = key  # self._setLength(key)
        self.iv = iv  # self._setLength(iv)

        # Initialize state
        # len 93
        init_list = list(map(int, list(self.key)))
        init_list += list(repeat(0, 13))
        # len 84
        init_list += list(map(int, list(self.iv)))
        init_list += list(repeat(0, 4))
        # len 111
        init_list += list(repeat(0, 108))
        init_list += list([1, 1, 1])
        self.state = deque(init_list)

        # Do 4 full cycles, drop output
        for i in range(4*288):
            self._gen_keystream()

    def encrypt(self, message):
        """To be implemented"""
        pass

    def decrypt(self, cipher):
        """To be implemented"""
        #maybe with code from here https://github.com/mortasoft/Trivium/blob/master/trivium.py
        # Line 119
        pass

    def keystream(self):
        """output keystream
        only use this when you know what you are doing!!"""
        while self.counter < 2**64:
            self.counter += 1
            yield self._gen_keystream()

    def _setLength(self, input_data):
        """we cut off after 80 bits, alternatively we pad these with zeros."""
        input_data = "{0:080b}".format(input_data)
        if len(input_data) > 80:
            input_data = input_data[:(len(input_data)-81):-1]
        else:
            input_data = input_data[::-1]
        return input_data

    def _gen_keystream(self):
        """this method generates triviums keystream"""
        
        t_1 = self.state[65] ^ self.state[92]
        t_2 = self.state[161] ^ self.state[176]
        t_3 = self.state[242] ^ self.state[287]

        out = t_1 ^ t_2 ^ t_3

        u_1 = t_1 ^ self.state[90]  & self.state[91]  ^ self.state[170]
        u_2 = t_2 ^ self.state[174] & self.state[175] ^ self.state[263]
        u_3 = t_3 ^ self.state[285] & self.state[286] ^ self.state[68]

        self.state.rotate(1)

        self.state[0] = u_3
        self.state[93] = u_1
        self.state[177] = u_2

        return out
import sys

k1="00000000000000000000"
i1="00000000000000000000"

print ("Key: "+k1)
print ("IV:  "+i1)
def main():
    KEY = hex_to_bits(k1)[::-1]
    IV = hex_to_bits(i1)[::-1]
    trivium = Trivium(KEY, IV)


    next_key_bit = trivium.keystream().__next__

    for i in range(1):
        keystream = []
        for j in range(128):
            keystream.append(next_key_bit())
        print ("Stream: "+bits_to_hex(keystream))

# Convert strings of hex to strings of bytes and back, little-endian style
_allbytes = dict([("%02X" % i, i) for i in range(256)])


def _hex_to_bytes(s):
    return [_allbytes[s[i:i+2].upper()] for i in range(0, len(s), 2)]


def hex_to_bits(s):
    return [(b >> i) & 1 for b in _hex_to_bytes(s)
            for i in range(8)]


def bits_to_hex(b):
    return "".join(["%02X" % sum([b[i + j] << j for j in range(8)])
                    for i in range(0, len(b), 8)])


if __name__ == "__main__":
    main()

# -

# ## 9 OTP
#
# Make your own example with one-time pad. Why is it perfectly secure? Make sure, the key is truly random not used more than once and kept secret from adversaries.

# $m = 0110100001100101011011000110110001101111001000000111011101101111011100100110110001100100$<br />
# $k = 0110110111011101100100110001101100000001010001110010110111101010101110010001101100011100$

# +
m = '0110100001100101011011000110110001101111001000000111011101101111011100100110110001100100'
k = '0110110111011101100100110001101100000001010001110010110111101010101110010001101100011100'

c = int(m,2)^int(k,2)

print('m: ' + m)
print('k: ' + k)
print('c: ' + bin(c)[2:].zfill(len(m)))
print('d: ' + bin(c^int(k,2))[2:].zfill(len(m)))
print('m: ' + m)
