Cipher Python 3 Vigenere

Nov 02 2020

Sono ancora abbastanza nuovo in Python e sto cercando di vedere se ho usato in modo efficiente moduli, funzioni, ecc. O se c'è un altro / modo più semplice per fare qualcosa.

Questa crittografia Vigenere di Python 3 è una ricostruzione di una crittografia basata su JavaScript e basata su Windows. Accetta qualsiasi carattere alfabetico e dispone di opzioni demo integrate. La demo Cipher utilizza la fase 1 del cifrario 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()

Risposte

7 hjpotter92 Nov 02 2020 at 17:35

shebang

Lo shebang dovrebbe essere generico. Stai attualmente chiamando python, il che potrebbe puntare a python 2 su alcuni sistemi.

Uno shebang di Python generico e rispettoso dell'ambiente virtuale è:

#!/usr/bin/env python3

PEP-8

Pochi punti dalla guida PEP-8 :

  • Circonda la funzione di primo livello e le definizioni di classe con due righe vuote.
  • Usa le righe vuote nelle funzioni, con parsimonia, per indicare le sezioni logiche.
  • Le costanti sono generalmente definite a livello di modulo e scritte in tutte le lettere maiuscole con trattini bassi che separano le parole.

PEP-484

Le funzioni di suggerimento del tipo rendono più facile seguire le funzioni. Dai un'occhiata al PEP-484 .

if __name__ bloccare

Metti la logica di esecuzione del tuo script all'interno del if __name__ == "__main__"blocco. Una spiegazione più descrittiva può essere verificata su Stack Overflow .

Logica ridondante

Nel tuo codice, hai 5 diverse funzioni, solo per leggere l'input dell'utente. Tutti hanno lo stesso lavoro di:

  1. ciclo infinito
  2. chiedere l'input dell'utente
  3. converti in maiuscolo
  4. convalida se l'input è vuoto (o in un insieme di valori validi)
  5. in caso di valore vuoto, restituisce un valore predefinito
  6. restituire con il valore

Tutto questo potrebbe essere gestito da un'unica funzione:

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 / convalida

La regex per la convalida dell'input lo consente _, mentre il messaggio di errore dice esplicitamente nessun carattere speciale. Il controllo nella riscrittura sopra viene eseguito utilizzando (aggiornato in base al commento di seguito):

value.isalpha() and value.isascii()

che funzionerà più velocemente delle espressioni regolari (a meno che l'utente non continui a inserire valori errati \$ 10^ n \$volte, in cui il pattern precompilato potrebbe funzionare leggermente meglio).

Prestazione

Alcune cose che possono essere modificate per rendere il codice più performante:

  1. Invece di concatenare (aggiungere) alla stringa mode_string, eseguire il push in un elenco e alla fine utilizzare "".join(). Maggiori dettagli su Stack Overflow .

  2. Puoi fare in modo che il tuo programma supporti anche i sistemi Linux (* nix). L'unica dipendenza da Windows è la tua chiamata di sistema a cls. Forse ( tratto da Stack Overflow ):

    def clear():
        os.system("cls" if os.name == "nt" else "clear")
    
  3. Ci sono 2 funzioni con nomi molto simili: start_cipher(mode...)e start_cipher_mode(mode). Ciò rende davvero difficile sapere quale sta avviando veramente il codice . Forse, hanno 2 funzioni separate encrypte decrypt?

  4. Utilizzando l'operazione modulo, è possibile rimuovere il seguente condizionale:

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

    e sarebbe simile a:

    result.append(alphabet[cipher_alphabets[cipher_alphabet_index % alphabets_length].find(char)]
    
  5. Dal momento che usi la alphabetstringa solo per lavorare effettivamente con i valori di indice dei caratteri in essa contenuti, crea un dizionario. Le ricerche nel dizionario sono \$ O(1) \$rispetto a \$ O(n) \$per il .find(). Questo sarebbe:

    from itertools import count
    alphabet_map = dict(zip(alphabet, count()))
    
  6. Dai 2 punti precedenti, è chiaro che non hai davvero bisogno di caratteri / alfabeti dopo l'input dell'utente. Solo il valore di indice modulo è importante. Questo potrebbe essere difficile da capire / implementare senza una notevole comprensione matematica, quindi puoi saltare questo per ora.

1 Oddthinking Nov 03 2020 at 00:52

@hjpotter ha coperto la maggior parte dei miei commenti.

Booleani

Python ha un concetto di valori veritieri e falsi , quindi è preferibile trattare i valori come booleani direttamente, piuttosto che confrontarli con True o False.


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

può essere semplificato in:

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

    elif string_is_alpha(alphabet) is False:

Questo può essere semplificato in:

    elif not string_is_alpha(alphabet):

Se generale, raramente si desidera utilizzare "è" per il confronto. (L'eccezione principale è il confronto con None.)

Compilazione di espressioni regolari

Questo è quasi certamente un miglioramento delle prestazioni non necessario, ma potrebbe essere utile sapere per dopo:

La chiamata a re.matchdeve compilare la regexp ogni volta che viene chiamata. È possibile precompilare la regexp una volta e quindi chiamare matchl'oggetto compilato per velocizzarlo.

Globali

Quasi ogni volta che cerco la globalparola chiave, risulta essere un errore.

Non penso che sia necessario dichiarare gli identificatori demo come globali; dovrebbero essere già disponibili per l'uso (solo per leggere - se si tenta di scrivere su di loro, si definirà una nuova variabile nel nuovo ambito, nascondendo gli originali).