Consider adding PEP484 type hints. I needed to go through this to make some sense of the values you're passing around.
not being OS specific - indeed. Your call to
cls
has dubious security value, and if you deem it to have such value, it's better to call into a cross-platform library that will accomplish the same thing. Currently you're pegged to Windows and that is bad. You're so close to having a cross-compatible application; it would be a shame to let this remain your only obstacle. For now in the example I have simply deleted yourcls
calls. If they were only for aesthetic purposes, you should keep it that way.Of much (much) higher security value is
getpass
instead ofinput
, to prevent an over-the-shoulder of passwords.obfuscate
is not a particularly good name for a symmetric crypto function; it only "obfuscates" ifencrypting=True
. Names are hard; maybe call thisprocess_crypto
or somesuch.A string of 0 and 1 characters - or possibly worse, a list of strings of length 1, each a 0 or 1 character - is a very inefficient and impractical internal representation of binary data. It's more work than I'm willing to do, but for an application that whatsoever exceeds superficial, beginner-level instructional code, it's of critical importance that you refactor to use
bytes
arrays. (in the case of immutable data) orbytearray()
(in the case of mutable data)Related to the above - probably not a great idea to carry around an arbitrary-length integer of over 300 digits (!!). Again
bytes
is a better representation.Avoid incremental concatenation of strings in a loop, O(n^2) in time.
You need to relax with the one-liners. This:
xored_secret += decimal_to_binary([bin_to_decimal(key, len(key))[0] ^ bin_to_decimal(secret[i * len(key):len(key) + (i * len(key))], len(key))[0]], len(key))
Consider adding PEP484 type hints. I needed to go through this to make some sense of the values you're passing around.
not being OS specific - indeed. Your call to
cls
has dubious security value, and if you deem it to have such value, it's better to call into a cross-platform library that will accomplish the same thing. Currently you're pegged to Windows and that is bad. You're so close to having a cross-compatible application; it would be a shame to let this remain your only obstacle. For now in the example I have simply deleted yourcls
calls. If they were only for aesthetic purposes, you should keep it that way.Of much (much) higher security value is
getpass
instead ofinput
, to prevent an over-the-shoulder of passwords.obfuscate
is not a particularly good name for a symmetric crypto function; it only "obfuscates" ifencrypting=True
. Names are hard; maybe call thisprocess_crypto
or somesuch.A string of 0 and 1 characters - or possibly worse, a list of strings of length 1, each a 0 or 1 character - is a very inefficient and impractical internal representation of binary data. It's more work than I'm willing to do, but for an application that whatsoever exceeds superficial, beginner-level instructional code, it's of critical importance that you refactor to use
bytes
arrays.Related to the above - probably not a great idea to carry around an arbitrary-length integer of over 300 digits (!!). Again
bytes
is a better representation.Avoid incremental concatenation of strings in a loop, O(n^2) in time.
You need to relax with the one-liners. This:
xored_secret += decimal_to_binary([bin_to_decimal(key, len(key))[0] ^ bin_to_decimal(secret[i * len(key):len(key) + (i * len(key))], len(key))[0]], len(key))
Consider adding PEP484 type hints. I needed to go through this to make some sense of the values you're passing around.
not being OS specific - indeed. Your call to
cls
has dubious security value, and if you deem it to have such value, it's better to call into a cross-platform library that will accomplish the same thing. Currently you're pegged to Windows and that is bad. You're so close to having a cross-compatible application; it would be a shame to let this remain your only obstacle. For now in the example I have simply deleted yourcls
calls. If they were only for aesthetic purposes, you should keep it that way.Of much (much) higher security value is
getpass
instead ofinput
, to prevent an over-the-shoulder of passwords.obfuscate
is not a particularly good name for a symmetric crypto function; it only "obfuscates" ifencrypting=True
. Names are hard; maybe call thisprocess_crypto
or somesuch.A string of 0 and 1 characters - or possibly worse, a list of strings of length 1, each a 0 or 1 character - is a very inefficient and impractical internal representation of binary data. It's more work than I'm willing to do, but for an application that whatsoever exceeds superficial, beginner-level instructional code, it's of critical importance that you refactor to use
bytes
arrays (in the case of immutable data) orbytearray()
(in the case of mutable data)Related to the above - probably not a great idea to carry around an arbitrary-length integer of over 300 digits (!!). Again
bytes
is a better representation.Avoid incremental concatenation of strings in a loop, O(n^2) in time.
You need to relax with the one-liners. This:
xored_secret += decimal_to_binary([bin_to_decimal(key, len(key))[0] ^ bin_to_decimal(secret[i * len(key):len(key) + (i * len(key))], len(key))[0]], len(key))
Consider adding PEP484 type hints. I needed to go through this to make some sense of the values you're passing around.
not being OS specific - indeed. Your call to
cls
has dubious security value, and if you deem it to have such value, it's better to call into a cross-platform library that will accomplish the same thing. Currently you're pegged to Windows and that is bad. You're so close to having a cross-compatible application; it would be a shame to let this remain your only obstacle. For now in the example I have simply deleted yourcls
calls. If they were only for aesthetic purposes, you should keep it that way.Of much (much) higher security value is
getpass
instead ofinput
, to prevent an over-the-shoulder of passwords.obfuscate
is not a particularly good name for a symmetric crypto function; it only "obfuscates" ifencrypting=True
. Names are hard; maybe call thisprocess_crypto
or somesuch.A string of 0 and 1 characters - or possibly worse, a list of strings of length 1, each a 0 or 1 character - is a very inefficient and impractical internal representation of binary data. It's more work than I'm willing to do, but for an application that whatsoever exceeds superficial, beginner-level instructional code, it's of critical importance that you refactor to use
bytes
arrays.Related to the above - probably not a great idea to carry around an arbitrary-length integer of over 300 digits (!!). Again
bytes
is a better representation.Avoid incremental concatenation of strings in a loop, O(n^2) in time.
You need to relax with the one-liners. This:
xored_secret += decimal_to_binary([bin_to_decimal(key, len(key))[0] ^ bin_to_decimal(secret[i * len(key):len(key) + (i * len(key))], len(key))[0]], len(key))
is illegible and unmaintainable, and I see at least three different expressions in there that should receive their own separate, temporary variable on a separate line.
- Reassigning
key
to a value of a different type -str
tobytes
- is not adviseable; make a different variable name. - Having a
__main__
guard is insufficient to create scope. To put your main variables into function scope you need an actual function. - Calling into
random
is deleterious to the security of your crypto, and is one of the mistakes that probably every newcomer to crypto commits. Call intosecrets
instead.
Covering some (certainly not all) of the above:
#!/usr/bin/env python
# not sure if I did this right
import base64
import random
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 decimal_to_binary(padded_text)
def xor_string(key: str, secret: str) -> str:
xored_secret = ''
for i in range(len(secret) // len(key)):
if i > 0:
key = get_round_key(key)
some_decimals = bin_to_decimal(secret[i * len(key):len(key) + (i * len(key))], len(key))
some_values = [
bin_to_decimal(key, len(key))[0] ^ some_decimals[0]
]
xored_secret += decimal_to_binary(some_values, len(key))
return xored_secret
def generate_key(key: str) -> str:
if len(key) >= 128:
key = decimal_to_binary(key.encode())
return key[:1024]
elif len(key) < 128:
key = key.encode()
for i in range(128 - len(key)):
b = decimal_to_binary([key[i]])
b = xor_string(decimal_to_binary([sum(key) // len(key)]), b[::-1])
key += bytes([int(b, 2)])
new_key = ''.join(str(i) for i in key)
half1 = new_key[:len(new_key) // 2]
half2 = new_key[len(new_key) // 2:]
new_key = decimal_to_binary([int(half1 + half2)])
new_key = new_key[:1024]
return new_key
def bin_to_base64(binary: str) -> str:
ints = [
int(binary[i * 8:8 + i * 8], 2)
for i in range(len(binary) // 8)
]
return base64.b64encode(bytes(ints)).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_bin(base: str) -> str:
decoded = ''
for letter in base64.b64decode(base):
decoded += bin(letter)[2:].zfill(8)
return decoded
def matrix_to_str(m: List[List[str]]) -> str:
return ''.join(
str(m[i][j])
for i in range(32) for j in range(32)
)
def obfuscate(binary: str, key: str, encrypting: bool, loops: int) -> str:
shuffled_binary = ''
round_key = key
for i in range(len(binary) // 1024):
if i > 0:
round_key = get_round_key(round_key)
if encrypting:
m = [list(binary[j * 32 + i * 1024:j * 32 + i * 1024 + 32]) for j in range(32)]
m = shuffle(m, bin_to_decimal(round_key, 1024)[0], loops)
shuffled_binary += xor_string(round_key, matrix_to_str(m))
else:
xor = xor_string(round_key, binary[i * 1024:i * 1024 + 1024])
m = [list(xor[j * 32:j * 32 + 32]) for j in range(32)]
m = reverse_shuffle(m, bin_to_decimal(round_key, 1024)[0], loops)
shuffled_binary += matrix_to_str(m)
return xor_string(key, shuffled_binary)
def shuffle(m: List[List[str]], key: int, loops: int) -> List[List[str]]:
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] * 32 for _ in range(32)]
for idx, sidx in enumerate(test(key)):
shuffled_m[idx // 32][idx % 32] = m[sidx // 32][sidx % 32]
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] * 32 for _ in range(32)]
for idx, sidx in enumerate(test(key)):
shuffled_m[sidx // 32][sidx % 32] = m[idx // 32][idx % 32]
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 test(seed: int) -> List[int]:
random.seed(seed)
lst = list(range(1024))
random.shuffle(lst)
return lst
def get_round_key(key):
key = [[key[(j * 32 + n)] for n in range(32)] for j in range(32)]
# get the last column
col = [i[-1] for i in key]
# interweave
col = [x for i in range(len(col) // 2) for x in (col[-i - 1], col[i])]
new_key = ''
for i in range(32):
cols = ''
for row in key:
cols += row[i]
cols = cols[16:] + cols[:16]
new_key += xor_string(''.join(str(ele) for ele in col), cols)
return new_key
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 = bin_to_base64(secret)
return secret
def decrypt(password: str, base: str, loops: int = 1) -> str:
key = generate_key(password)
binary = base64_to_bin(base)
binary = xor_string(key, binary)
binary = obfuscate(binary, key, False, loops)
binary = bin_to_bytes(binary)
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: ')
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__':
main()
Speaking more generally, for educational and recreational purposes writing this kind of code is fun. However, cryptographic implementations are viciously difficult to get correct, and sometimes even more difficult to prove that they're correct. In the real, production world, please do not use this; just call into a library.