This is a follow up question to Encryption/Decryption algorithm #2 and Encryption/Decryption algorithm.
In my last question, in @Reinderien's answer, he added this line from typing import List
, and stuff like this m: List[List[int]]
. Is that just for type hints?
Would it be faster to use numpy
arrays instead of python lists of lists?
Aside from those follow up questions, if you see anything needing to be changed, let me now.
import base64
import numpy as np
from randomgen import ChaCha
from getpass import getpass
from typing import List
def add_padding(plain_text: str, block_size: int = 128) -> str:
plain_text = plain_text.encode()
padding = -(len(plain_text) + 1) % block_size # Amount of padding needed to fill block
padded_text = plain_text + b'=' * padding + bytes([padding + 1])
return padded_text
def xor_string(key: bytes, secret: bytes) -> bytes:
xored_secret = b''
for i in range(len(secret) // len(key)):
if i > 0:
key = get_round_key(key)
some_decimals = secret[i * len(key):len(key) + (i * len(key))]
xored_secret = xored_secret + b''.join(bytes([key[i] ^ some_decimals[i]]) for i in range(len(key)))
return xored_secret
def get_round_key(key: bytes) -> bytes:
last_col = key[15::16]
# interweave
last_col = b''.join(x for i in range(len(last_col) // 2) for x in (bytes([last_col[-i - 1]]), bytes([last_col[i]])))
new_key = b''
for current_col in [key[i::16] for i in range(16)]:
current_col = xor_string(last_col, current_col)
new_key = new_key + current_col[len(current_col) // 2:] + current_col[:len(current_col) // 2]
return new_key
def generate_key(key: str) -> bytes:
if len(key) >= 128:
key = key.encode()
return key[:128]
elif len(key) < 128:
key = key.encode()
for i in range(128 - len(key)):
key = key + bytes([(sum(key) // len(key)) ^ sum(1 << (8 - 1 - j) for j in range(8) if key[i] >> j & 1)])
decimal = ''.join(str(i) for i in key)
binary = f'{bin(int(decimal[len(decimal) // 2:] + decimal[:len(decimal) // 2]))[2:]:<01024s}'
key = bin_to_bytes(binary[:1024])
half1 = key[:len(key) // 2]
half2 = key[len(key) // 2:]
key = half2 + half1
return key[:128]
def bytes_to_base64(binary: bytes) -> str:
# ints = [int(binary[i * 8:8 + i * 8], 2) for i in range(len(binary) // 8)]
return base64.b64encode(binary).decode()
def bin_to_decimal(binary: str, length: int = 8) -> List[int]:
b = [
binary[i * length:length + (i * length)]
for i in range(len(binary) // length)
]
decimal = [int(i, 2) for i in b]
return decimal
def decimal_to_binary(decimal: List[int], length: int = 8) -> str:
return ''.join(str(bin(num)[2:].zfill(length)) for num in decimal)
def base64_to_bytes(base: str) -> bytes:
return base64.b64decode(base)
def matrix_to_bytes(m: List[List[int]]) -> bytes:
return b''.join(bytes([m[i][j]]) for i in range(16) for j in range(8))
def obfuscate(secret: bytes, key: bytes, encrypting: bool, loops: int) -> bytes:
shuffled_data = b''
round_key = key
for i in range(len(secret) // 128):
if i > 0:
round_key = get_round_key(round_key)
if encrypting:
m = [
list(secret[j * 8 + i * 128:j * 8 + i * 128 + 8])
for j in range(16)
]
m = shuffle(m, round_key, loops)
m = matrix_to_bytes(m)
m = shift_bits(round_key, m)
shuffled_data += xor_string(round_key, m)
else:
xor = xor_string(round_key, secret[i * 128:i * 128 + 128])
xor = unshift_bits(round_key, xor)
m = [list(xor[j * 8:j * 8 + 8]) for j in range(16)]
m = reverse_shuffle(m, round_key, loops)
shuffled_data += matrix_to_bytes(m)
return xor_string(key, shuffled_data)
def shuffle(m: List[List[int]], key: int, loops: int) -> List[List[int]]:
for j in range(loops):
# move columns to the right
m = [row[-1:] + row[:-1] for row in m]
# move rows down
m = m[-1:] + m[:-1]
shuffled_m = [[0] * 8 for _ in range(16)]
for idx, sidx in enumerate(test(key)):
shuffled_m[idx // 8][idx % 8] = m[sidx // 8][sidx % 8]
m = shuffled_m
# cut in half and flip halves
m = m[len(m) // 2:] + m[:len(m) // 2]
# test
# m = list(map(list, zip(*m)))
return m
def reverse_shuffle(m: List[List[str]], key: int, loops: int) -> List[List[str]]:
for j in range(loops):
# test
# m = list(map(list, zip(*m)))
# cut in half and flip halves
m = m[len(m) // 2:] + m[:len(m) // 2]
shuffled_m = [[0] * 8 for _ in range(16)]
for idx, sidx in enumerate(test(key)):
shuffled_m[sidx // 8][sidx % 8] = m[idx // 8][idx % 8]
m = shuffled_m
# move rows up
m = m[1:] + m[:1]
# move columns to the left
m = [row[1:] + row[:1] for row in m]
return m
def shift_bits(key: bytes, secret: bytes) -> bytes:
"""As you can see I have no idea what I'm doing :)"""
shifted = b''
for idx, byte in enumerate(secret):
byte = byte ^ 255
byte = sum(1 << (8 - 1 - j) for j in range(8) if byte >> j & 1)
byte = byte ^ (key[idx] ^ 255)
shifted = shifted + bytes([byte])
return shifted
def unshift_bits(key: bytes, secret: bytes) -> bytes:
shifted = b''
for idx, byte in enumerate(secret):
byte = byte ^ (key[idx] ^ 255)
byte = sum(1 << (8 - 1 - j) for j in range(8) if byte >> j & 1)
byte = byte ^ 255
shifted = shifted + bytes([byte])
return shifted
def test(seed: bytes) -> List[int]:
rg = np.random.Generator(ChaCha(seed=int.from_bytes(seed, byteorder='big'), rounds=8))
lst = np.arange(128)
rg.shuffle(lst)
return lst
def bin_to_bytes(binary: str) -> bytes:
return int(binary, 2).to_bytes(len(binary) // 8, byteorder='big')
def encrypt(password: str, secret: str, loops: int = 1) -> str:
key = generate_key(password)
secret = add_padding(secret)
secret = xor_string(key, secret)
secret = obfuscate(secret, key, True, loops)
secret = bytes_to_base64(secret)
return secret
def decrypt(password: str, base: str, loops: int = 1) -> str:
key = generate_key(password)
binary = base64_to_bytes(base)
binary = xor_string(key, binary)
binary = obfuscate(binary, key, False, loops)
pad = binary[-1]
binary = binary[:-pad]
return binary.decode()
def main():
while True:
com = input('1) Encrypt Text\n' '2) Decrypt Text\n' '3) Exit\n')
input_text = input('Enter the text: ')
# key = getpass('Enter your key: ')
key = input('Enter your key: ') # getpass doesn't work in pycharm, just for testing
if com == '1':
print(f'Encrypted text: {encrypt(key, input_text)}')
elif com == '2':
print(f'Decrypted text: {decrypt(key, input_text)}')
elif com == '3':
break
print()
if __name__ == '__main__':
# from datetime import datetime
#
# start = datetime.now()
# encrypt('password', 'hello this is a test')
# print(datetime.now() - start)
main()