Skip to main content
added 77 characters in body
Source Link
Reinderien
  • 65.4k
  • 5
  • 69
  • 188
  • 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 your cls calls. If they were only for aesthetic purposes, you should keep it that way.

  • Of much (much) higher security value is getpass instead of input, to prevent an over-the-shoulder of passwords.

  • obfuscate is not a particularly good name for a symmetric crypto function; it only "obfuscates" if encrypting=True. Names are hard; maybe call this process_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) or bytearray() (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 your cls calls. If they were only for aesthetic purposes, you should keep it that way.

  • Of much (much) higher security value is getpass instead of input, to prevent an over-the-shoulder of passwords.

  • obfuscate is not a particularly good name for a symmetric crypto function; it only "obfuscates" if encrypting=True. Names are hard; maybe call this process_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 your cls calls. If they were only for aesthetic purposes, you should keep it that way.

  • Of much (much) higher security value is getpass instead of input, to prevent an over-the-shoulder of passwords.

  • obfuscate is not a particularly good name for a symmetric crypto function; it only "obfuscates" if encrypting=True. Names are hard; maybe call this process_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) or bytearray() (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))
    
Source Link
Reinderien
  • 65.4k
  • 5
  • 69
  • 188

  • 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 your cls calls. If they were only for aesthetic purposes, you should keep it that way.

  • Of much (much) higher security value is getpass instead of input, to prevent an over-the-shoulder of passwords.

  • obfuscate is not a particularly good name for a symmetric crypto function; it only "obfuscates" if encrypting=True. Names are hard; maybe call this process_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 to bytes - 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 into secrets 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.