Cifrado Python 3 Vigenere
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
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:
- Bucle infinito
- solicitar información del usuario
- convertir a mayúsculas
- validar si la entrada está vacía (o en un conjunto de valores válidos)
- en caso de valor vacío, devuelve un valor predeterminado
- 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:
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 .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")Hay 2 funciones con nombres muy similares:
start_cipher(mode...)ystart_cipher_mode(mode). Esto hace que sea realmente difícil saber cuál está realmente iniciando el cifrado . ¿Quizás, tiene 2 funciones separadasencryptydecrypt?Usando la operación de módulo, puede eliminar el siguiente condicional:
if cipher_alphabet_index == len(cipher_alphabets): cipher_alphabet_index = 0y se vería así:
result.append(alphabet[cipher_alphabets[cipher_alphabet_index % alphabets_length].find(char)]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()))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.
@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).