Python 3 Vigenere Cipher

Nov 02 2020

Wciąż jestem całkiem nowy w Pythonie i próbuję sprawdzić, czy efektywnie korzystałem z modułów, funkcji itp. Lub czy istnieje inny / łatwiejszy sposób na zrobienie czegoś.

Ten szyfr Python 3 Vigenere Cipher to przebudowa szyfru opartego na JavaScript i oparty na systemie Windows. Akceptuje dowolny znak alfabetu i ma wbudowane opcje demo. Demo szyfru wykorzystuje etap 1 szyfru CIA Kryptos .

#! python
import os
import re

## Initialize global variables
continue_cipher = ""
demo_alphabet = "KRYPTOSABCDEFGHIJLMNQUVWXZ"
demo_key = "PALIMPSEST"
demo_cipher_string = "EMUFPHZLRFAXYUSDJKZLDKRNSHGNFIVJYQTQUXQBQVYUVLLTREVJYQTMKYRDMFD"
demo_cipher_decoded = "BETWEENSUBTLESHADINGANDTHEABSENCEOFLIGHTLIESTHENUANCEOFIQLUSION"


## Visuals
def display_header():
    print("################################################")
    print("#                                              #")
    print("#            --- VIGENERE CIPHER ---           #")
    print("#                                              #")
    print("#   A simple Vigenere cipher decoder/encoder   #")
    print("#                                              #")
    print("################################################", end="\n\n")
    return
def display_results(mode, cipher_vars):
    # Clear screen for final results
    os.system('cls')

    # Display header
    display_header()

    # Decompose cipher_vars
    (alphabet, key, cipher_string, results) = cipher_vars

    print("Mode:", "Decrypt" if mode == "D" else "Encrypt", end="\n\n")
    print("Alphabet:", alphabet)
    print("Key:", key)
    print("Cipher String:", cipher_string, end="\n\n")
    print("Decoded string:" if mode == "D" else "Encoded string:", results, end="\n\n")
    return


## Validations
def string_is_alpha(input_string):
    return True if re.match("^[a-zA-Z_]*$", input_string) else False


## Cipher variables
def get_alphabet():
    global demo_alphabet

    while True:
        alphabet = input("Enter cipher alphabet: ").upper()
        if alphabet == "":
            alphabet = demo_alphabet
            break
        elif string_is_alpha(alphabet) is False:
            print("The alphabet is not valid. Alphabet should not contain spaces, digits or special characters.")
        else:
            break

    return alphabet
def get_key():
    global demo_key

    while True:
        key = input("Enter cipher key: ").upper()
        if key == "":
            key = demo_key
            break
        elif string_is_alpha(key) is False:
            print("The key is not valid. Key should not contain spaces, digits or special characters.")
        else:
            break

    return key
def get_cipher_string(mode):
    global demo_cipher_string
    global demo_cipher_decoded

    while True:
        cipher_string = input("Enter cipher string: ").upper()
        if cipher_string == "":
            cipher_string = demo_cipher_string if mode == "D" else demo_cipher_decoded
            break
        elif string_is_alpha(cipher_string) is False:
            print("The cipher string is not valid. Cipher strings should not contain spaces, digits or special characters.")
        else:
            break

    return cipher_string


## Cipher actions
def get_cipher_alphabets(alphabet, key):
    cipher_alphabets = []

    for char in key:
        char_index = alphabet.find(char)
        cipher_alphabet = alphabet[char_index:] + alphabet[:char_index]
        cipher_alphabets.append(cipher_alphabet)

    return cipher_alphabets
def start_cipher(mode, alphabet, key, cipher_string):
    mode_string = ""
    cipher_alphabets = get_cipher_alphabets(alphabet, key)
    
    cipher_alphabet_index = 0
    for char in cipher_string:
        # Reset cipher_alphabet_index to 0 when at end of cipher alphabets
        if cipher_alphabet_index == len(cipher_alphabets):
            cipher_alphabet_index = 0
        
        # Use appropriate alphabet based on mode
        # Syntax: base_alphabet[mode_alphabet.find(char)]
        if mode == "D":
            mode_string += alphabet[cipher_alphabets[cipher_alphabet_index].find(char)]
        else:
            mode_string += cipher_alphabets[cipher_alphabet_index][alphabet.find(char)]

        cipher_alphabet_index += 1

    return mode_string


## Cipher Mode
def get_cipher_mode():
    while True:
        cipher_mode = input("Choose cipher mode - [D]ecrypt or [E]ncrypt: ").upper()
        if cipher_mode != "D" and cipher_mode != "E":
            print("That is not a valid option. Please enter 'D' for decrypt and 'E' for encrypt.")
        else:
            break

    print("")
    return cipher_mode
def start_cipher_mode(mode):
    print("Press 'enter' to use demo options")
    alphabet = get_alphabet()
    key = get_key()
    cipher_string = get_cipher_string(mode)
    mode_string = start_cipher(mode, alphabet, key, cipher_string)
    return alphabet, key, cipher_string, mode_string


## Loop cipher
def get_continue_cipher():
    while True:
        continue_cipher = input("Do you want to decode/encode more? [Y/N]: ").upper()
        if continue_cipher != "Y" and continue_cipher != "N":
            print("That is not a valid option. Please enter 'Y' to continue and 'N' to quit.")
        else:
            break
    return continue_cipher


## Start vigenere cipher program
while continue_cipher != "N":
    # Clear the screen after each operation
    os.system('cls')

    # Display header
    display_header()

    # Determine cipher mode
    cipher_mode = get_cipher_mode()
    cipher_vars = start_cipher_mode(cipher_mode)

    # Display results
    display_results(cipher_mode, cipher_vars)

    continue_cipher = get_continue_cipher()

Odpowiedzi

7 hjpotter92 Nov 02 2020 at 17:35

szulernia

Shebang powinien być ogólny. Obecnie dzwonisz python, co może wskazywać na język Python 2 w niektórych systemach.

Ogólny, przyjazny dla środowiska wirtualny shebang w języku Python to:

#!/usr/bin/env python3

PEP-8

Kilka punktów z poradnika PEP-8 :

  • Otocz definicje funkcji najwyższego poziomu i klas dwoma pustymi wierszami.
  • Używaj oszczędnie pustych linii w funkcjach, aby wskazać sekcje logiczne.
  • Stałe są zwykle definiowane na poziomie modułu i zapisywane dużymi literami z podkreśleniami oddzielającymi słowa.

PEP-484

Funkcje podpowiedzi do typów ułatwiają wykonywanie funkcji. Sprawdź PEP-484 .

if __name__ blok

Umieść logikę wykonywania skryptu wewnątrz if __name__ == "__main__"bloku. Bardziej opisowe wyjaśnienie można sprawdzić na stronie Stack Overflow .

Logika nadmiarowa

W swoim kodzie masz 5 różnych funkcji, tylko do odczytu danych wejściowych użytkownika. Wszyscy mają tę samą pracę:

  1. nieskończona pętla
  2. poproś o wprowadzenie danych przez użytkownika
  3. przekonwertować na wielkie litery
  4. Sprawdź, czy dane wejściowe są puste (lub w zestawie prawidłowych wartości)
  5. w przypadku pustej wartości, zwraca wartość domyślną
  6. powrót z wartością

Wszystko to mogłaby obsłużyć jedna funkcja:

def ask_user_input(message: str, options: List[str] = None, default: str = None, check_alpha: bool = False) -> str:
    if not any([options, default]):
        raise ValueError("Either a set of `options` for validation or a fallback `default` needed.")
    while True:
        value = input(message).upper()
        if options:
            if value in options:
                break
            else:
                print(f"Invalid value. Select one of {', '.join(options)}")
                continue
        if default is not None:
            if not value:
                value = default
                break
            elif not check_alpha:
                break
            elif not (value.isalpha() and value.isascii()):
                print("The input text should only consist of ascii alphabets.")
                continue
            else:
                break
    return value

Regex / validation

Wyrażenie regularne do walidacji danych wejściowych pozwala na _, podczas gdy komunikat o błędzie wyraźnie nie mówi o żadnych znakach specjalnych. Sprawdzanie powyższego przepisania odbywa się za pomocą (zaktualizowane na podstawie poniższego komentarza):

value.isalpha() and value.isascii()

który będzie działał szybciej niż regex (chyba że użytkownik będzie ciągle wprowadzał błędne wartości \$ 10^ n \$razy, w których prekompilowany wzorzec może działać nieco lepiej).

Występ

Kilka rzeczy, które można zmienić, aby kod był bardziej wydajny:

  1. Zamiast łączyć (dołączać) do łańcucha mode_string, wypchnij do listy i na końcu użyj "".join(). Więcej informacji na temat przepełnienia stosu .

  2. Możesz sprawić, by Twój program obsługiwał również systemy linux (* nix). Jedyną zależnością od systemu Windows jest wywołanie systemowe cls. Być może ( wzięte z przepełnienia stosu ):

    def clear():
        os.system("cls" if os.name == "nt" else "clear")
    
  3. Istnieją 2 funkcje o bardzo podobnych nazwach: start_cipher(mode...)i start_cipher_mode(mode). To sprawia, że ​​naprawdę trudno jest wiedzieć, który z nich naprawdę rozpoczyna szyfr . Być może mają 2 oddzielne funkcje encrypti decrypt?

  4. Używając operacji modulo, możesz usunąć następujący warunek:

    if cipher_alphabet_index == len(cipher_alphabets):
        cipher_alphabet_index = 0
    

    i wyglądałoby następująco:

    result.append(alphabet[cipher_alphabets[cipher_alphabet_index % alphabets_length].find(char)]
    
  5. Ponieważ używasz ciągu tylko alphabetdo faktycznej pracy z wartościami indeksu znaków w nim, utwórz słownik. Wyszukiwania w słowniku to \$ O(1) \$w porównaniu do \$ O(n) \$dla .find(). To byłoby:

    from itertools import count
    alphabet_map = dict(zip(alphabet, count()))
    
  6. Z powyższych 2 punktów jasno wynika, że ​​tak naprawdę nie potrzebujesz znaków / alfabetów po wprowadzeniu danych przez użytkownika. Liczy się tylko wartość indeksu modulo. Może to być trudne do zrozumienia / wdrożenia bez znacznego zrozumienia matematycznego, więc na razie możesz to pominąć.

1 Oddthinking Nov 03 2020 at 00:52

@hjpotter opisał większość moich komentarzy.

Booleans

Python ma koncepcję prawdziwych i fałszywych wartości, dlatego lepiej jest traktować wartości bezpośrednio jako wartości logiczne, zamiast porównywać je z wartością True lub False.


return True if re.match("^[a-zA-Z_]*$", input_string) else False

można uprościć do:

return re.match("^[a-zA-Z_]*$", input_string)

    elif string_is_alpha(alphabet) is False:

Można to uprościć, aby:

    elif not string_is_alpha(alphabet):

Jeśli chodzi o ogólne, rzadko chcesz używać „jest” dla porównania. (Głównym wyjątkiem jest porównanie None.)

Kompilowanie wyrażeń regularnych

Jest to prawie na pewno niepotrzebna poprawa wydajności, ale warto wiedzieć na później:

Wywołanie to re.matchmusi kompilować wyrażenie regularne za każdym razem, gdy jest wywoływane. Możesz wstępnie skompilować wyrażenie regularne raz, a następnie wywołać matchskompilowany obiekt, aby go przyspieszyć.

Globals

Niemal za każdym razem, gdy sięgam po globalsłowo kluczowe, okazuje się, że jest to błąd.

Myślę, że nie musisz deklarować identyfikatorów demo jako globalnych; powinny już być dostępne do użycia (tylko do odczytu - jeśli spróbujesz do nich napisać, zdefiniujesz nową zmienną w nowym zakresie, ukrywając oryginały).