Cifrado Python 3 Vigenere

Nov 02 2020

Todavía soy bastante nuevo en Python y estoy tratando de ver si he usado módulos, funciones, etc. de manera eficiente o si hay otra forma más fácil de hacer algo.

Este cifrado Python 3 Vigenere es una reconstrucción de un cifrado basado en JavaScript y está basado en Windows. Acepta cualquier carácter del alfabeto y tiene opciones de demostración integradas. La demostración de cifrado utiliza la etapa 1 del cifrado 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()

Respuestas

7 hjpotter92 Nov 02 2020 at 17:35

el asunto

El shebang debería ser genérico. Actualmente está llamando python, lo que podría apuntar a python 2 en ciertos sistemas.

Un shebang de Python genérico y respetuoso con el medio ambiente virtual es:

#!/usr/bin/env python3

PEP-8

Algunos puntos de la guía PEP-8 :

  • Rodee las definiciones de clases y funciones de nivel superior con dos líneas en blanco.
  • Use líneas en blanco en las funciones, con moderación, para indicar secciones lógicas.
  • Las constantes generalmente se definen a nivel de módulo y se escriben en letras mayúsculas con guiones bajos que separan las palabras.

PEP-484

Las funciones de sugerencias de tipo facilitan el seguimiento de sus funciones. Consulte el PEP-484 .

if __name__ bloquear

Ponga la lógica de ejecución de su script dentro del if __name__ == "__main__"bloque. Se puede consultar una explicación más descriptiva en Stack Overflow .

Lógica redundante

En su código, tiene 5 funciones diferentes, solo para leer la entrada del usuario. Todos ellos tienen el mismo trabajo de:

  1. Bucle infinito
  2. solicitar información del usuario
  3. convertir a mayúsculas
  4. validar si la entrada está vacía (o en un conjunto de valores válidos)
  5. en caso de valor vacío, devuelve un valor predeterminado
  6. volver con el valor

Todo esto podría ser manejado por una sola función:

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 / validación

La expresión regular para validar la entrada lo permite _, mientras que el mensaje de error dice explícitamente que no hay caracteres especiales. La verificación en la reescritura anterior se realiza usando (actualizado según el comentario a continuación):

value.isalpha() and value.isascii()

que funcionará más rápido que regex (a menos que el usuario siga ingresando valores incorrectos \$ 10^ n \$veces, en las que el patrón precompilado podría funcionar un poco mejor).

Actuación

Algunas cosas que se pueden cambiar para hacer que el código sea más eficiente:

  1. En lugar de concatenar (agregar) a una cadena mode_string, empuje a una lista y al final, use "".join(). Más detalles sobre Stack Overflow .

  2. También puede hacer que su programa admita sistemas Linux (* nix). La única dependencia de Windows es la llamada al sistema cls. Quizás ( tomado de Stack Overflow ):

    def clear():
        os.system("cls" if os.name == "nt" else "clear")
    
  3. Hay 2 funciones con nombres muy similares: start_cipher(mode...)y start_cipher_mode(mode). Esto hace que sea realmente difícil saber cuál está realmente iniciando el cifrado . ¿Quizás, tiene 2 funciones separadas encrypty decrypt?

  4. Usando la operación de módulo, puede eliminar el siguiente condicional:

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

    y se vería así:

    result.append(alphabet[cipher_alphabets[cipher_alphabet_index % alphabets_length].find(char)]
    
  5. Dado que solo usa la alphabetcadena para trabajar realmente con los valores de índice de los caracteres que contiene, cree un diccionario. Las búsquedas en el diccionario son \$ O(1) \$comparado con \$ O(n) \$para el .find(). Esto sería:

    from itertools import count
    alphabet_map = dict(zip(alphabet, count()))
    
  6. A partir de los 2 puntos anteriores, está claro que realmente no necesita caracteres / alfabetos después de la entrada del usuario. Solo importa el módulo del valor del índice. Esto puede ser difícil de entender / implementar sin una comprensión matemática considerable, por lo que puede omitirlo por ahora.

1 Oddthinking Nov 03 2020 at 00:52

@hjpotter cubrió la mayoría de mis comentarios.

Booleanos

Python tiene un concepto de valores verdaderos y falsos , por lo que es preferible tratar los valores como booleanos directamente, en lugar de compararlos con Verdadero o Falso.


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

se puede simplificar a:

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

    elif string_is_alpha(alphabet) is False:

Esto se puede simplificar a:

    elif not string_is_alpha(alphabet):

Si es general, rara vez desea utilizar "es" para comparar. (La principal excepción es la comparación con None).

Compilar expresiones regulares

Es casi seguro que se trata de una mejora de rendimiento innecesaria, pero podría ser útil saber para más adelante:

La llamada a re.matchnecesita compilar la expresión regular cada vez que se llama. Puede precompilar la expresión regular una vez y luego llamar matchal objeto compilado para acelerarlo.

Globals

Casi cada vez que busco la globalpalabra clave, resulta ser un error.

No creo que sea necesario declarar los identificadores de demostración como globales; ya deberían estar disponibles para su uso (solo para leer; si intenta escribir en ellos, definirá una nueva variable en el nuevo alcance, ocultando los originales).