Skip to content

Latest commit

 

History

History
277 lines (196 loc) · 12.5 KB

File metadata and controls

277 lines (196 loc) · 12.5 KB

6 用暴力破解凯撒密码

原文:https://inventwithpython.com/cracking/chapter6.html

*“阿拉伯学者发明了密码分析,即在不知道密钥的情况下解读信息的科学 —西蒙·辛格,*电码书

Images

我们可以通过使用一种叫做暴力破解的密码分析技术来破解凯撒密码。暴力破解攻击用每一个可能的密钥尝试对一个密码进行解密。没有什么可以阻止密码分析者猜测一个密钥,用那个密钥解密密文,查看输出,然后如果他们没有找到秘密消息,就继续下一个密钥。因为暴力破解技术对凯撒密码非常有效,所以您实际上不应该使用凯撒密码来加密秘密信息。

理想情况下,密文永远不会落入任何人手中。但是 Kerckhoffs 原则(以 19 世纪密码学家 Auguste Kerckhoffs 命名)指出,即使每个人都知道密码是如何工作的,并且其他人也有密文,密码仍然应该是安全的。这个原则被 20 世纪数学家克劳德·香农重述为香农的格言:“敌人知道这个系统。”密码中使信息保密的部分就是密钥,对于凯撒密码来说,这个信息很容易找到。

本章涵盖的主题

  • Kerckhoffs 原则和香农准则

  • 暴力破解技术

  • range()函数

  • 字符串格式(字符串插值)

凯撒密码破解程序的源代码

选择文件 -> 新文件,打开新文件编辑器窗口。在文件编辑器中输入以下代码,保存为caesarHacker.py。然后下载pyperclip.py模块(如果你还没有下载的话)(www.nostarch.com/crackingcodes)并把它放在与caesarCipher.py文件相同的目录(也就是同一个文件夹)中。这个模块将由caesarCipher.py导入。

完成文件设置后,按F5运行程序。如果您的代码遇到任何错误或问题,您可以在www.nostarch.com/crackingcodes使用在线比较工具将它与书中的代码进行比较。

caesarHacker.py

# Caesar Cipher Hacker
# https://www.nostarch.com/crackingcodes/ (BSD Licensed)

message = 'guv6Jv6Jz!J6rp5r7Jzr66ntrM'
SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz12345
      67890 !?.'

# Loop through every possible key:
for key in range(len(SYMBOLS)):
    # It is important to set translated to the blank string so that the
    # previous iteration's value for translated is cleared:
    translated = ''

    # The rest of the program is almost the same as the Caesar program:

    # Loop through each symbol in message:
    for symbol in message:
        if symbol in SYMBOLS:
            symbolIndex = SYMBOLS.find(symbol)
            translatedIndex = symbolIndex - key

            # Handle the wraparound:
            if translatedIndex < 0:
                translatedIndex = translatedIndex + len(SYMBOLS)

            # Append the decrypted symbol:
            translated = translated + SYMBOLS[translatedIndex]

        else:
            # Append the symbol without encrypting/decrypting:
            translated = translated + symbol

    # Display every possible decryption:
    print('Key #%s: %s' % (key, translated))

请注意,这段代码的大部分与最初的凯撒密码程序中的代码相同。这是因为凯撒密码破解程序使用相同的步骤来解密消息。

凯撒密码破解程序的运行示例

当您运行凯撒密码破解程序程序时,它会打印以下输出。它通过用所有 66 个可能的密钥解密密文来破解密文guv6Jv6Jz!J6rp5r7Jzr66ntrM:

Key #0: guv6Jv6Jz!J6rp5r7Jzr66ntrM
Key #1: ftu5Iu5Iy I5qo4q6Iyq55msqL
Key #2: est4Ht4Hx0H4pn3p5Hxp44lrpK
Key #3: drs3Gs3Gw9G3om2o4Gwo33kqoJ
Key #4: cqr2Fr2Fv8F2nl1n3Fvn22jpnI
--snip--
Key #11: Vjku?ku?o1?ugetgv?oguucigB
Key #12: Uijt!jt!nz!tfdsfu!nfttbhfA
Key #13: This is my secret message.
Key #14: Sghr0hr0lx0rdbqds0ldrrZfd?
Key #15: Rfgq9gq9kw9qcapcr9kcqqYec!
--snip--
Key #61: lz1 O1 O5CO wu0w!O5w  sywR
Key #62: kyz0Nz0N4BN0vt9v N4v00rxvQ
Key #63: jxy9My9M3AM9us8u0M3u99qwuP
Key #64: iwx8Lx8L2.L8tr7t9L2t88pvtO
Key #65: hvw7Kw7K1?K7sq6s8K1s77ousN

因为密钥13的解密输出是简单的英语,我们知道原始的加密密钥一定是13

设置变量

破解程序将创建一个message变量,存储程序试图解密的密文字符串。SYMBOLS常量包含密码可以加密的每个字符:

# Caesar Cipher Hacker
# https://www.nostarch.com/crackingcodes/ (BSD Licensed)

message = 'guv6Jv6Jz!J6rp5r7Jzr66ntrM'
SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz12345
      67890 !?.'

SYMBOLS的值需要与我们试图破解的加密密文的凯撒密码程序中使用的SYMBOLS的值相同;否则破解程序无法运行。注意,字符串值中的0!之间有一个空格。

range()函数循环

第 8 行是一个for循环,它不遍历字符串值,而是遍历对range()函数调用的返回值:

# Loop through every possible key:
for key in range(len(SYMBOLS)):

range()函数接受一个整数参数并返回一个数据类型为range的值。范围值可以用在for循环中,根据你给函数的整数循环特定的次数。让我们试一个例子。在交互式 shell 中输入以下内容:

>>> for i in range(3):
...   print('Hello')
...
Hello
Hello
Hello

for循环将循环三次,因为我们将整数3传递给了range()

更具体地说,从range()函数调用返回的范围值将把for循环的变量设置为从0到(但不包括)传递给range()的参数的整数。例如,在交互式 shell 中输入以下内容:

>>> for i in range(6):
...   print(i)
...
0
1
2
3
4
5

这段代码将变量i设置为从0到(但不包括)6的值,这类似于caesarHacker.py中第 8 行的操作。第 8 行用从0到(但不包括)66的值设置key变量。我们没有将值66直接硬编码到我们的程序中,而是使用来自len(SYMBOLS)的返回值,因此如果我们修改SYMBOLS,程序仍然会工作。

程序执行第一次经过这个循环时,key被设置为0,用密钥0解密message中的密文。(当然,如果0不是真正的密钥,message只是“解密”成废话。)从第 9 行到第 31 行的for循环中的代码,我们接下来将解释,类似于原始的凯撒密码程序并进行解密。在第 8 行的for循环的下一次迭代中,key被设置为1用于解密。

虽然我们不会在这个程序中使用它,但是您也可以向range()函数传递两个整数参数,而不是一个。第一个参数是范围应该开始的位置,第二个参数是范围应该停止的位置(直到但不包括第二个参数)。参数由逗号分隔:

>>> for i in range(2, 6):
...   print(i)
...
2
3
4
5

变量i将取从2(包括2)到6(不包括6))的值。

解密消息

接下来几行中的解密代码将解密后的文本添加到translated中的字符串末尾。在第 11 行,translated被设置为空字符串:

# Loop through every possible key:
for key in range(len(SYMBOLS)):
    # It is important to set translated to the blank string so that the
    # previous iteration's value for translated is cleared:
    translated = ''

在这个for循环的开始,我们将translated重置为空字符串,这一点很重要;否则,用当前密钥解密的文本将被添加到循环中最后一次迭代的translated解密文本中。

第 16 行到第 30 行几乎与第 5 章中的凯撒密码程序中的代码相同,但是稍微简单一些,因为这段代码只需要解密:

    # The rest of the program is almost the same as the Caesar program:

    # Loop through each symbol in message:
    for symbol in message:
        if symbol in SYMBOLS:
            symbolIndex = SYMBOLS.find(symbol)

在第 16 行,我们遍历存储在message中的密文字符串中的每个符号。在这个循环的每次迭代中,第 17 行检查symbol是否存在于SYMBOLS常量变量中,如果存在,就解密它。第 18 行的find()方法调用定位SYMBOLSsymbol所在的索引,并将其存储在一个名为symbolIndex的变量中。

然后我们从第 19 行的symbolIndex中减去key来解密:

            translatedIndex = symbolIndex - key

            # Handle the wraparound:
            if translatedIndex < 0:
                translatedIndex = translatedIndex + len(SYMBOLS)

这个减法操作可能会导致translatedIndex变得小于零,并且当我们在SYMBOLS中找到要解密的字符的位置时,需要我们“环绕”常量SYMBOLS。第 22 行检查这种情况,如果translatedIndex小于0,第 23 行增加66(这是len(SYMBOLS)返回的值)。

现在translatedIndex已经被修改,SYMBOLS[translatedIndex]将计算出解密后的符号。第 26 行将这个符号添加到存储在translated中的字符串的末尾:

            # Append the decrypted symbol:
            translated = translated + SYMBOLS[translatedIndex]

        else:
            # Append the symbol without encrypting/decrypting:
            translated = translated + symbol

如果在SYMBOL集合中没有找到值,第 30 行只是将未修改的symbol添加到translated的末尾。

使用字符串格式显示密钥和解密后的消息

尽管第 33 行是我们的 Caesar cipher hacker 程序中唯一的print()函数调用,但它将执行多次,因为它在第 8 行的for循环的每次迭代中被调用一次:

    # Display every possible decryption:
    print('Key #%s: %s' % (key, translated))

print()函数调用的参数是一个使用字符串格式化(也称为字符串插值)的字符串值。使用%s文本的字符串格式将一个字符串放在另一个字符串中。字符串中的第一个%s被字符串末尾括号中的第一个值替换。

在交互式 shell 中输入以下内容:

>>> 'Hello %s!' % ('world')
'Hello world!'
>>> 'Hello ' + 'world' + '!'
'Hello world!'
>>> 'The %s ate the %s that ate the %s.' % ('dog', 'cat', 'rat')
'The dog ate the cat that ate the rat.'

在这个例子中,首先将字符串'world'插入到字符串'Hello %s!'中,代替%s。它的工作原理就好像你已经将字符串中位于%s之前的部分与插入的字符串和位于%s之后的部分连接起来。当您插入多个字符串时,它们会按顺序替换每个%s

字符串格式通常比使用+操作符的字符串连接更容易键入,尤其是对于大型字符串。而且,与字符串连接不同,您可以将整数等非字符串值插入到字符串中。在交互式 shell 中输入以下内容:

>>> '%s had %s pies.' % ('Alice', 42)
'Alice had 42 pies.'
>>> 'Alice' + ' had ' + 42 + ' pies.'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly

当您使用插值时,整数42被插入到字符串中没有任何问题,但是当您尝试连接该整数时,它会导致错误。

caesarHacker.py的第 33 行使用字符串格式创建一个字符串,该字符串同时具有keytranslated变量的值。因为key存储一个整数值,所以我们使用字符串格式将它放入一个传递给print()的字符串值中。

总结

凯撒密码的致命弱点是没有很多用来加密的密钥。任何计算机都可以很容易地用所有 66 个可能的密钥进行解密,密码分析人员只需要几秒钟就可以在解密的信息中找到一个英文的。为了使我们的信息更安全,我们需要一个有更多潜在密钥的密码。第七章中讨论的换位密码可以为我们提供这种安全性。

练习题

练习题的答案可以在本书的网站www.nostarch.com/crackingcodes找到。

  1. 破解下面的密文,一次解密一行,因为每一行都有不同的密钥。请记住对任何引用字符进行转义:

    qeFIP?eGSeECNNS,
    5coOMXXcoPSZIWoQI,
    avnl1olyD4l'ylDohww6DhzDjhuDil,
    
    z.GM?.cEQc. 70c.7KcKMKHA9AGFK,
    ?MFYp2pPJJUpZSIJWpRdpMFY,
    ZqH8sl5HtqHTH4s3lyvH5zH5spH4t pHzqHlH3l5K
    
    Zfbi,!tif!xpvme!qspcbcmz!fbu!nfA
    ```*