【问题标题】:Struggling with ASCII Loop for Caesar Cipher与凯撒密码的 ASCII 循环作斗争
【发布时间】:2020-02-25 05:42:08
【问题描述】:

这是我在 stackoverflow 上发布的第一个问题,感谢任何和所有帮助/批评/帮助...我需要我能得到的所有帮助,哈哈。

我对编程很陌生。

目的是创建一个凯撒密码,对用户输入的字符串进行加密和解密,添加用户输入offset_value,然后将其改回字符。

我使用的是 ASCII 字符。问题是我需要将加密和解密隔离为 ASCII 32 ('a') - ASCII 126 ('~')。我不确定如何创建一个循环返回 94 个字符的函数。

因此,例如,如果 char 是 'Z',即 ASCII ord 94,如果我们添加用户输入 offset_value,这可能是 90,这将使 ord 184。超出范围。

这导致了真正的问题,即暴力加密。它工作......有点。它需要显示所有可能的结果,offset_value 在 1 和 94 之间变化。例如,如果我们用 x 的offset_value(x 是 1-94 之间的任何数字)解密每个字母会发生什么。

相反,它只是不断上升。

这些有意义吗?

我的代码如下。我知道我还没有创建任何函数,但我会的。

提前谢谢各位。

choice = 0
list1 = [1, 2, 3, 4]
list2 = list(range(1, 95))
new_ord = 0
index = 1
encryption = ''
decryption = ''
offset_value = 1


#while loop allows for multiple use, option 4 ends loop
while choice != 4:
    print('*** Menu ***')
    print('\r')
    print('1. Encrypt string')
    print('2. Decrypt string')
    print('3. Brute force decryption')
    print('4. Quit')
    print('\r')
    choice = int(input('What would you like to do [1,2,3,4]? '))

    #invalid user input loop, valid entry ends loop
    while choice not in list1:
        print('\r')
        print('Invalid choice, please enter either 1, 2, 3 or 4.')
        print('\r')
        choice = int(input('What would you like to do [1,2,3,4]? '))

    #user chooses 'encrypt string', stores data
    if choice == 1:
        print('\r')
        string_to_encrypt = str(input('Please enter string to encrypt: '))
        offset_value = int(input('Please enter offset value (1 to 94): '))

        #invalid user input loop, valid entry ends loop
        while offset_value not in list2:
            offset_value = int(input('Please enter offset value (1 to 94): '))

        #encryption loop for length of string_to_encrypt
        for letter in string_to_encrypt:
            encryption = encryption + chr((ord(letter) + offset_value))

        #prints encrypted string
        print('\r')
        print('Encrypted string:')
        print(encryption)
        print('\r')

        #clears ecryption data
        encryption = ''

    #user chooses 'decrypt string', stores data
    elif choice == 2:
        print('\r')
        string_to_decrypt = str(input('Please enter string to decrypt: '))
        offset_value = int(input('Please enter offset value (1 to 94): '))

        #invalid user input loop, valid entry ends loop
        while offset_value not in list2:
            offset_value = int(input('Please enter offset value (1 to 94): '))

        #decryption loop for length of string_to_decrypt
        for letter in string_to_decrypt:
                decryption = decryption + chr((ord(letter) - offset_value))

        #prints decrypted string
        print('\r')
        print('Decrypted string:')
        print(decryption)
        print('\r')

        #clears decryption data
        decryption = ''

    #user chooses 'Brute Force Decryption
    elif choice == 3:
        string_to_decrypt = str(input('Please enter string to decrypt: '))
        for number in range(94):
            for letter in string_to_decrypt:
                decryption = decryption + chr((ord(letter) - offset_value))
            print('Offset: ', index, '= Decrypted String: ', decryption)
            offset_value = offset_value + 1
            index = index + 1
            decryption = ''

    #user chooses 'quit'
        print('Goodbye.')

【问题讨论】:

  • ascii 表上的 32 映射到一个空格(''),而不是 'a'(即 97)。此外,该字符范围有 95 个字符,而不是 94 个(假设 126 包括在内)。

标签: python python-3.x encryption ascii caesar-cipher


【解决方案1】:

我将描述如何解决这样的问题,而不是为你重写程序,这主要涉及做一些数学运算。

您的问题的本质归结为如何将给定的整数值范围偏移给定数量并包装结果,使它们仍然在原始值范围内。

这可以通过减去范围中的最小值,添加偏移量,计算范围内的高值和低值之间的差值 (+1) 的模数来计算范围内的每个值,以及然后最后加回最小值以补偿最初减去它。关键部分是 modulo % 操作来约束总和。

下面offset_range() 函数中的代码展示了如何在list comprehension 中执行所有这些操作,这是在Python 中创建列表的一种简写方式。

这和print_range() 函数也用于说明如何在 Python 中定义函数。

def print_range(name, range_):
    """ Pretty-print given range of character ordinals. """
    ints = ', '.join('{:3d}'.format(val) for val in range_)
    chrs = ', '.join('{:>}'.format(repr(chr(val))) for val in range_)
    print('{}: [{}]'.format(name, ints))
    print('{}  [{}]'.format(len(name)*' ', chrs))

def offset_range(offset_value, range_):
    """ Add offset value to integers in given range and wrap result. """
    min_val, max_val = range_[0], range_[-1]  # Get upper and lower limits.
    diff_plus_1 = (max_val - min_val) + 1  # Their difference + 1 for computation.
    # Use a list comprehension to create new list.
    offset_range = [(((val - min_val) + offset_value) % diff_plus_1) + min_val
                        for val in range_]
    return offset_range

ascii_range = list(range(ord('a'), ord('~')+1))
print_range('ascii_range', ascii_range)

print()
offset_range = offset_range(1, ascii_range)
print_range('offset_range', offset_range)

输出:

ascii_range: [ 97,  98,  99, 100, 101, 102, 103, 104, 105, ... 122, 123, 124, 125, 126]
             ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', ... 'z', '{', '|', '}', '~']

offset_range: [ 98,  99, 100, 101, 102, 103, 104, 105, ... 122, 123, 124, 125, 126,  97]
              ['b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', ... 'z', '{', '|', '}', '~', 'a']

【讨论】:

    【解决方案2】:

    凯撒密码使用索引从 0 开始的字母表。因此您需要一些函数将一个从 32 到 126 的范围转换为 0 到 126 - 32 = 98。然后您需要执行计算 mod 99 因为有 99 个元素,索引为 0 到 98。

    一般来说,编程的想法是通过将问题分解为多个功能来解决问题。大多数答案已经假设您流利并且做到了。但是,这通常只会让您感到困惑,并让您了解如何编程。

    所以下面我写出了加密过程。现在,如果您创建 decryption 过程,那么您可以重新使用decrypt 方法进行蛮力尝试。您会发现现在正确地做事要容易得多。

    LO_CHAR = ord('A')
    HI_CHAR = ord('~')
    CHARS = HI_CHAR - LO_CHAR + 1
    
    def char_to_index(char):
        o = ord(char)
        if (o < LO_CHAR or o > HI_CHAR):
           raise
        return o - LO_CHAR
    
    def index_to_char(index):
        if (index < 0 or index >= CHARS):
           raise
        return chr(index + LO_CHAR)
    
    def encrypt_index(key, index):
        return (index + key) % CHARS
    
    def encrypt(key, plaintext):
        ciphertext = ''
        for plain_char in plaintext:
            plain_index = char_to_index(plain_char)
            cipher_index = encrypt_index(key, plain_index)
            cipher_char = index_to_char(cipher_index)
            ciphertext = ciphertext + cipher_char
        return ciphertext
    

    ...快乐的编程...

    【讨论】:

    • 是的,因为我在使用 Python 时总是遇到一些启动问题,所以在创建这个程序时我确实收到了 raise。只是向您展示为什么需要这样的 guard statements
    【解决方案3】:

    已经提供了一些使用ordinal % len(alphabet) 的解决方案。我想提出一个稍微不同的解决方案。整个解决方案围绕str.maketrans 展开,它可以提供一种简单的实现密码的方法。

    如果您将两个长度相等的字符串传入str.maketrans,它会返回一个字典,该字典将第一个字符的序数值映射到第二个字符的序数值,按照它们出现的顺序.

    例如:

    >>> str.maketrans("ABC", "DEF")
    {65: 68, 66: 69, 67: 70}
    >>> 
    

    大写“A”(65) -> 大写“D”(68)。等等。您已经可以看到如何使用它来实现您的凯撒密码 - 只要您可以生成两个等长的字符串,其中第二个字符串从第一个字符串偏移一定量(然后环绕到开始),您可以轻松地生成其中一个翻译表(文档在多个地方都这么称呼它,但它只是一个普通的旧 Python 字典)并让它完成大部分繁重的工作。

    我的解决方案:

    get_alphabet,给定最小和最大序数,返回一个包含所需字母表中所有字符的字符串。

    get_offset_alphabet,给定一个字母表(字符串)和一个偏移量(整数),返回传入的字母表,但偏移量和包装。这是通过使用itertools.cycle 创建一个无限的字符迭代器来实现的,根据偏移量从迭代器的开头切掉并丢弃一些数量,然后从迭代器的下一个 n 字符构造一个等长的字符串 (其中'n'是原始字母的长度)。这也支持负偏移。

    main 中,我们得到了两个字母 - 第一个是不变的,第二个是偏移和包装的。我们创建一个翻译表(字典),断言我们明文中的所有字符都可以根据我们的翻译表映射到其他字符,然后执行实际翻译以产生密文。 str.maketrans 还有第三个可选参数,可用于将没有映射到None 的字符映射到None,这里我没有利用。

    def get_alphabet(min_ordinal, max_ordinal):
        assert isinstance(min_ordinal, int)
        assert isinstance(max_ordinal, int)
        assert 0 <= min_ordinal <= 255
        assert 0 <= max_ordinal <= 255
        assert min_ordinal <= max_ordinal
        return "".join(map(chr, range(min_ordinal, max_ordinal+1)))
    
    def get_offset_alphabet(alphabet, *, offset):
        assert isinstance(alphabet, str)
        assert isinstance(offset, int)
        assert alphabet
    
        from itertools import cycle, islice
        char_iterator = cycle(alphabet)
        _ = list(islice(char_iterator, [offset, len(alphabet)+offset][offset<0]))
        return "".join(islice(char_iterator, len(alphabet)))
    
    def main():
    
        from_alphabet = get_alphabet(32, 126)
        to_alphabet = get_offset_alphabet(from_alphabet, offset=1)
    
        translation = str.maketrans(from_alphabet, to_alphabet)
    
        plaintext = "Hello World"
        assert all(ord(char) in translation for char in plaintext)
    
        ciphertext = plaintext.translate(translation)
        print(ciphertext)
    
        return 0
    
    
    if __name__ == "__main__":
        import sys
        sys.exit(main())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-03
      • 1970-01-01
      • 2021-10-30
      • 1970-01-01
      相关资源
      最近更新 更多