5
\$\begingroup\$

This is the first piece of code that I write that's not just an exercise but a full program I can actually run.

Features:

  • Generate random letter/number pairs for each "printable" ASCII character
  • Save those pairs in a pickle file
  • Encode/decode any string based on that
  • Basic user interface

I would greatly appreciate any advice, feedback, comment you can give me on it, as I've been learning programming by myself for a couple months now. Did I structure it right, is it how a program is supposed to be put together, is it commented well and enough?

Also, I realize it's probably a weird way of encrypting anything and there's surely a better/safer/easier way to do encryption but it was mostly an excuse to write the program.

from string import printable,\
                   ascii_letters,\
                   ascii_lowercase,\
                   ascii_uppercase
import random
import pickle

def generate_code():
    # Generates a random number for each printable ASCII character,
    # Returns a list of (character, number) tuples for each pair
    characters = printable
    numbers = random.sample(range(len(characters) + 1000), len(characters))
    code = list(zip(characters, numbers))
    return code

def encode(string, code):
    # Replaces each character of string with its code-number
    # and adds a random number of random letters

    coded_string = []
    # find matching number for each character
    for character in string:
        for letter, number in code:
            if character == letter:
                # add the matching number
                coded_string.append(str(number))
                coded_string.append(''.join(
                                        random.sample(
                                            ascii_lowercase,
                                            random.randint(2,6)
                                            )
                                        )
                                   )# random letters used to separate numbers
    for _ in range(random.randrange(len(string))):
        coded_string.insert(
                            random.randrange(len(coded_string)),
                            ''.join(random.sample(
                                          ascii_uppercase, random.randint(1, 3)
                                          ))
                            ) # random uppercase letters randomly inserted
    return ''.join(coded_string)

def decode(string, code):

    def retrieve_letter(n):
        for letter, number in code:
            if int(n) == number:
                return letter
            else:
                continue
        return "No Match found"


    decoded_list = []
    decoded_string = ''
    character = ''
    for item in string:
        if item.isdigit():
            character += item
        else:
            if character != '':
                decoded_list.append(character)
                character = ''
    for n in decoded_list:
        decoded_string += retrieve_letter(n)
    return decoded_string

def save_code(object):
    with open("code.p", "wb") as f:
        pickle.dump(object, f)

def load_code():
    try:
        with open("code.p", "rb") as f:
            return pickle.load(f)
    except FileNotFoundError:
        print("No saved code found.")
        return None


def main():

    import time

    code = generate_code()

    print("Welcome to my encryption program!")

    while True: #Code selection menu

        print("Please select an option:")
        print("1: Use saved code")
        print("2: Use new code and overwrite saved code")
        print("3: Use new code and keep saved code")
        prompt = input(">")
        if prompt == "1":
            if load_code() == None:
                code = generate_code()
            else:
                code = load_code()
            break
        elif prompt == "2":
            save_code(code)
            break
        elif prompt == "3":
            break
        else:
            "This option is not available"
            continue

    while True: #Main Loop, asks user if he wants to encode/decode

        print("Would you like to encrypt a phrase?(Y/N)")

        prompt = input(">")
        if prompt in ("N", "no", "No", "n"):
            print("Press Enter to exit or type in anything to continue:")
            prompt = input(">")
            if prompt == '':
                print ("Thank you for using the program, good bye!")
                time.sleep(2)
                break
        else:
            phrase = input("Enter your text here :\n>")
            print (f"\nHere is your code : {encode(phrase, code)}\n")

        print("Would you like to decrypt a phrase?(Y/N)")

        prompt = input(">")
        if prompt in ("N", "no", "No", "n"):
            print("Press Enter to exit or type in anything to continue:")
            prompt = input(">")
            if prompt == '':
                print ("Thank you for using the program, good bye!")
                time.sleep(2)
                break
        else:
            coded_phrase = input("Enter your code here :\n>")
            print(f"\nHere is your original text: {decode(coded_phrase, code)}\n")
            time.sleep(1)
            input("Press Enter to continue")
            print("\n")

if __name__ == '__main__':
    main()

Addendum : To help anyone who, like me, knows very little about encryption, I found this video from the YouTube channel Computerphile.
Here Dr Mike Pound talks about the Secure Hashing Algorithm (SHA1, which however is not a safe encryption algorithm anymore), explaining how it works to create a hash, a seemingly randomly generated string of a fixed length from an input.

\$\endgroup\$
4
\$\begingroup\$

Congrats on teaching yourself programming! You seem to be doing a good job of it. Aside from the fact that I would not want to use a homebrew crypto algorithm the following should be taken as minimal requirements for being able to rely on this code for productive work:

  • The sleep calls do not add anything useful. sleep should be avoided like the plague in production code.
  • I would expect this sort of program to follow some general *nix shell script guidelines:
    • Read from standard input and print() reusable data to standard output. So rather than open("code.p", …) and input() you'd read the input stream, perform decryption or encryption based on parameters, and print() the reusable data before exiting. In other words, after running something like ./my.py --encrypt --key=password1 < input.txt | ./my.py --decrypt --key=password1 > output.txt (I don't know what that would look like on Windows, sorry) input.txt and output.txt should be identical.
    • Expose all functionality in a non-interactive manner.
  • I can't follow the algorithm. I see a lot of mixing of numbers and letters, and what looks like an attempt to obfuscate the ciphertext by adding random length strings to the contents. Some unit tests (or even doctests) would be very useful to understand how this is meant to be read and understood.
  • It's hard to follow two while True loops. The first one doesn't even seem to be necessary - it only loops back if the user specifies an invalid option. It would be perfectly valid in this case to exit the program with an error instead.
  • Running the code through at least one linter such as flake8 or pycodestyle until it passes both without any warnings will help you create more idiomatic Python. Since you're using Python 3 I would also recommend adding type hints and running through a strict mypy configuration such as this one:

    [mypy]
    check_untyped_defs = true
    disallow_untyped_defs = true
    ignore_missing_imports = true
    no_implicit_optional = true
    warn_redundant_casts = true
    warn_return_any = true
    warn_unused_ignores = true
    
\$\endgroup\$
  • \$\begingroup\$ When you say "requirements for putting this code into production" you mean "make it usable by professionals"? Also regarding the while true loops, I wanted to prompt the user again for the same choice if his choice is invalid, how should I do it? And loads of things I've never heard of but thanks a lot, that means I know what to research now! \$\endgroup\$ – Clepsyd Dec 7 '18 at 2:15
  • \$\begingroup\$ I'd consider those kind of the same thing - I think I can use "production" to mean it's performing useful work and can be built on top of, not necessarily that it's on a server answering queries. \$\endgroup\$ – l0b0 Dec 7 '18 at 2:16
  • \$\begingroup\$ Also : "read from standard input, write to standard output" ? I don't really understand that one. Also I'm on Windows at the moment. \$\endgroup\$ – Clepsyd Dec 7 '18 at 2:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.