Python 3 Vigenere 암호

Nov 02 2020

나는 여전히 파이썬에 익숙하지 않으며 모듈, 함수 등을 효율적으로 사용했는지 또는 무언가를 할 수있는 다른 / 쉬운 방법이 있는지 확인하려고합니다.

이 Python 3 Vigenere Cipher는 JavaScript 기반 암호를 재 구축하고 Windows를 기반으로합니다. 모든 알파벳 문자를 허용하며 데모 옵션이 내장되어 있습니다. Cipher 데모는 CIA Kryptos 암호의 1 단계를 사용합니다 .

#! 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()

답변

7 hjpotter92 Nov 02 2020 at 17:35

오두막

shebang은 일반적이어야합니다. 현재 python특정 시스템에서 python 2를 가리킬 수있는을 호출 하고 있습니다.

일반적인 가상 환경 친화적 인 Python shebang은 다음과 같습니다.

#!/usr/bin/env python3

PEP-8

PEP-8 가이드의 몇 가지 요점 :

  • 두 개의 빈 줄로 최상위 함수 및 클래스 정의를 묶습니다.
  • 논리 섹션을 나타내려면 함수에 빈 줄을 사용하십시오.
  • 상수는 일반적으로 모듈 수준에서 정의되며 단어를 구분하는 밑줄과 함께 모두 대문자로 작성됩니다.

PEP-484

유형 힌트 함수를 사용하면 함수를보다 쉽게 ​​수행 할 수 있습니다. PEP-484를 확인하십시오 .

if __name__ 블록

if __name__ == "__main__"블록 안에 스크립트의 실행 로직을 넣으십시오 . 자세한 설명 은 Stack Overflow에서 확인할 수 있습니다 .

중복 논리

코드에는 사용자 입력을 읽기위한 5 가지 기능이 있습니다. 그들 모두는 다음과 같은 일을합니다.

  1. 무한 루프
  2. 사용자 입력 요청
  3. 대문자로 변환
  4. 입력이 비어 있는지 (또는 유효한 값 집합에 있는지) 확인
  5. 값이 비어있는 경우 기본값을 반환합니다.
  6. 값으로 반환

이 모든 것은 단일 함수로 처리 할 수 ​​있습니다.

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

정규식 / 유효성 검사

입력 유효성 검사를위한 정규식은를 허용하는 _반면 오류 메시지는 명시 적으로 특수 문자가 없다고 말합니다. 위의 재 작성에 대한 확인은 다음을 사용하여 수행됩니다 (아래 설명에 따라 업데이트 됨).

value.isalpha() and value.isascii()

정규식보다 빠르게 수행됩니다 (사용자가 잘못된 값을 계속 입력하지 않는 한 \$ 10^ n \$미리 컴파일 된 패턴 약간 더 잘 수행 수있는 경우).

공연

코드의 성능을 높이기 위해 변경할 수있는 몇 가지 사항 :

  1. string에 연결 (추가)하는 대신 mode_string목록으로 푸시하고 끝에 "".join(). Stack Overflow에 대한 자세한 내용 .

  2. 프로그램이 Linux (* nix) 시스템을 지원하도록 만들 수도 있습니다. Windows에 대한 유일한 종속성은 cls. 아마도 ( Stack Overflow에서 가져옴 ) :

    def clear():
        os.system("cls" if os.name == "nt" else "clear")
    
  3. 이름이 매우 유사한 2 개의 함수가 있습니다 : start_cipher(mode...)start_cipher_mode(mode). 이로 인해 어떤 것이 진정으로 암호를 시작 하는지 알기가 정말 어렵습니다 . 아마도 두 개의 별도 기능이 encrypt있고 decrypt?

  4. 모듈로 연산을 사용하여 다음 조건을 제거 할 수 있습니다.

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

    다음과 같이 표시됩니다.

    result.append(alphabet[cipher_alphabets[cipher_alphabet_index % alphabets_length].find(char)]
    
  5. alphabet문자열을 사용하여 실제로 문자의 인덱스 값으로 작업하기 때문에 사전을 만드십시오. 사전 검색은 \$ O(1) \$\ 와 비교$ O(n) \$에 대한 .find(). 이것은 다음과 같습니다.

    from itertools import count
    alphabet_map = dict(zip(alphabet, count()))
    
  6. 위의 두 점에서 사용자 입력 후 문자 / 알파벳이 실제로 필요하지 않음이 분명합니다. 인덱스 값 모듈로만 중요합니다. 이것은 상당한 수학적 이해없이 이해 / 구현하기 어려울 수 있으므로 지금은 건너 뛸 수 있습니다.

1 Oddthinking Nov 03 2020 at 00:52

@hjpotter는 대부분의 의견을 다루었습니다.

부울

파이썬은 진실과 거짓 값 의 개념을 가지고 있으므로 값을 True 또는 False와 비교하는 것보다 직접 부울로 취급하는 것이 좋습니다.


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

다음과 같이 단순화 할 수 있습니다.

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

    elif string_is_alpha(alphabet) is False:

다음과 같이 단순화 할 수 있습니다.

    elif not string_is_alpha(alphabet):

일반적인 경우 비교를 위해 "is"를 거의 사용하지 않습니다. (주요 예외는와 비교하는 것 None입니다.)

정규식 컴파일

이것은 거의 확실히 불필요한 성능 향상이지만 나중에 알아두면 유용 할 수 있습니다.

에 대한 호출은 호출 될 re.match때마다 정규 표현식을 컴파일해야합니다. regexp를 한 번 미리 컴파일 한 다음 match컴파일 된 객체를 호출 하여 속도를 높일 수 있습니다.

글로벌

global키워드 를 찾을 때마다 거의 실수로 판명되었습니다.

데모 식별자를 전역으로 선언 할 필요는 없다고 생각합니다. 이미 사용할 수 있어야합니다 (읽기 전용-쓰기를 시도하면 새 범위에 새 변수를 정의하여 원본을 숨 깁니다).