当前位置: 首页 > news >正文

网站的换肤功能怎么做dede旅游网站源码 多城市

网站的换肤功能怎么做,dede旅游网站源码 多城市,网页设计师岗位分析,中国人在俄罗斯做网站需要多少卢布协议#xff1a;CC BY-NC-SA 4.0 译者#xff1a;飞龙 本文来自【OpenDocCN 饱和式翻译计划】#xff0c;采用译后编辑#xff08;MTPE#xff09;流程来尽可能提升效率。 收割 SB 的人会被 SB 们封神#xff0c;试图唤醒 SB 的人是 SB 眼中的 SB。——SB 第三定律 五、凯… 协议CC BY-NC-SA 4.0 译者飞龙 本文来自【OpenDocCN 饱和式翻译计划】采用译后编辑MTPE流程来尽可能提升效率。 收割 SB 的人会被 SB 们封神试图唤醒 SB 的人是 SB 眼中的 SB。——SB 第三定律 五、凯撒密码 原文https://inventwithpython.com/cracking/chapter5.html “老大哥在看着你。” ——乔治·奥威尔1984 年 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FI7hKBWA-1692873504732)(https://gitcode.net/OpenDocCN/invent-with-python-zh/-/raw/master/docs/cracking/img/3e754c09a1a42c45ac36ea03cdd9684e.png)] 在第 1 章中我们使用了一个密码轮和一个字母数字表来实现凯撒密码。在这一章中我们将在计算机程序中实现凯撒密码。 我们在第四章中制作的反向密码总是以同样的方式加密。但是凯撒密码使用密钥根据使用的密钥不同加密信息的方式也不同。凯撒密码的密钥是从0到25的整数。即使密码分析人员知道使用了凯撒密码这也不足以给他们充分的信息来破解密码除非他们知道对应的密钥。 本章涵盖的主题 import语句 常量 for循环 if, else和elif语句 in和not in运算符 find()字符串方法 凯撒密码程序的源代码 在文件编辑器中输入以下代码并保存为caesarCipher.py。然后从www.nostarch.com/crackingcodes下载pyperclip.py模块放在与文件caesarCipher.py相同的目录即同一个文件夹下。该模块将被caesarCipher.py导入我们将在第 56 页的[的导入模块和设置变量中对此进行更详细的讨论。 完成文件设置后按F5运行程序。如果您的代码遇到任何错误或问题您可以在www.nostarch.com/crackingcodes使用在线比较工具将它与书中的代码进行比较。 *caesarCipher.py # Caesar Cipher # https://www.nostarch.com/crackingcodes/ (BSD Licensed)import pyperclip# The string to be encrypted/decrypted: message This is my secret message.# The encryption/decryption key: key 13# Whether the program encrypts or decrypts: mode encrypt # Set to either encrypt or decrypt.# Every possible symbol that can be encrypted: SYMBOLS ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.# Store the encrypted/decrypted form of the message: translated for symbol in message:# Note: Only symbols in the SYMBOLS string can beencrypted/decrypted.if symbol in SYMBOLS:symbolIndex SYMBOLS.find(symbol)# Perform encryption/decryption:if mode encrypt:translatedIndex symbolIndex keyelif mode decrypt:translatedIndex symbolIndex - key# Handle wraparound, if needed:if translatedIndex len(SYMBOLS):translatedIndex translatedIndex - len(SYMBOLS)elif translatedIndex 0:translatedIndex translatedIndex len(SYMBOLS)translated translated SYMBOLS[translatedIndex]else:# Append the symbol without encrypting/decrypting:translated translated symbol# Output the translated string: print(translated) pyperclip.copy(translated)凯撒密码程序的运行示例 当您运行caesarCipher.py程序时输出如下: guv6Jv6Jz!J6rp5r7Jzr66ntrM输出是使用密钥13进行凯撒密码加密的字符串This is my secret message.。您刚才运行的凯撒密码程序会自动将这个加密字符串复制到剪贴板以便您可以将其粘贴到电子邮件或文本文件中。因此您可以轻松地将程序的加密输出发送给其他人。 运行该程序时您可能会看到以下错误信息: Traceback (most recent call last):File C:\caesarCipher.py, line 4, in moduleimport pyperclip ImportError: No module named pyperclip如果是这样你可能没有将pyperclip.py模块下载到正确的文件夹中。如果您确认pyperclip.py在带有caesarCipher.py的文件夹中但仍然无法让模块工作只需在caesarCipher.py程序的第 4 行和第 45 行代码其中有文本pyperclip前面加上一个#就可以了。这使得 Python 忽略了依赖于pyperclip.py模块的代码允许程序成功运行。请注意如果您注释掉该代码加密或解密的文本不会在程序结束时复制到剪贴板。你也可以在以后的章节中注释掉程序中的pyperclip代码这也将从那些程序中移除复制到剪贴板的功能。 要解密消息只需将输出文本作为新值粘贴到第 7 行的message变量中。然后修改第 13 行的赋值语句将字符串decrypt存储在变量mode中: # The string to be encrypted/decrypted: message guv6Jv6Jz!J6rp5r7Jzr66ntrM# The encryption/decryption key: key 13# Whether the program encrypts or decrypts: mode decrypt # Set to either encrypt or decrypt.当您现在运行该程序时输出如下所示: This is my secret message.导入模块和设置变量 尽管 Python 包含许多内置函数但有些函数存在于称为模块的独立程序中。模块是 Python 程序包含你的程序可以使用的附加函数。我们用恰如其名的import语句导入模块该语句由import关键字和模块名组成。 第 4 行包含一个import语句: # Caesar Cipher # https://www.nostarch.com/crackingcodes/ (BSD Licensed)import pyperclip在本例中我们导入了一个名为pyperclip的模块这样我们就可以在程序的后面调用pyperclip.copy()函数。pyperclip.copy()函数会自动将字符串复制到你电脑的剪贴板这样你就可以方便地将它们粘贴到其他程序中。 在caesarCipher.py中接下来的几行设置了三个变量: # The string to be encrypted/decrypted: message This is my secret message.# The encryption/decryption key: key 13# Whether the program encrypts or decrypts: mode encrypt # Set to either encrypt or decrypt.message变量存储要加密或解密的字符串key变量存储加密密钥的整数。mode变量要么存储字符串encrypt让程序后面的代码加密message中的字符串要么存储decrypt让程序解密而不是加密。 常量和变量 常量是程序运行时其值不应改变的变量。例如凯撒密码程序需要一个字符串该字符串包含可以用这个凯撒密码加密的每个可能的字符。因为该字符串不应该改变所以我们将它存储在第 16 行名为SYMBOLS的常量变量中: # Every possible symbol that can be encrypted: SYMBOLS ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.符号是密码学中的常用术语指密码可以加密或解密的单个字符。一个符号集是一个密码用来加密或解密的每一个可能的符号。因为我们将在这个程序中多次使用符号集并且因为我们不想每次在程序中出现时都键入完整的字符串值我们可能会输入错误这将导致错误所以我们使用一个常量变量来存储符号集。我们输入一次字符串值的代码并将其放入SYMBOLS常量中。 注意SYMBOLS全是大写字母这是常量的命名约定。虽然我们可以改变SYMBOLS就像任何其他变量一样但是全大写的名字提醒程序员不要写这样做的代码。 就像所有的惯例一样我们不需要必须遵循这个惯例。但是这样做可以让其他程序员更容易理解这些变量是如何使用的。它甚至可以帮助你以后查看自己的代码。 在第 19 行程序在一个名为translated的变量中存储一个空字符串这个变量稍后将存储加密或解密的消息: # Store the encrypted/decrypted form of the message: translated 就像在第五章的中的反向密码一样在程序结束时translated变量将包含完全加密或解密的消息。但是现在它以一个空字符串开始。 ###for循环语句 在第 21 行我们使用了一种叫做for循环的循环: for symbol in message:回想一下只要某个条件为True一个while循环就会循环。for循环的目的略有不同它没有像while循环那样的条件。相反它在一个字符串或一组值上循环。图 5-1 显示了一个for回路的六个部分。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpnLQC2Z-1692873504732)(https://gitcode.net/OpenDocCN/invent-with-python-zh/-/raw/master/docs/cracking/img/578f225b6e3836fb8288381140c9ad83.png)] 图 5-1for循环语句的六个部分 每次程序执行循环时也就是说在循环的每次迭代中for语句中的变量在第 21 行中是symbol取包含字符串的变量中的下一个字符的值在本例中是message。for语句类似于赋值语句因为变量被创建并赋值除了for语句循环不同的值来给变量赋值之外。 一个for循环的例子 例如在交互式 shell 中键入以下内容。注意在您键入第一行之后提示符将会消失在我们的代码中表示为...因为 shell 期望在for语句的冒号之后有一段代码。在交互式 shell 中当您输入一个空行时该块将结束: for letter in Howdy: ... print(The letter is letter) ... The letter is H The letter is o The letter is w The letter is d The letter is y这段代码循环遍历字符串Howdy中的每个字符。当它开始时变量letter按顺序一次一个地取Howdy中每个字符的值。为了看到这一点我们在循环中编写了代码为每次迭代打印出letter的值。 等价于for循环的while循环 for循环非常类似于while循环但是当你只需要迭代一个字符串中的字符时使用for循环更有效。通过编写更多的代码您可以使while循环像for循环一样: i 0 # ➊ while i len(Howdy): # ➋... letter Howdy[i] # ➌... print(The letter is letter) # ➍ ➎ ... i i 1...The letter is HThe letter is oThe letter is wThe letter is dThe letter is y注意这个while循环与for循环的工作结果相同但是没有for循环那么简短。首先我们在while语句 ➊ 前设置一个新变量i为0。该while语句有一个条件只要变量i小于字符串Howdy ➋ 的长度该条件将求值为True。因为i是一个整数并且只跟踪字符串中的当前位置我们需要声明一个单独的letter变量来保存字符串中位于i位置 ➌ 的字符。然后我们可以打印出letter的当前值以获得与for循环 ➍ 相同的输出。当代码执行完毕后我们需要通过增加1来增加i以移动到下一个位置 ➎ 。 要理解caesarCipher.py中的第 23 行和第 24 行您需要了解if、elif和else语句、in和not in操作符以及find()字符串方法。我们将在接下来的章节中讨论这些。 if语句 凯撒密码中的第 23 行有另一种 Python 指令——if语句: if symbol in SYMBOLS:你可以把一个if语句理解为“如果这个条件是True执行下面块中的代码。否则如果是False跳过这个代码块。”一个if语句的格式是使用关键字if后跟一个条件再跟一个冒号:。与循环一样要执行的代码缩进在一个块中。 if语句的案例 让我们尝试一个if语句的例子。打开一个新的文件编辑器窗口输入以下代码保存为checkPw.py : checkPw.py print(Enter your password.)typedPassword input() # ➊if typedPassword swordfish: # ➋print(Access Granted) # ➌print(Done) # ➍当你运行这个程序时它会显示文本Enter your password.并让用户输入密码。然后密码被存储在变量typedPassword ➊ 中。接下来if语句检查密码是否等于字符串swordfish ➋。如果是执行移到跟随if语句的块内向用户 ➌ 显示文本Access Granted否则如果typedPassword不等于swordfish执行将跳过if语句的块。无论哪种方式执行都继续到if块之后的代码以显示Done ➍。 else语句 通常我们想要测试一个条件如果条件是True就执行一段代码如果条件是False就执行另一段代码。我们可以在if语句块后使用else语句如果if语句的条件为False则else语句的代码块将被执行。对于一个else语句您只需编写关键字else和一个冒号:。它不需要条件因为如果if语句的条件不为真它就会运行。您可以将代码读作“如果这个条件是True则执行这个块否则如果是False则执行另一个块。” 修改checkPw.py程序如下所示新行以粗体显示: checkPw.py print(Enter your password.)typedPassword input()if typedPassword swordfish: # ➊print(Access Granted)else:print(Access Denied) # ➋print(Done) # ➌这个版本的程序几乎和以前的版本一样。如果if语句的条件为True ➊文本Access Granted仍将显示。但是现在如果用户键入除了swordfish之外的东西if语句的条件将是False导致执行进入else语句的块并显示Access Denied ➋。无论哪种方式执行仍将继续并显示Done ➌。 elif语句 另一个语句叫做elif语句也可以和if成对出现。就像一个if语句它有一个条件。像一个else语句一样它跟随一个if或另一个elif语句如果前一个if或elif语句的条件为False则执行该语句。您可以将if、elif和else语句理解为“如果这个条件是True运行这个块。否则检查该下一个条件是否为True。否则就跑完这最后一个代码块。”任意数量的elif语句可以跟在if语句之后。再次修改checkPw.py程序使其看起来如下: checkPw.py print(Enter your password.)typedPassword input()if typedPassword swordfish: # ➊print(Access Granted) # ➋elif typedPassword mary: # ➌print(Hint: the password is a fish.)elif typedPassword 12345: # ➍print(That is a really obvious password.)else:print(Access Denied)print(Done)该代码包含四个模块分别用于if、elif和else语句。如果用户输入12345则typedPassword swordfish的计算结果为False ➊因此跳过第一个带有print(Access Granted) ➋ 的块。接下来执行检查typedPassword mary条件该条件也求值为False ➌因此第二个块也被跳过。typedPassword 12345条件是True ➍因此执行进入该elif语句之后的块以运行代码print(That is a really obvious password.)并跳过任何剩余的elif和else语句。注意这些程序块中有且只有一个会被执行。 在一个if语句之后可以有零个或多个elif语句。您可以有零个或一个但不是多个else语句并且else语句总是最后一个因为它只在没有一个条件求值为True时执行。具有True条件的第一条语句执行其块。其余的条件即使它们也是True没有被检查。 in和not in运算符 caesarCipher.py中的第 23 行也使用了in操作符: if symbol in SYMBOLS:一个in操作符可以连接两个字符串如果第一个字符串在第二个字符串内它将计算为True否则计算为False。in操作符也可以与not配对后者的作用正好相反。在交互式 shell 中输入以下内容: hello in hello world!True hello not in hello world!False ello in hello world!True HELLO in hello world! # ➊False in Hello # ➋True注意in和not in操作符区分大小写 ➊。此外空白字符串总是被认为是在任何其他字符串 ➋ 中。 如果一个字符串存在于另一个字符串中使用in和not in操作符的表达式可以方便地用作if语句的条件来执行一些代码。 返回到caesarCipher.py第 23 行检查symbol中的字符串第 21 行的for循环将其设置为来自message字符串的单个字符是否在SYMBOLS字符串中该密码程序可以加密或解密的所有字符的符号集。如果symbol在SYMBOLS中执行进入从第 24 行开始的下一个程序块。如果不是执行将跳过这个块转而进入第 39 行的else语句后面的块。根据符号是否在符号集中密码程序需要运行不同的代码。 find()字符串方法 第 24 行找到了SYMBOLS字符串中的索引其中symbol是: symbolIndex SYMBOLS.find(symbol)这段代码包含一个方法调用。方法就像函数一样只不过它们附加了一个带句点的值或者在第 24 行一个包含值的变量。这个方法的名字是find()它被存储在SYMBOLS字符串值中用于调用。 大多数数据类型如字符串都有方法。find()方法接受一个字符串参数并返回该参数在方法字符串中出现位置的整数索引。在交互式 shell 中输入以下内容: hello.find(e)1 hello.find(o)4 spam hello spam.find(h)0 # ➊你可以在一个字符串或者一个包含字符串值的变量上使用find()方法。记住 Python 中的索引是从0开始的所以当find()返回的索引是字符串中的第一个字符时就会返回一个0➊。 如果找不到字符串参数find()方法返回整数-1。在交互式 shell 中输入以下内容: hello.find(x)-1 hello.find(H) # ➊-1注意find()方法也区分大小写 ➊。 作为参数传递给find()的字符串可以超过一个字符。find()返回的整数将是找到参数的第一个字符的索引。在交互式 shell 中输入以下内容: hello.find(ello) 1hello.find(lo) 3hello hello.find(e) 1字符串方法类似于使用in操作符的一个更具体的版本。它不仅告诉你一个字符串是否存在于另一个字符串中还告诉你在哪里。 加密和解密符号 既然你已经理解了if、elif和else语句in运算符和find()字符串方法这将更容易理解凯撒密码程序的其余部分是如何工作的。 密码程序只能加密或解密符号集中的符号: if symbol in SYMBOLS:symbolIndex SYMBOLS.find(symbol)所以在运行第 24 行的代码之前程序必须弄清楚symbol是否在符号集中。然后可以在SYMBOLS中找到symbol所在的索引。find()调用返回的索引存储在symbolIndex中。 现在我们已经将当前符号的索引存储在symbolIndex中我们可以对它进行加密或解密运算。凯撒密码将密钥号添加到符号的索引中进行加密或者从符号的索引中减去密钥号进行解密。该值存储在translatedIndex中因为它将是已翻译符号在SYMBOLS中的索引。 caesarCipher.py # Perform encryption/decryption:if mode encrypt:translatedIndex symbolIndex keyelif mode decrypt:translatedIndex symbolIndex - keymode变量包含一个字符串告诉程序应该加密还是解密。如果这个字符串是encrypt那么第 27 行的if语句的条件将是True执行第 28 行将key加上symbolIndex跳过elif语句后的块。否则如果mode是decrypt则执行第 30 行减去key。 处理绕回 当我们在第一章中用纸和笔实现凯撒密码时有时增加或减少密钥会导致一个大于或等于符号集大小或小于零的数。在这些情况下我们必须增加或减少符号集的长度以便它能够“绕回”或者返回到符号集的开头或结尾。我们可以使用代码len(SYMBOLS)来做这件事它返回66即SYMBOLS字符串的长度。第 33 到 36 行在密码程序中处理这种绕回。 # Handle wraparound, if needed:if translatedIndex len(SYMBOLS):translatedIndex translatedIndex - len(SYMBOLS)elif translatedIndex 0:translatedIndex translatedIndex len(SYMBOLS)如果translatedIndex大于等于66则第 33 行的条件为True执行第 34 行跳过第 35 行的elif语句。从translatedIndex中减去SYMBOLS的长度将变量的索引指向SYMBOLS字符串的开头。否则 Python 会检查translatedIndex是否小于0。如果条件是True则执行第 36 行并且translatedIndex绕到SYMBOLS字符串的末尾。 你可能想知道为什么我们不直接使用整数值66而不是len(SYMBOLS)。通过使用len(SYMBOLS)而不是66我们可以添加或删除SYMBOLS中的符号代码的其余部分仍然可以工作。 现在您已经在translatedIndex中有了translated变量中符号集的索引SYMBOLS[translatedIndex]将对translated变量中符号集求值。第 38 行使用字符串连接将这个加密/解密的符号添加到translated字符串的末尾: translated translated SYMBOLS[translatedIndex]最终translated字符串将是整个编码或解码的消息。 处理符号集外的符号 message字符串可能包含不在SYMBOLS字符串中的字符。这些字符在密码程序的符号集之外无法加密或解密。相反它们将被直接追加到translated字符串中这发生在第 39 到 41 行: else:# Append the symbol without encrypting/decrypting:translated translated symbol第 39 行的else语句有四个缩进空间。如果您查看上面行的缩进您会看到它与第 23 行的if语句成对出现。尽管在这个if和else语句之间有很多代码但它们都属于同一个代码块。 如果第 23 行的if语句的条件是False该块将被跳过程序执行将从第 41 行开始进入else语句的块。这个else块只有一行。它将未更改的symbol字符串添加到translated的末尾。结果符号集之外的符号例如%或(被添加到翻译后的字符串中而没有被加密或解密。 显示和复制翻译后的字符串 第 43 行没有缩进这意味着它是从第 21 行开始的块for循环的块之后的第一行。当程序执行到第 44 行时它已经遍历了message字符串中的每个字符加密或解密了这些字符并将它们添加到translated: # Output the translated string: print(translated) pyperclip.copy(translated)第 44 行调用print()函数在屏幕上显示translated字符串。注意这是整个程序中唯一的print()调用。计算机做了大量的工作来加密message中的每个字母处理绕回以及处理非字母字符。但是用户不需要看到这些。用户只需要在translated中看到最后一个字符串。 第 45 行调用copy()它接受一个字符串参数并将其复制到剪贴板。因为copy()是pyperclip模块中的一个函数我们必须通过在函数名前面加上pyperclip.来告诉 Python 这一点。如果我们输入copy(translated)而不是pyperclip.copy(translated)Python 会给我们一个错误消息因为它找不到这个函数。 如果你在试图调用pyperclip.copy()之前忘记了import pyperclip行第 4 行Python 也会给出错误信息。 这就是整个凯撒密码程序。当您运行它时请注意您的计算机在不到一秒的时间内如何执行整个程序并加密字符串。即使你输入一个很长的字符串存储在message变量中你的计算机也能在一两秒钟内加密或解密消息。相比之下使用密码轮需要几分钟的时间。该程序甚至自动将加密文本复制到剪贴板这样用户就可以简单地将其粘贴到电子邮件中发送给某人。 加密其他符号 我们实现的凯撒密码的一个问题是它不能加密其符号集之外的字符。例如如果您用密钥20加密字符串Be sure to bring the $$$.消息将加密到VyQ?A!yQ.9Qv!381Q.2yQ$$$T。这个加密的消息并没有隐藏你指的是$$$。然而我们可以修改程序来加密其他符号。 通过改变存储在SYMBOLS中的字符串以包含更多的字符程序也将对它们进行加密因为在第 23 行条件symbol in SYMBOLS将是True。在这个新的、更大的SYMBOLS常量变量中symbolIndex的值将是symbol的索引。“环绕”将需要增加或减少这个新字符串中的字符数但这已经得到了处理因为我们使用了len(SYMBOLS)而不是直接在代码中键入66这就是为什么我们这样编写代码的原因。 例如您可以将第 16 行扩展为: SYMBOLS ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.[email protected]#$%^*()_-[]{}|;:,/请记住消息必须使用相同的符号集进行加密和解密。 总结 您已经学习了几个编程概念并通读了相当多的章节现在您有了一个实现秘密密码的程序。更重要的是您了解这些代码是如何工作的。 模块是包含有用函数的 Python 程序。要使用这些函数您必须首先使用一个import语句导入它们。要调用导入模块中的函数在函数名前加一个句点像这样:module.function()。 常量变量按照约定用大写字母书写。这些变量并不意味着它们的值被改变尽管没有什么能阻止程序员编写这样做的代码。常量很有帮助因为它们为程序中的特定值提供了一个“名称”。 方法是附加到特定数据类型的值的函数。find()字符串方法返回传递给它的字符串参数在被调用的字符串中的位置的整数值。 您了解了几种新的方法来控制哪些代码行运行以及每行运行多少次。一个for循环遍历一个字符串值中的所有字符在每次迭代中为每个字符设置一个变量。if、elif和else语句根据条件是True还是False来执行代码块。 in和not in运算符检查一个字符串是否在另一个字符串中并相应地对True或False求值。 学习编程方法让你有能力用计算机能理解的语言写下像用凯撒密码加密或解密这样的过程。一旦计算机知道如何执行这个过程它就能比任何人做得更快而且不会出错除非错误出在你的程序中。尽管这是一项非常有用的技能但事实证明知道如何编程的人可以轻松破解凯撒密码。在第 6 章中你将使用你所学的技能编写一个凯撒密码黑客这样你就可以读取其他人加密的密文。让我们继续学习如何破解加密。 练习题 练习题的答案可以在本书的网站www.nostarch.com/crackingcodes找到。 使用caesarCipher.py用给定的密钥加密以下句子: You can show black is white by argument, said Filby, but you will never convince me.用密钥8 1234567890用密钥21 使用caesarCipher.py用给定的密钥解密以下密文: Kv?uqwpfu?rncwukdng?gpqwijB用密钥2 XCBSw88S18A1S 2SB41SE .8zSEwAS50D5A5x81V用密钥22 哪个 Python 指令会导入一个名为watermelon.py的模块 下列代码在屏幕上显示了什么 spam foo for i in spam:spam spam i print(spam)if 10 5:print(Hello) elif False:print(Alice) elif 5 ! 5:print(Bob) else:print(Goodbye)print(f not in foo)print(foo in f)print(hello.find(oo))六、暴力破解凯撒密码 原文https://inventwithpython.com/cracking/chapter6.html “阿拉伯学者发明了密码分析即在不知道密钥的情况下解读信息的科学。” ——西蒙·辛格《密码之书》 我们可以通过使用一种叫做暴力破解的密码分析技术来破解凯撒密码。暴力破解攻击用每一个可能的密钥尝试对一个密码进行解密。没有什么可以阻止密码分析者猜测一个密钥用那个密钥解密密文查看输出然后如果他们没有找到秘密消息就继续下一个密钥。因为暴力破解技术对凯撒密码非常有效所以您实际上不应该使用凯撒密码来加密秘密信息。 理想情况下密文永远不会落入任何人手中。但是 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 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.# 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 iterations 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 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.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 Hellofor循环将循环三次因为我们将整数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 iterations 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()方法调用定位SYMBOLS中symbol所在的索引并将其存储在一个名为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 行是我们的凯撒密码破解程序中唯一的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: Cant convert int object to str implicitly当您使用插值时整数42被插入到字符串中没有任何问题但是当您尝试连接该整数时它会导致错误。 caesarHacker.py的第 33 行使用字符串格式创建一个字符串该字符串同时具有key和translated变量的值。因为key存储一个整数值所以我们使用字符串格式将它放入一个传递给print()的字符串值中。 总结 凯撒密码的致命弱点是没有很多用来加密的密钥。任何计算机都可以很容易地用所有 66 个可能的密钥进行解密密码分析人员只需要几秒钟就可以在解密的信息中找到一个英文的。为了使我们的信息更安全我们需要一个有更多潜在密钥的密码。第七章中讨论的换位密码可以为我们提供这种安全性。 练习题 练习题的答案可以在本书的网站www.nostarch.com/crackingcodes找到。 破解下面的密文一次解密一行因为每一行都有不同的密钥。请记住对任何引用字符进行转义: qeFIP?eGSeECNNS, 5coOMXXcoPSZIWoQI, avnl1olyD4lylDohww6DhzDjhuDil,z.GM?.cEQc. 70c.7KcKMKHA9AGFK, ?MFYp2pPJJUpZSIJWpRdpMFY, ZqH8sl5HtqHTH4s3lyvH5zH5spH4t pHzqHlH3l5KZfbi,!tif!xpvme!qspcbcmz!fbu!nfA七、用换位密码加密 原文https://inventwithpython.com/cracking/chapter7.html “辩称你不在乎隐私权是因为你没什么可隐瞒的这与说你不在乎言论自由是因为你无话可说没什么区别。” ——爱德华·斯诺登2015 凯撒密码不安全。对一台计算机来说暴力破解所有 66 个可能的密钥并不需要太多时间。另一方面换位密码更难以暴力破解因为可能的密钥数量取决于消息的长度。有许多不同类型的换位密码包括围栏密码、路由密码、Myszkowski 换位密码和中断换位密码。本章涵盖了一个简单的换位密码称为列式换位密码。 本章涵盖的主题 用def语句创建函数 自变量和参数 全局和局部作用域内的变量 main()函数 列表数据类型 列表和字符串的相似性 列表的列表 扩展赋值运算符(, -, *, /) join()字符串方法 返回值和return语句 __name__变量 换位密码的工作原理 换位密码不是用其他字符替换字符而是将邮件的符号重新排列成使原始邮件不可读的顺序。因为每一个密钥都创造了不同的字符顺序或称排列密码分析者不知道如何将密文重新排列成原始信息。 用换位密码加密的步骤如下: 计算消息和密钥中的字符数。 画一排与密钥数量相等的盒子例如8 个盒子代表 8 个密钥。 开始从左到右填写方框每个方框输入一个字符。 当你用完了所有的方块但仍然有更多的字符添加另一行方块。 当到达最后一个字符时在最后一行未使用的框中添加阴影。 从左上角开始沿着每一列写出字符。当到达一列的底部时移动到右边的下一列。跳过任何阴影框。这将是密文。 为了了解这些步骤在实践中是如何工作的我们将手动加密一条消息然后将这个过程转换成一个程序。 手动加密信息 在我们开始写代码之前让我们加密“常识并不那么普遍”这个消息。使用铅笔和纸。包括空格和标点这条消息有 30 个字符。对于本例您将使用数字 8 作为密钥。此密码类型的可能密钥范围是从 2 到消息大小的一半即 15。但是消息越长可能的密钥就越多。使用柱状置换密码加密整本书将允许数千个可能的密钥。 第一步连续画八个方框与密钥号匹配如图 7-1 。 图 7-1第一行的框数要和密钥数匹配。 第二步是开始将你想要加密的信息写入盒子每个盒子放一个字符如图 7-2 所示。记住空格也是字符这里用/表示。 图 7-2每框填写一个字符包括空格。 你只有八个盒子但是消息里有 30 个字符。当你用完盒子后在第一行下面再画一行八个盒子。继续创建新行直到你写完整个消息如图 7-3 所示。 图 7-3添加更多的行直到填满整个消息。 在最后一行的两个框中画阴影以提醒忽略它们。密文由从左上方的方框中读取的字母组成。C、e、n和o来自第一列如图所示。当到达一列的最后一行时移动到右边下一列的顶行。接下来的字符是o、n、o、m。忽略阴影框。 密文是Cenoonommstmme oo snnio. s s c它被充分地扰乱以防止有人通过查看它来理解原始消息。 创建加密程序 要制作一个加密程序你需要将这些纸上谈兵的步骤翻译成 Python 代码。让我们再次看看如何使用密钥8加密字符串Common sense is not so common.。对于 Python 来说一个字符在字符串中的位置就是它的编号索引所以把字符串中每个字母的索引加到你原来的加密图的方框中如图 7-4 所示。记住索引以0开始而不是1。 图 7-4给每个盒子加上索引号从 0 开始。 这些方框显示第一列具有索引0、8、16和24处的字符它们是C、e、n和o。下一列具有索引1、9、17和25处的字符它们是o、n、o和m。注意出现的模式:第n列包含字符串中索引为0 (n – 1)、8 (n – 1)、16 (n – 1)、24 (n – 1)的所有字符如图 7-5 所示。 图 7-5每个盒子的索引遵循一个可预测的模式。 第 7 列和第 8 列的最后一行有一个例外因为24(7–1)和24(8–1)将大于 29这是字符串中最大的索引。在这些情况下你只需将 0、8 和 16 加到n 并跳过 24。 数字 0、8、16 和 24 有什么特别的这些是从 0 开始添加密钥在本例中是 8时得到的数字。所以0 8是 88 8是 1616 8是 24。24 8的结果将是 32但是因为 32 比消息的长度大所以您将在 24 处停止。 对于第n列的字符串从索引n–1开始继续加 8密钥得到下一个索引。只要索引小于 30消息长度就一直加 8此时移到下一列。 如果您将每一列想象成一个字符串那么结果将是一个包含八个字符串的列表如下所示:Ceno、onom、mstm、me o、o sn、nio.、 s 、s c。如果您按顺序将字符串连接在一起结果将是密文:Cenoonommstmme oo snnio. s s c。在这一章的后面你会学到一个叫做列表的概念它会让你做到这一点。 换位密码加密程序的源代码 选择文件 - 新文件打开新文件编辑器窗口。在文件编辑器中输入以下代码然后保存为transpositonecrypt.py。记得将pyperclip.py模块放在与transpositonecrypt.py文件相同的目录下。然后按F5运行程序。 换位 Encrypt.py # Transposition Cipher Encryption # https://www.nostarch.com/crackingcodes/ (BSD Licensed)import pyperclipdef main():myMessage Common sense is not so common.myKey 8ciphertext encryptMessage(myKey, myMessage)# Print the encrypted string in ciphertext to the screen, with# a | (pipe character) after it in case there are spaces at# the end of the encrypted message:print(ciphertext |)# Copy the encrypted string in ciphertext to the clipboard:pyperclip.copy(ciphertext)def encryptMessage(key, message):# Each string in ciphertext represents a column in the grid:ciphertext [] * key# Loop through each column in ciphertext:for column in range(key):currentIndex column# Keep looping until currentIndex goes past the message length:while currentIndex len(message):# Place the character at currentIndex in message at the# end of the current column in the ciphertext list:ciphertext[column] message[currentIndex]# Move currentIndex over:currentIndex key# Convert the ciphertext list into a single string value and return it:return .join(ciphertext)# If transpositionEncrypt.py is run (instead of imported as a module) call # the main() function: if __name__ __main__:main()换位密码加密程序的运行示例 当您运行transpositonecrypt.py程序时它会产生以下输出: Cenoonommstmme oo snnio. s s c|竖线字符|标记密文的结尾以防结尾有空格。这个密文末尾没有管道字符也被复制到剪贴板因此您可以将它粘贴到给某人的电子邮件中。如果您想要加密不同的消息或使用不同的密钥请更改第 7 行和第 8 行中分配给myMessage和myKey变量的值。然后再次运行该程序。 用def语句创建自己的函数 导入pyperclip模块后您将在第 6 行使用一个def语句创建一个定制函数main()。 # Transposition Cipher Encryption # https://www.nostarch.com/crackingcodes/ (BSD Licensed)import pyperclip def main():myMessage Common sense is not so common.myKey 8def语句意味着你正在创建或者说定义一个你可以在程序中稍后调用的新函数。def语句后的代码块是调用函数时将运行的代码。当你调用这个函数时执行在函数的def语句之后的代码块内移动。 正如你在第三章中了解到的在某些情况下函数会接受参数这些参数是函数可以在代码中使用的值。例如print()可以将一个字符串值作为其括号之间的参数。当您定义一个接受参数的函数时您在它的def语句中的括号之间放置一个变量名。这些变量被称为参数。这里定义的main()函数没有参数所以在调用时没有参数。如果您试图调用一个函数而该函数的参数数量太多或太少Python 将会抛出一条错误消息。 定义带参数的函数 让我们用一个参数创建一个函数然后用一个参数调用它。打开一个新的文件编辑器窗口并在其中输入以下代码: 你好 Function.py def hello(name): # ➊print(Hello, name) # ➋print(Start.) # ➌hello(Alice) # ➍ ➎ print(Call the function again:) ➏ hello(Bob) ➐ print(Done.)将该程序另存为helloFunction.py并按F5运行。输出如下所示: Start. Hello, Alice Call the function again: Hello, Bob Done.当helloFunction.py程序运行时执行从顶部开始。def语句 ➊ 用一个参数定义了hello()函数这个参数就是变量name。执行会跳过def语句 ➋ 之后的块因为该块只在函数被调用时运行。接下来它执行print(Start.) ➌这就是为什么Start.是运行程序时打印的第一个字符串。 print(Start.)之后的下一行是对hello()的第一次函数调用。程序执行跳转到hello()函数块 ➋ 的第一行。字符串Alice作为参数传递并被赋给参数name。这个函数调用将字符串Hello, Alice打印到屏幕上。 当程序执行到def语句块的底部时执行跳回到函数调用 ➍ 的那一行并从那里继续执行代码所以Call the function again:被打印 ➎ 。 接下来是对hello() ➏ 的第二次调用。程序执行跳回到hello()函数的定义 ➋ 并再次执行那里的代码在屏幕上显示Hello, Bob。然后函数返回执行到下一行即print(Done.)语句 ➐ 并执行它。这是程序的最后一行所以程序退出。 对参数的更改只存在于函数内部 在交互式 shell 中输入以下代码。这段代码定义并调用一个名为func()的函数。请注意交互式 shell 要求您在param 42后输入一个空行来关闭def语句块: def func(param):param 42 spam Hellofunc(spam)print(spam) Hellofunc()函数接受一个名为param的参数并将其值设置为42。函数外的代码创建一个spam变量并将其设置为字符串值然后在spam上调用该函数并打印出spam。 当你运行这个程序时最后一行的print()调用将打印Hello而不是42。当以spam作为参数调用func()时只有spam内的值被复制并赋给param。在函数内部对param所做的任何改变将不改变spam变量中的值。当您传递列表或字典值时此规则有一个例外但这在第 119 页的列表变量使用引用中有解释。 每次调用函数时都会创建一个局部作用域。在函数调用过程中创建的变量存在于这个局部作用域内被称为局部变量。参数总是存在于局部作用域内它们是在调用函数时创建并赋值的。把一个作用域想象成一个容器变量存在于其中。当函数返回时局部作用域被销毁作用域中包含的局部变量被遗忘。 在每个函数之外创建的变量存在于全局作用域中被称为全局变量。当程序退出时全局作用域被破坏程序中的所有变量都被遗忘。分别在第 5 章和第 6 章中的逆向密码和凯撒密码程序中的所有变量都是全局变量。 变量必须是局部的或全局的不可能两者兼得。两个不同的变量可以有相同的名字只要它们在不同的作用域内。它们仍然被认为是两个不同的变量就像旧金山的大街和伯明翰的大街是不同的一样。 需要理解的重要思想是被“传递”到函数调用中的参数值是被复制到参数中的。因此即使参数被更改提供参数值的变量也不会更改。 定义main()函数 在transpositonecrypt.py中的第 6 到第 8 行你可以看到我们已经定义了一个main()函数它将在被调用时为变量myMessage和myKey设置值: def main():myMessage Common sense is not so common.myKey 8本书中其余的程序也有一个名为main()的函数在每个程序开始时被调用。我们有一个main()函数的原因会在本章末尾解释但是现在只需要知道在本书中的程序运行后不久main()就会被调用。 第 7 行和第 8 行是定义main()的代码块中的前两行。在这几行中变量myMessage和myKey存储要加密的明文消息和用于加密的密钥。第 9 行是一个空行但仍然是代码块的一部分它将第 7 行和第 8 行与第 10 行分开以使代码更具可读性。第 10 行通过调用一个带有两个参数的函数将变量ciphertext指定为加密的消息: ciphertext encryptMessage(myKey, myMessage)进行实际加密的代码在第 21 行后面定义的encryptMessage()函数中。这个函数有两个参数:密钥的整数值和要加密的消息的字符串值。在这种情况下我们传递变量myMessage和myKey我们刚刚在第 7 行和第 8 行定义了它们。向函数调用传递多个参数时用逗号分隔参数。 encryptMessage()的返回值是加密密文的字符串值。该字符串存储在ciphertext中。 密文信息打印到屏幕的第 15 行并复制到剪贴板的第 18 行: # Print the encrypted string in ciphertext to the screen, with# a | (pipe character) after it in case there are spaces at# the end of the encrypted message:print(ciphertext |)# Copy the encrypted string in ciphertext to the clipboard:pyperclip.copy(ciphertext)该程序在消息的末尾打印一个管道字符|这样用户就可以看到密文末尾的任何空格字符。 第 18 行是main()函数的最后一行。在它执行之后程序执行返回到调用它的行之后的行。 将密钥和消息作为参数传递 第 21 行括号之间的key和message变量是参数: def encryptMessage(key, message):当在第 10 行调用encryptMessage()函数时传递两个参数值在myKey和myMessage中的值。当执行移动到函数的顶部时这些值被分配给参数key和message。 您可能想知道为什么还要有key和message参数因为在main()函数中已经有了变量myKey和myMessage。我们需要不同的变量因为myKey和myMessage在main()函数的局部作用域内不能在main()之外使用。 列表数据类型 transpositonecrypt.py程序中的第 23 行使用了一种叫做列表的数据类型: # Each string in ciphertext represents a column in the grid:ciphertext [] * key在我们继续之前你需要理解列表是如何工作的以及你能用它们做什么。列表值可以包含其他值。类似于字符串如何以引号开始和结束列表值以左括号[开始以右括号]结束。列表中存储的值在括号之间。如果列表中有多个值则这些值用逗号分隔。 要查看运行中的列表请在交互式 shell 中输入以下内容: animals [aardvark, anteater, antelope, albert]animals [aardvark, anteater, antelope, albert]animals变量存储一个列表值在这个列表值中有四个字符串值。列表中的单个值也被称为项或元素。当您必须在一个变量中存储多个值时列表是理想的选择。 您可以对字符串进行的许多操作也适用于列表。例如索引和切片处理列表值的方式与处理字符串值的方式相同。索引指的是列表中的一项而不是字符串中的单个字符。在交互式 shell 中输入以下内容: animals [aardvark, anteater, albert] animals[0] # ➊aardvark animals[1]anteater animals[2]albert animals[1:3] # ➋[anteater, albert]请记住第一个指标是0而不是1 ➊。类似于对字符串使用切片会得到一个作为原始字符串一部分的新字符串对列表使用切片会得到一个作为原始列表一部分的列表。请记住如果一个切片有第二个索引该切片只走到第二个索引 ➋ 处的前一个项目。 一个for循环也可以遍历列表中的值就像它可以遍历字符串中的字符一样。存储在for循环变量中的值是列表中的单个值。在交互式 shell 中输入以下内容: for spam in [aardvark, anteater, albert]: ... print(For dinner we are cooking spam) ... For dinner we are cooking aardvark For dinner we are cooking anteater For dinner we are cooking albert每次循环迭代时spam变量从列表中被赋予一个新值从列表的0索引开始直到列表的结尾。 重新赋值列表中的项目 您还可以通过使用带有普通赋值语句的列表索引来修改列表中的项目。在交互式 shell 中输入以下内容: animals [aardvark, anteater, albert] animals[2] 9999 # ➊ animals[aardvark, anteater, 9999] # ➋为了修改animals列表的第三个成员我们使用索引来获得第三个值animals[2]然后使用赋值语句将其值从albert更改为值9999 ➊。当我们再次检查列表的内容时albert不再包含在 ➋ 列表中。 重新赋值字符串中的字符 虽然您可以重新赋值列表中的项目但不能重新赋值字符串值中的字符。在交互式 shell 中输入以下代码: Hello world![6] X您会看到以下错误: Traceback (most recent call last):File pyshell#0, line 1, in moduleHello world![6] X TypeError: str object does not support item assignment您看到这个错误的原因是 Python 不允许您在字符串的索引值上使用赋值语句。相反要更改字符串中的字符您需要使用切片创建一个新的字符串。在交互式 shell 中输入以下内容: spam Hello world!spam spam[:6] X spam[7:]spam Hello Xorld!首先从字符串的开头开始一直到要更改的字符获取一个片段。然后你可以把它连接到新字符的字符串以及从新字符后的字符到字符串末尾的一段。这导致原始字符串只有一个字符发生了变化。 列表的列表 列表值甚至可以包含其他列表。在交互 shell 中输入以下内容: spam [[dog, cat], [1, 2, 3]]spam[0] [dog, cat]spam[0][0] dogspam[0][1] catspam[1][0] 1spam[1][1] 2spam[0]的值求值为列表[dog, cat]它有自己的索引。用于spam[0][0]的双索引括号表示我们从第一个列表中取出第一个项目:spam[0]计算为[dog, cat][dog, cat][0]计算为dog。 对列表使用len()和in运算符 您已经使用了len()来表示字符串中的字符数即字符串的长度。len()函数也作用于列表值并返回列表中项目数的整数。 在交互式 shell 中输入以下内容: animals [aardvark, anteater, antelope, albert]len(animals) 4类似地您已经使用了in和not in操作符来指示一个字符串是否存在于另一个字符串值中。in操作符也用于检查列表中是否有值而not in操作符检查列表中是否没有值。在交互式 shell 中输入以下内容: animals [aardvark, anteater, antelope, albert] anteater in animalsTrue anteater not in animalsFalse anteat in animals # ➊False anteat in animals[1] # ➋True delicious spam in animalsFalse为什么 ➊ 的表达式返回False而 ➋ 的表达式返回True记住animals是一个列表值而animals[1]计算的是字符串值anteater。因为字符串anteat不在animals列表中所以 ➊ 处的表达式求值为False。然而 ➋ 处的表达式求值为True因为animals[1]是字符串anteater并且anteat存在于该字符串中。 类似于一组空引号表示一个空字符串值一组空括号表示一个空列表。在交互式 shell 中输入以下内容: animals []len(animals) 0animals列表为空因此其长度为0。 用和*运算符链接和复制列表 您知道和*操作符可以连接和复制字符串相同的操作符也可以连接和复制列表。在交互式 shell 中输入以下内容。 [hello] [world] [hello, world][hello] * 5 [hello, hello, hello, hello, hello]关于字符串和列表的相似之处已经说得够多了。请记住您可以对字符串值进行的大多数操作也适用于列表值。 换位加密算法 我们将在加密算法中使用列表来创建密文。让我们回到transpositionEncrypt.py程序中的代码。在我们前面看到的第 23 行中ciphertext变量是一个空字符串值列表: # Each string in ciphertext represents a column in the grid:ciphertext [] * keyciphertext变量中的每个字符串代表换位密码网格中的一列。因为列的数量等于密钥的数量所以可以使用列表复制将一个包含一个空字符串值的列表乘以key中的值。这就是第 23 行如何计算出包含正确数量的空白字符串的列表。字符串值将被分配到网格的一列中的所有字符。结果将是代表每一列的字符串值的列表如本章前面所讨论的。因为列表索引从 0 开始所以还需要从 0 开始标记每一列。所以ciphertext[0]是最左边的一列ciphertext[1]是右边的一列以此类推。 为了了解这是如何工作的让我们再次从“常识并不常见”的角度来看网格。本章前面的示例列表索引对应的列号添加到顶部如图 7-6 所示。 图 7-6带有每列列表索引的示例消息网格 如果我们手动将字符串值分配给该网格的ciphertext变量它将如下所示: ciphertext [Ceno, onom, mstm, me o, o sn, nio., s , s c]ciphertext[0] Ceno下一步是向ciphertext中的每个字符串添加文本就像我们刚刚在手动示例中所做的那样只是这次我们添加了一些代码让计算机以编程方式完成: # Loop through each column in ciphertext:for column in range(key):currentIndex column第 26 行的for循环对每一列迭代一次变量column具有用于索引ciphertext的整数值。在通过for循环的第一次迭代中column变量被设置为0第二次迭代时设置为1然后2诸如此类。我们在ciphertext中有字符串值的索引我们希望稍后使用表达式ciphertext[column]来访问它。 同时currentIndex变量保存了程序在for循环的每次迭代中查看的message字符串的索引。在循环的每次迭代中第 27 行将currentIndex设置为与column相同的值。接下来我们将通过一次一个字符地将加扰后的消息连接在一起来创建密文。 扩展赋值运算符 到目前为止当我们相互连接或添加值时我们使用了操作符将新值添加到变量中。通常当您为变量赋值新值时您希望它基于变量的当前值因此您将变量作为表达式的一部分来计算并赋值给变量如交互式 shell 中的示例所示: spam 40spam spam 2print(spam) 42还有其他方法可以根据变量的当前值来操作变量中的值。例如您可以通过使用扩展赋值操作符来实现这一点。语句spam 2使用了扩展赋值运算符它将做与spam spam 2同样的事情。只是打字时间短了一点。操作符使用整数做加法使用字符串做字符串连接使用列表做列表连接。表 7-1 显示了扩展赋值运算符及其等价赋值语句。 表 7-1 扩展赋值运算符 扩展赋值等效普通赋值spam 42spam spam 42spam - 42spam spam - 42spam * 42spam spam * 42spam / 42spam spam / 42 我们将使用扩展赋值操作符将字符连接到我们的密文中。 通过message移动currentIndex currentIndex变量保存了message字符串中下一个字符的索引该字符串将被连接到ciphertext列表中。在第 30 行的while循环的每次迭代中key被添加到currentIndex中以指向message中的不同字符并且在第 26 行的for循环的每次迭代中currentIndex被设置为column变量中的值。 为了对message变量中的字符串进行加扰我们需要取message的第一个字符C并将其放入ciphertext的第一个字符串中。然后我们将跳过八个字符进入message因为key等于8并将这个字符e连接到密文的第一个字符串。我们将继续根据密钥跳过字符并连接每个字符直到我们到达消息的结尾。这样做将创建字符串Ceno这是密文的第一列。然后我们将再次这样做但是从message中的第二个字符开始创建第二列。 从第 26 行开始的for循环中有一个从第 30 行开始的while循环。这个while循环在message中找到并连接正确的字符来生成每一列。当currentIndex小于message的长度时循环 # Keep looping until currentIndex goes past the message length:while currentIndex len(message):# Place the character at currentIndex in message at the# end of the current column in the ciphertext list:ciphertext[column] message[currentIndex]# Move currentIndex over:currentIndex key对于每一列while循环遍历原始的message变量并通过将key加到currentIndex中来挑选出key间隔中的字符。在第 27 行上对于for循环的第一次迭代currentIndex被设置为从0开始的column的值。 如图 7-7 中的所示message[currentIndex]是message第一次迭代的第一个字符。在message[currentIndex]T13 的字符被连接到ciphertext[column]以在第 33 行开始第一列。第 36 行每次通过循环将key即8中的值添加到currentIndex。第一次是message[0]第二次message[8]第三次message[16]第四次message[24]。 图 7-7当列设置为 0 时在for循环的第一次迭代中指向message[currentIndex]所指内容的箭头 虽然currentIndex中的值小于message字符串的长度但是您希望继续将message[currentIndex]处的字符连接到ciphertext中column索引处的字符串末尾。当currentIndex大于message的长度时执行退出while循环回到for循环。因为在while循环之后的for块中没有代码所以for循环迭代column被设置为1currentIndex从与column相同的值开始。 现在当第 36 行在第 30 行的while循环的每次迭代中将8加到currentIndex时索引将为1、9、17和25如图 7-8 所示。 图 7-8当列设置为 1 时在循环的的第二次迭代中指向message[currentIndex]所指的箭头 由于message[1]、message[9]、message[17]和message[25]连接在ciphertext[1]的末尾它们形成了字符串onom。这是网格的第二列。 当for循环完成其余列的循环后ciphertext中的值为[Ceno, onom, mstm, me o, o sn, nio., s , s c]。有了字符串列的列表后我们需要将它们连接在一起形成一个字符串这就是整个密文:Cenoonommstmme oo snnio. s s c。 join()字符串方法 第 39 行使用了join()方法将ciphertext的各个列字符串连接成一个字符串。对一个字符串值调用join()方法并获取一个字符串列表。它返回一个字符串列表中的所有成员都由调用join()的字符串连接。如果您只想将字符串连接在一起这是一个空白字符串。在交互式 shell 中输入以下内容: eggs [dogs, cats, moose] .join(eggs) # ➊dogscatsmoose , .join(eggs) # ➋dogs, cats, moose XYZ.join(eggs) # ➌dogsXYZcatsXYZmoose当你在一个空字符串上调用join()并加入列表eggs ➊ 时你会得到列表的字符串它们之间没有字符串。在某些情况下你可能想要在一个列表中分离每个成员以使其更具可读性我们已经在 ➋ 通过在字符串, 上调用join()做到了这一点。这将在列表的每个成员之间插入字符串, 。你可以在列表成员之间插入任何你想要的字符串正如你在 ➌ 看到的。 返回值和返回语句 函数或方法调用总是计算出一个值。这是函数或方法调用返回的值也称为函数的返回值。当您使用def语句创建自己的函数时return语句会告诉 Python 该函数的返回值是什么。第 39 行是一个return语句: # Convert the ciphertext list into a single string value and return it:return .join(ciphertext)第 39 行调用空白字符串上的join()并将ciphertext作为参数传递因此ciphertext列表中的字符串被连接成一个字符串。 返回语句示例 return语句是return关键字后跟要返回的值。您可以使用一个表达式来代替一个值如第 39 行所示。当您这样做时返回值是该表达式计算的任何值。打开一个新的文件编辑器窗口进入如下程序保存为addNumbers.py然后按F5运行: addNumbers.py def addNumbers(a, b):return a bprint(addNumbers(2, 40))当您运行addNumbers.py程序时输出如下: 42这是因为第 4 行的函数调用addNumbers(2, 40)计算结果为42。第 2 行addNumbers()中的return语句对表达式a b求值然后返回求值结果。 返回加密的密文 在transpositonecrypt.py程序中encryptMessage()函数的return语句返回一个字符串值该值是通过连接ciphertext列表中的所有字符串而创建的。如果密文中的列表是[Ceno,onom,mstm,me o,o sn,nio., s ,s c]那么join()方法调用将返回Cenoonommstmme oo snnio. s s c。这个最后的字符串即加密代码的结果由我们的encryptMessage()函数返回。 使用函数的最大好处是程序员必须知道函数做什么但不需要知道函数的代码是如何工作的。程序员可以理解当他们调用encryptMessage()函数并传递给它一个整数以及一个用于key和message参数的字符串时函数调用计算出一个加密的字符串。他们不需要知道encryptMessage()中的代码实际上是如何做到这一点的这类似于你如何知道当你将一个字符串传递给print()时它将打印该字符串即使你从未见过print()函数的代码。 __name__变量 你可以使用一个特殊的技巧将换位加密程序变成一个模块这个技巧涉及到main()函数和一个名为__name__的变量。 当运行 Python 程序时甚至在程序的第一行运行之前__name__即name之前的两个下划线和之后的两个下划线就被赋予了字符串值__main__同样main之前和之后的两个下划线。双下划线在 Python 中通常被称为dunder而__main__被称为“双下划线main双下划线”。 在脚本文件的末尾更重要的是在所有的def语句之后您希望有一些代码来检查__name__变量是否被赋予了__main__字符串。如果是你要调用main()函数。 第 44 行的if语句实际上是运行程序时执行的第一行代码之一在第 4 行的import语句和第 6、21 行的def语句之后。 # If transpositionEncrypt.py is run (instead of imported as a module) call # the main() function: if __name__ __main__:main()以这种方式设置代码的原因是尽管 Python 在程序运行时将__name__设置为__main__但如果程序是由另一个 Python 程序导入的它会将其设置为字符串transpositionEncrypt。类似于程序如何导入pyperclip模块来调用其中的函数其他程序可能想要导入transpositonecrypt.py来调用其encryptMessage()函数而不运行main()函数。当执行一个import语句时Python 通过添加。py到文件名的末尾这就是为什么import pyperclip导入pyperclip.py文件。这就是我们的程序如何知道它是作为主程序运行还是作为模块被不同的程序导入。您将在第 9 章的中导入transpositonecrypt.py作为一个模块。 当您导入一个 Python 程序时在程序执行之前__name__变量被设置为文件名的 before 部分。py。当transpositonecrypt.py程序被导入时所有的def语句都被运行以定义导入程序想要使用的encryptMessage()函数但是main()函数没有被调用所以Common sense is not so common.的带密钥8的加密代码没有被执行。 这就是为什么用myKey密钥加密myMessage字符串的代码在一个函数内部这个函数按照惯例被命名为main()。当transpositonecrypt.py被其他程序导入时main()里面的这段代码不会运行但是这些其他程序仍然可以调用它的encryptMessage()函数。这就是函数的代码可以被其他程序重用的方式。 注 了解一个程序如何工作的一个有用的方法是在它运行时一步一步地跟踪它的执行。您可以使用在线程序跟踪工具查看 Hello 函数和换位密码加密程序的踪迹。跟踪工具将为您提供每行代码执行时程序正在做什么的可视化表示。 总结 咻在本章中你学习了几个新的编程概念。换位密码程序比第六章中的凯撒密码程序更复杂但安全得多。您在本章中学到的新概念、函数、数据类型和运算符使您能够以更复杂的方式操作数据。请记住理解一行代码的大部分工作是按照 Python 的方式一步一步地求值它。 您可以将代码组织成称为函数的组这些函数是用def语句创建的。参数值可以作为函数的参数传递给函数。参数是局部变量。所有函数之外的变量都是全局变量。局部变量不同于全局变量即使它们与全局变量同名。一个函数中的局部变量也与另一个函数中的局部变量分开即使它们同名。 列表值可以存储多个其他值包括其他列表值。许多可以在字符串上使用的操作比如索引、切片和使用len()函数也可以在列表上使用。扩展赋值操作符为常规赋值操作符提供了一个很好的捷径。join()方法可以连接包含多个字符串的列表以返回单个字符串。 如果你对这些编程概念还不太熟悉最好回顾一下这一章。在第八章中你将学习如何使用换位密码解密。 练习题 练习题的答案可以在本书的网站www.nostarch.com/crackingcodes找到。 用纸和笔使用换位密码用密钥 9 加密以下消息。为了您的方便已经提供了字符数。 Underneath a huge oak tree there was of swine a huge company61 个字符 That grunted as they crunched the mast: For that was ripe and fell full fast77 个字符 Then they trotted away for the wind grew high: One acorn they left, and no more might you spy94 个字符 在下面的程序中每个垃圾邮件是全局变量还是局部变量 spam 42 def foo():global spamspam 99print(spam)下列每个表达式的值是多少 [0, 1, 2, 3, 4][2] [[1, 2], [3, 4]][0] [[1, 2], [3, 4]][0][1] [hello][0][1] [2, 4, 6, 8, 10][1:3] list(Hello world!) list(range(10))[2]下列每个表达式的值是多少 len([2, 4]) len([]) len([, , ]) [4, 5, 6] [1, 2, 3] 3 * [1, 2, 3] [9] 42 in [41, 42, 42, 42]四种扩展赋值运算符是什么 八、用换位密码解密 原文https://inventwithpython.com/cracking/chapter8.html “弱化加密或为加密设备和数据创建后门以供好人使用实际上会产生漏洞供坏人利用。” ——蒂姆·库克苹果公司首席执行官2015 年 与凯撒密码不同换位密码的解密过程不同于加密过程。在本章中您将创建一个名为transpositionecrypt.py的独立程序来处理解密。 本章涵盖的主题 用换位密码解密 round()、math.ceil()和math.floor()函数 布尔运算符and和or 真值表 如何用纸上的换位密码解密 假装你已经发送了密文Cenoonommstmme oo snnio. s s c给朋友并且他们已经知道秘密密钥是 8。他们解密密文的第一步是计算他们需要画的盒子的数量。要确定这个数字他们必须用密钥除密文的长度如果结果不是整数就四舍五入到最接近的整数。密文的长度是 30 个字符与明文相同密钥是 8所以 30 除以 8 是 3.75。 图 8-1通过反转网格解密消息 将 3.75 四舍五入为 4你的朋友将画出一个由四列他们刚刚计算出的数字和八行密钥组成的格子。 你的朋友还需要计算需要遮蔽的箱子数量。使用盒子的总数32他们减去密文的长度3032–30 2。它们在最右边列的底部的两个方框中着色。 然后他们开始填充盒子在每个盒子里放一个密文字符。从左上角开始它们向右填充就像加密时一样。密文是Cenoonommstmme oo snnio. s s c所以Ceno在第一行onom在第二行依此类推。当它们完成后这些方框看起来将像图 8-1 中的一个/代表一个空格。 你收到密文的朋友注意到当他们阅读这些列中的文本时原始的明文被恢复:“常识并不那么普遍。” 概括地说解密的步骤如下: 通过将消息的长度除以密钥然后向上舍入计算出所需的列数。 按列和行绘制方框。使用您在步骤 1 中计算的列数。行数与密钥相同。 通过计算框的总数行数乘以列数并减去密文消息的长度来计算要加阴影的框的数量。 在最右边一栏的底部画出你在第三步中计算出的盒子数量。 从第一行开始从左到右填写密文的字符。跳过任何阴影框。 从上到下读取最左边的列并在每一列中继续这样做从而获得明文。 请注意如果您使用不同的密钥您将绘制错误的行数。即使您正确地遵循了解密过程中的其他步骤明文也将是随机垃圾类似于您在凯撒密码中使用了错误的密钥。 换位密码解密程序的源代码 点击文件 - 新建文件打开一个新建文件编辑器窗口。在文件编辑器中输入以下代码然后保存为transpositondecrypt.py。记得把pyperclip.py放在同一个目录下。按F5运行程序。 transpositondecrypt.py # Transposition Cipher Decryption # https://www.nostarch.com/crackingcodes/ (BSD Licensed)import math, pyperclipdef main():myMessage Cenoonommstmme oo snnio. s s cmyKey 8plaintext decryptMessage(myKey, myMessage)# Print with a | (called pipe character) after it in case# there are spaces at the end of the decrypted message:print(plaintext |)pyperclip.copy(plaintext)def decryptMessage(key, message):# The transposition decrypt function will simulate the columns and# rows of the grid that the plaintext is written on by using a list# of strings. First, we need to calculate a few values.# The number of columns in our transposition grid:numOfColumns int(math.ceil(len(message) / float(key)))# The number of rows in our grid:numOfRows key# The number of shaded boxes in the last column of the grid:numOfShadedBoxes (numOfColumns * numOfRows) - len(message)# Each string in plaintext represents a column in the grid:plaintext [] * numOfColumns# The column and row variables point to where in the grid the next# character in the encrypted message will go:column 0row 0for symbol in message:plaintext[column] symbolcolumn 1 # Point to the next column.# If there are no more columns OR were at a shaded box, go back# to the first column and the next row:if (column numOfColumns) or (column numOfColumns - 1 androw numOfRows - numOfShadedBoxes):column 0row 1return .join(plaintext)# If transpositionDecrypt.py is run (instead of imported as a module), # call the main() function: if __name__ __main__:main()换位密码解密程序的示例运行 当您运行transpositondecrypt.py程序时它会产生以下输出: Common sense is not so common.|如果您想要解密不同的消息或使用不同的密钥请更改第 7 行和第 8 行中分配给myMessage和myKey变量的值。 导入模块并设置main()函数 transpositonecrypt.py程序的第一部分与transpositonecrypt.py的第一部分类似: # Transposition Cipher Decryption # https://www.nostarch.com/crackingcodes/ (BSD Licensed)import math, pyperclipdef main():myMessage Cenoonommstmme oo snnio. s s cmyKey 8plaintext decryptMessage(myKey, myMessage)# Print with a | (called pipe character) after it in case# there are spaces at the end of the decrypted message:print(plaintext |)pyperclip.copy(plaintext)第 4 行的模块pyperclip和另一个名为math的模块一起被导入。如果用逗号分隔模块名可以用一个import语句导入多个模块。 我们在第 6 行开始定义的main()函数创建名为myMessage和myKey的变量然后调用解密函数decryptMessage ()。decryptMessage()的返回值是密文和密钥的解密明文。这被存储在一个名为plaintext的变量中该变量被打印到屏幕上在消息末尾有一个管道字符以防消息末尾有空格,然后被复制到剪贴板。 用密钥解密消息 decryptMessage()函数遵循第 100 页中描述的六个解密步骤然后以字符串形式返回解密结果。为了使解密更容易我们将使用来自math模块的函数该模块是我们在程序的前面导入的。 round()、math.ceil()和math.floor()函数 Python 的round()函数会将浮点数带小数点的数字四舍五入为最接近的整数。math.ceil()和math.floor()函数在 Python 的math模块中将分别向上和向下舍入一个数字。 当使用/运算符对数字进行除法运算时表达式返回一个浮点数带小数点的数字。即使数字被均匀地分割也会发生这种情况。例如在交互式 shell 中输入以下内容: 21 / 7 3.022 / 5 4.4如果你想把一个数字四舍五入到最接近的整数你可以使用round()函数。要查看该函数如何工作请输入以下内容: round(4.2) 4round(4.9) 5round(5.0) 5round(22 / 5) 4如果你只想取整你需要使用math.ceil()函数它代表“天花板”如果您只想向下舍入请使用math.floor()。这些函数存在于math模块中您需要在调用它们之前导入它们。在交互式 shell 中输入以下内容: import mathmath.floor(4.0) 4math.floor(4.2) 4math.floor(4.9) 4math.ceil(4.0) 4math.ceil(4.2) 5math.ceil(4.9) 5math.floor()函数将总是从浮点数中移除小数点并将其转换为整数以向下舍入而math.ceil()将递增浮点数的 1 位并将其转换为整数以向上舍入。 decryptMessage()函数 decryptMessage()函数将每个解密步骤实现为 Python 代码。它接受一个整数key和一个message字符串作为参数。math.ceil()函数用于decryptMessage的换位解密计算列数时确定需要制作的盒数: def decryptMessage(key, message):# The transposition decrypt function will simulate the columns and# rows of the grid that the plaintext is written on by using a list# of strings. First, we need to calculate a few values.# The number of columns in our transposition grid:numOfColumns int(math.ceil(len(message) / float(key)))# The number of rows in our grid:numOfRows key# The number of shaded boxes in the last column of the grid:numOfShadedBoxes (numOfColumns * numOfRows) - len(message)第 25 行通过将len(message)除以key中的整数来计算列数。这个值被传递给math.ceil()函数返回值被存储在numOfColumns中。为了让这个程序与 Python 2 兼容我们调用了float()因此key变成了一个浮点值。在 Python 2 中两个整数相除的结果会自动向下舍入。调用float()避免了这种行为而不影响 Python 3 下的行为。 第 27 行计算行数这是存储在key中的整数。该值存储在变量numOfRows中。 第 29 行计算网格中阴影框的数量即列数乘以行数减去消息的长度。 如果你在解密Cenoonommstmme oo snnio. s s c键为 8numOfColumns设置为4numOfRows设置为8numOfShadedBoxes设置为2。 就像加密程序有一个名为ciphertext的变量它是代表密文网格的字符串列表一样decryptMessage()也有一个名为plaintext的字符串列表变量: # Each string in plaintext represents a column in the grid:plaintext [] * numOfColumns这些字符串最初是空白的网格的每一列都有一个字符串。使用列表复制您可以将一个包含一个空白字符串的列表乘以numOfColumns得到一个包含几个空白字符串的列表其数量等于所需的列数。 请记住这个plaintext不同于main()函数中的plaintext。因为decryptMessage ()函数和main()函数都有自己的局部作用域所以函数的plaintext变量是不同的只是碰巧具有相同的名称。 请记住Cenoonommstmme oo snnio. s s c示例的网格看起来像第 100 页上的图 8-1 。 plaintext变量将有一个字符串列表列表中的每个字符串将是这个网格的一个单独的列。对于这个解密您希望plaintext以下面的值结束: [Common s, ense is , not so c, ommon.]这样您可以将列表中的所有字符串连接在一起以返回Common sense is not so common.字符串值。 为了制作列表我们首先需要将message中的每个符号一次一个地放在plaintext列表中的正确字符串中。我们将创建两个名为column和row的变量来跟踪message中下一个字符应该去的列和行这些变量应该从第一列和第一行的0开始。第 36 行和第 37 行是这样做的: # The column and row variables point to where in the grid the next# character in the encrypted message will go:column 0row 0第 39 行开始一个for循环遍历message字符串中的字符。在这个循环中代码将调整column和row变量以便将symbol连接到plaintext列表中的正确字符串: for symbol in message:plaintext[column] symbolcolumn 1 # Point to the next column.第 40 行将symbol连接到plaintext列表中索引column处的字符串因为plaintext中的每个字符串代表一列。然后第 41 行将1加到column也就是说它递增 column这样在循环的下一次迭代中symbol将被连接到plaintext列表中的下一个字符串。 我们已经处理了递增的column和row但是在某些情况下我们还需要将变量重置为0。要理解这样做的代码您需要理解布尔运算符。 布尔运算符 布尔运算符比较布尔值或求值为布尔值的表达式并求值为布尔值。布尔运算符and和or可以帮助您为if和while语句形成更复杂的条件。and运算符连接两个表达式如果两个表达式的计算结果都为True则计算结果为True。or运算符连接两个表达式如果一个或两个表达式的计算结果为True则计算结果为True否则这些表达式的计算结果为False。在交互式 shell 中输入以下内容看看and操作符是如何工作的: 10 5 and 2 4 True10 5 and 4 ! 4 False第一个表达式的计算结果为True因为and操作符两边的表达式都计算结果为True。换句话说表达式10 5 and 2 4计算为True and True后者又计算为True。 然而在第二个表达式中虽然10 5的计算结果是True但是表达式4 ! 4的计算结果是False。这意味着表达式计算结果为True and False。因为两个表达式都必须是True以便and操作符计算出True所以整个表达式计算出False。 如果您忘记了布尔运算符是如何工作的您可以查看它的真值表它显示了基于所使用的运算符布尔值的不同组合的计算结果。表 8-1 是and运算符的真值表。 表 8-1和运算符真值表 A 和 B求值为True and TrueTrueTrue and FalseFalseFalse and TrueFalseFalse and FalseFalse 要查看or操作符是如何工作的请在交互式 shell 中输入以下内容: 10 5 or 4 ! 4 True10 5 or 4 ! 4 False当你使用or操作符时只有表达式的一边必须是True这样or操作符才能将整个表达式计算为True这就是为什么10 5 or 4 ! 4计算为True。但是因为表达式10 5和表达式4 ! 4都是False所以第二个表达式的计算结果是False or False而后者的计算结果是False。 or运算符真值表见表 8-2 。 表 8-2或运算符真值表 A 或 B求值为True or TrueTrueTrue or FalseTrueFalse or TrueTrueFalse or FalseFalse 第三个布尔运算符是not。not运算符的计算结果是它所运算的值的相反布尔值。所以not True是Falsenot False是True。在交互式 shell 中输入以下内容: not 10 5 Falsenot 10 5 Truenot False Truenot not False Falsenot not not not not False True正如您在最后两个表达式中看到的您甚至可以使用多个not操作符。not运算符真值表见表 8-3 。 表 8-3非运算符真值表 非 A求值为not TrueFalsenot FalseTrue 作为快捷方式的and和or运算符 类似于for循环让你用更少的代码完成与while循环相同的任务and和or操作符也让你缩短代码。在交互式 shell 中输入以下两段具有相同结果的代码: if 10 5: ... if 2 4: ... print(Hello!) ... Hello!if 10 5 and 2 4: ... print(Hello!) ... Hello!and操作符可以代替两个if语句分别检查表达式的每个部分其中第二个if语句在第一个if语句的块内。 您也可以用or操作符替换if和elif语句。要尝试一下请在交互式 shell 中输入以下内容: if 4 ! 4: ... print(Hello!) ... elif 10 5: ... print(Hello!) ... Hello!if 4 ! 4 or 10 5: ... print(Hello!) ... Hello!if和elif语句将分别检查表达式的不同部分而or操作符可以在一行中检查这两个语句。 布尔运算符的运算顺序 你知道数学运算符是有运算顺序的and、or、not运算符也是如此。首先求值not然后求值and然后求值or。在交互式 shell 中输入以下内容: not False and False # not False evaluates first Falsenot (False and False) # (False and False) evaluates first True在第一行代码中not False首先被求值所以表达式变成了True and False它求值为False。在第二行括号先被求值甚至在not运算符之前所以False and False被求值为False表达式变成not (False)也就是True。 调整列和行变量 现在你知道了布尔运算符是如何工作的你可以学习如何在transpositonencrypt.py中重置column和row变量。 在两种情况下您会希望将column重置为0以便在循环的下一次迭代中symbol被添加到plaintext中列表的第一个字符串中。在第一种情况下如果column增加到超过plaintext中的最后一个索引您就要这样做。在这种情况下column中的值将等于numOfColumns。记住plaintext中的最后一个索引将是numOfColumns减一。所以当column等于numOfColumns时已经过了最后一个索引。 第二种情况是如果column在最后一个索引处并且row变量指向最后一列中有阴影框的行。作为一个直观的例子在图 8-2 中显示了列索引在顶部、行索引在底部的解密网格。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-twv1QZC2-1692873504740)(https://gitcode.net/OpenDocCN/invent-with-python-zh/-/raw/master/docs/cracking/img/70da1c2a7fc02ef810347e4794e56122.png)] 图 8-2列和行索引的解密网格 您可以看到阴影框位于第 6 行和第 7 行的最后一列其索引为numOfColumns - 1。要计算哪些行索引可能有阴影框请使用表达式row numOfRows - numOfShadedBoxes。在我们的八行示例中索引为 0 到 7第 6 行和第 7 行是阴影的。无阴影框的数量是总行数在我们的例子中是 8减去阴影框的数量在我们的例子中是 2。如果电流row等于或大于这个数字8–2 6我们可以知道我们有一个阴影框。如果这个表达式是Truecolumn也等于numOfColumns - 1那么 Python 遇到了阴影框此时您想要为下一次迭代将column重置为0: # If there are no more columns OR were at a shaded box, go back# to the first column and the next row:if (column numOfColumns) or (column numOfColumns - 1 androw numOfRows - numOfShadedBoxes):column 0row 1这两种情况就是为什么第 45 行的条件是(column numOfColumns ) or (column numOfColumns - 1 and row numOfRows - numOfShadedBoxes )。尽管这看起来像一个很大很复杂的表达式但是记住你可以把它分解成更小的部分。表达式( column numOfColumns)检查列变量是否超出索引范围表达式的第二部分检查我们是否在一个阴影框的column和row索引处。如果这两个表达式中的任何一个为真执行的代码块将通过将column设置为0来将column重置为第一列。您还将增加变量row。 当第 39 行的for循环结束对message中每个字符的循环时plaintext列表的字符串已经被修改所以它们现在是解密后的顺序如果使用了正确的密钥。第 49 行的join()字符串方法将plaintext列表中的字符串连接在一起每个字符串之间有一个空白字符串: return .join(plaintext)第 49 行还返回了函数decryptMessage()返回的字符串。 对于解密plaintext将是[Common s, ense is , not so c, ommon.]所以.join(plaintext)将求值为Common sense is not so common. 调用main()函数 我们的程序在导入模块并执行def语句后运行的第一行是第 54 行的if语句。 # If transpositionDecrypt.py is run (instead of imported as a module), # call the main() function: if __name__ __main__:main()与换位加密程序一样Python 通过检查__name__变量是否被设置为字符串值__main__来检查该程序是否已经运行而不是由不同的程序导入。如果是代码执行main()函数。 总结 解密程序到此结束。大部分程序都在decryptMessage()函数中。我们编写的程序可以加密和解密“常识并不常见”这一信息用密钥 8但是您应该尝试其他几种消息和密钥以检查加密然后解密的消息是否会产生相同的原始消息。如果你没有得到你期望的结果你就会知道要么是加密代码要么是解密代码不起作用。在第九章中我们将通过编写一个程序来测试我们的程序从而自动化这个过程。 如果你想看到换位密码解密程序执行的一步一步的踪迹请访问www.nostarch.com/crackingcodes。 练习题 练习题的答案可以在本书的网站www.nostarch.com/crackingcodes找到。 使用纸和笔用密钥 9 解密以下消息。▪标记一个空格。总字符数已经帮你统计好了。 H▪cb▪▪irhdeuousBdi▪▪▪prrtyevdgp▪nir▪▪eerit▪eatoreechadihf▪pak en▪ge▪b▪te▪dih▪aoa.da▪tts▪tn (89 characters) A▪b▪▪drottthawa▪nwar▪eci▪t▪nlel▪ktShw▪leec,hheat▪.na▪▪e▪soog mah▪a▪▪ateniAcgakh▪dmnor▪▪ (86 characters) Bmmsrl▪dpnaua!toebooktn▪uknrwos.▪yaregonr▪w▪nd,tu▪▪oiady▪h gtRwt▪▪▪A▪hhanhhasthtev▪▪e▪t▪e▪▪eo (93 characters) 当您在交互式 shell 中输入下面的代码时每行会显示什么 math.ceil(3.0)math.floor(3.1)round(3.1)round(3.5)False and FalseFalse or Falsenot not True画出and、or和not运算符的完整真值表。 以下哪一项是正确的 if __name__ __main__: if __main__ __name__: if _name_ _main_: if _main_ _name_:九、编写一个程序来测试你的程序 原文https://inventwithpython.com/cracking/chapter9.html “安装有朝一日可能会促进警察国家的技术是糟糕的公民卫生。” ——布鲁斯·施奈尔秘密与谎言 换位程序似乎在用不同的密钥加密和解密不同的信息方面工作得很好但是你怎么知道它们总是工作呢除非你用各种各样的message和key参数值测试encryptMessage() and decryptMessage()函数否则你不能绝对肯定程序总是工作的。但是这要花很多时间因为你必须在加密程序中输入信息设置密钥运行加密程序将密文粘贴到解密程序中设置密钥然后运行解密程序。您还需要用几个不同的密钥和消息重复这个过程这导致了许多令人厌烦的工作 相反让我们编写另一个生成随机消息和随机密钥的程序来测试密码程序。这个新程序会用来自transpositonecrypt.py的encryptMessage()对消息进行加密然后将密文从transpositonecrypt.py传给decryptMessage()。如果decryptMessage()返回的明文与原始消息相同程序就知道加密和解密程序工作了。使用另一个程序自动测试一个程序的过程叫做自动化测试。 需要尝试几种不同的信息和组合密钥但计算机只需一分钟左右的时间就可以测试成千上万种组合。如果所有这些测试都通过了您就可以更加确定您的代码能够正常工作。 本章涵盖的主题 random.randint()函数 random.seed()函数 列表的引用 copy.deepcopy()函数 random.shuffle()函数 随机打乱字符串 sys.exit()函数 换位密码测试器程序的源代码 选择文件 - 新文件打开新文件编辑器窗口。在文件编辑器中输入以下代码保存为transpositionTest.py。然后按F5运行程序。 换位 Test.py # Transposition Cipher Test # https://www.nostarch.com/crackingcodes/ (BSD Licensed)import random, sys, transpositionEncrypt, transpositionDecryptdef main():random.seed(42) # Set the random seed to a static value.for i in range(20): # Run 20 tests.# Generate random messages to test.# The message will have a random length:message ABCDEFGHIJKLMNOPQRSTUVWXYZ * random.randint(4, 40)# Convert the message string to a list to shuffle it:message list(message)random.shuffle(message)message .join(message) # Convert the list back to a string.print(Test #%s: %s... % (i 1, message[:50]))# Check all possible keys for each message:for key in range(1, int(len(message)/2)):encrypted transpositionEncrypt.encryptMessage(key, message)decrypted transpositionDecrypt.decryptMessage(key, encrypted)# If the decryption doesnt match the original message, display# an error message and quit:if message ! decrypted:print(Mismatch with key %s and message %s. % (key,message))print(Decrypted as: decrypted)sys.exit()print(Transposition cipher test passed.)# If transpositionTest.py is run (instead of imported as a module) call # the main() function: if __name__ __main__:main()换位密码测试程序的示例运行 当您运行transpositionTest.py程序时输出应该是这样的: Test #1: JEQLDFKJZWALCOYACUPLTRRMLWHOBXQNEAWSLGWAGQQSRSIUIQ... Test #2: SWRCLUCRDOMLWZKOMAGVOTXUVVEPIOJMSBEQRQOFRGCCKENINV... Test #3: BIZBPZUIWDUFXAPJTHCMDWEGHYOWKWWWSJYKDQVSFWCJNCOZZA... Test #4: JEWBCEXVZAILLCHDZJCUTXASSZZRKRPMYGTGHBXPQPBEBVCODM... --snip-- Test #17: KPKHHLPUWPSSIOULGKVEFHZOKBFHXUKVSEOWOENOZSNIDELAWR... Test #18: OYLFXXZENDFGSXTEAHGHPBNORCFEPBMITILSSJRGDVMNSOMURV... Test #19: SOCLYBRVDPLNVJKAFDGHCQMXIOPEJSXEAAXNWCCYAGZGLZGZHK... Test #20: JXJGRBCKZXPUIEXOJUNZEYYSEAEGVOJWIRTSSGPUWPNZUBQNDA... Transposition cipher test passed.测试器程序通过将transpositonecrypt.py和transpositonecrypt.py程序作为模块导入来工作。然后测试程序从加密和解密程序中调用encryptMessage()和decryptMessage()。测试程序创建一个随机消息并选择一个随机密钥。消息只是随机的字母并不重要因为程序只需要检查加密然后解密消息的结果是原始消息。 使用一个循环程序重复这个测试 20 次。如果从transpositionDecrypt()返回的字符串与原始消息不同程序会打印一个错误并退出。 让我们更详细地探索源代码。 导入模块 程序从导入模块开始包括您已经看到的 Python 自带的两个模块random和sys: # Transposition Cipher Test # https://www.nostarch.com/crackingcodes/ (BSD Licensed)import random, sys, transpositionEncrypt, transpositionDecrypt我们还需要导入换位密码程序即transpositonecrypt.py和transpositonecrypt.py只需输入它们的名称而不需要输入.py扩展。 创建伪随机数 为了创建随机数来生成消息和密钥我们将使用random模块的seed()函数。在我们深入研究种子做什么之前让我们通过尝试random.randint()函数来看看随机数在 Python 中是如何工作的。我们稍后将在程序中使用的random.randint()函数接受两个整数参数并返回这两个整数之间的一个随机整数包括整数。在交互式 shell 中输入以下内容: import randomrandom.randint(1, 20) 20random.randint(1, 20) 18random.randint(100, 200) 107当然您得到的数字可能与这里显示的不同因为它们是随机数。 但是 Python 的random.randint()函数生成的数字并不是真正随机的。它们是由伪随机数发生器算法产生的该算法采用一个初始数字并根据一个公式产生其他数字。 伪随机数发生器开始使用的初始数字称为种子。如果您知道种子生成器生成的其余数字是可预测的因为当您将种子设置为某个特定数字时相同的数字将以相同的顺序生成。这些看起来随机但可预测的数字被称为伪随机数。没有设置种子的 Python 程序使用计算机的当前时钟时间来设置种子。你可以通过调用random.seed()函数来重置 Python 的随机种子。 要证明伪随机数不是完全随机的请在交互式 shell 中输入以下内容: import random random.seed(42) # ➊ numbers [] # ➋ for i in range(20):... numbers.append(random.randint(1, 10))...[2, 1, 5, 4, 4, 3, 2, 9, 2, 10, 7, 1, 1, 2, 4, 4, 9, 10, 1, 9] # ➌ random.seed(42) numbers [] for i in range(20):... numbers.append(random.randint(1, 10))...[2, 1, 5, 4, 4, 3, 2, 9, 2, 10, 7, 1, 1, 2, 4, 4, 9, 10, 1, 9] # ➍在这段代码中我们使用相同的种子两次生成 20 个数字。首先我们导入random并将种子设置为42 ➊。然后我们建立一个名为numbers ➋ 的列表在那里我们将存储我们生成的数字。我们使用一个for循环来生成 20 个数字并将每个数字添加到numbers列表中我们打印这个列表这样我们就可以看到生成的每个数字 ➌。 当 Python 的伪随机数发生器的种子设置为42时1和10之间的第一个“随机”数将始终是2。第二个数字永远是1第三个数字永远是5依此类推。当您将种子重置为42并再次使用该种子生成数字时从random.randint()返回相同的伪随机数集您可以通过比较 ➌ 和 ➍ 的numbers列表看到这一点。 在后面的章节中随机数对于密码将变得很重要因为它们不仅用于测试密码还用于更复杂密码的加密和解密。随机数如此重要以至于加密软件中一个常见的安全缺陷就是使用可预测的随机数。如果你程序中的随机数是可以预测的密码分析员就可以用这些信息来破解你的密码。 以真正随机的方式选择加密密钥对于密码的安全性是必要的但是对于其他用途比如这个代码测试伪随机数就可以了。我们将使用伪随机数为我们的测试程序生成测试字符串。你可以通过使用random.SystemRandom().randint()函数用 Python 生成真正的随机数你可以在/www.nostarch.com/crackingcodes了解更多。 创建随机字符串 现在你已经学会了如何使用random.randint()和random.seed()来创建随机数让我们回到源代码。为了完全自动化我们的加密和解密程序我们需要自动生成随机的字符串消息。 为此我们将在消息中使用一个字符串随机复制几次并将其存储为一个字符串。然后我们将得到重复字符的字符串并将它们打乱使它们更加随机。我们将为每个测试生成一个新的随机字符串这样我们就可以尝试许多不同的字母组合。 首先让我们设置main()函数它包含测试密码程序的代码。它首先为伪随机字符串设置一个种子: def main():random.seed(42) # Set the random seed to a static value.通过调用random.seed()设置随机种子对测试程序很有用因为您想要可预测的数字所以每次程序运行时都选择相同的伪随机消息和密钥。因此如果您注意到一条消息未能正确加密和解密您将能够重现这个失败的测试用例。 接下来我们将使用一个for循环复制一个字符串。 将一个字符串随机复制若干次 我们将使用一个for循环来运行 20 个测试并生成我们的随机消息: for i in range(20): # Run 20 tests.# Generate random messages to test.# The message will have a random length:message ABCDEFGHIJKLMNOPQRSTUVWXYZ * random.randint(4, 40)每次for循环迭代时程序都会创建并测试一条新消息。我们希望这个程序运行多个测试因为我们尝试的测试越多我们就越能确定程序是有效的。 第 13 行是测试代码的第一行创建一条随机长度的消息。它获取一串大写字母并使用randint()和字符串复制在4和40之间随机复制该字符串。然后它将新字符串存储在message变量中。 如果我们让message字符串保持现在的样子它将永远只是重复了随机次数的字母字符串。因为我们想测试不同的字符组合我们需要更进一步打乱message中的字符。要做到这一点让我们先学习更多关于列表的知识。 列表变量与引用 变量存储列表与存储其他值不同。变量将包含对列表的引用而不是列表本身。一个引用是指向某个数据位的值一个列表引用是指向一个列表的值。这导致代码的行为略有不同。 你已经知道变量存储字符串和整数值。在交互式 shell 中输入以下内容: spam 42cheese spamspam 100spam 100cheese 42我们将42赋给spam变量然后复制spam中的值赋给变量cheese。当我们稍后将spam中的值更改为100时新数字不会影响cheese中的值因为spam和cheese是存储不同值的不同变量。 但是列表不是这样工作的。当我们把一个列表赋给一个变量时我们实际上是把一个列表引用赋给了这个变量。下面的代码使这种区别更容易理解。在交互式 shell 中输入以下代码: spam [0, 1, 2, 3, 4, 5] # ➊ cheese spam # ➋ cheese[1] Hello! # ➌ spam[0, Hello!, 2, 3, 4, 5] cheese[0, Hello!, 2, 3, 4, 5]您可能会觉得这段代码很奇怪。代码只改变了cheese列表但是cheese和spam列表都改变了。 当我们创建列表 ➊ 时我们在spam变量中为它分配一个引用。但是下一行 ➋ 只复制了spam到cheese中的列表引用而不是列表值。这意味着存储在spam和cheese中的值现在都指向同一个列表。只有一个底层列表因为实际的列表实际上从未被复制过。所以当我们修改cheese ➌ 的第一个元素时我们修改的是spam引用的同一个列表。 请记住变量就像包含值的盒子。但是列表变量实际上并不包含列表——它们包含对列表的引用。这些引用会有 Python 内部使用的 ID 号但是你可以忽略它们。用盒子来比喻变量图 9-1 显示了当一个列表被分配给spam变量时会发生什么。 图 9-1spam [0, 1, 2, 3, 4, 5]存储一个列表的引用而不是实际的列表。 然后在图 9-2 中将spam中的引用复制到cheese。只有一个新的引用被创建并存储在cheese中而不是一个新的列表。请注意这两个引用指的是同一个列表。 图 9-2spam cheese复制引用非列表。 当我们改变cheese引用的列表时spam引用的列表也会改变因为cheese和spam引用的是同一个列表。你可以在图 9-3 中看到这一点。 虽然 Python 变量在技术上包含了对列表值的引用但人们经常会随口说变量“包含列表” 图 9-3cheese[1] Hello!修改两个变量引用的列表。 引用传递 引用对于理解参数如何传递给函数特别重要。当一个函数被调用时参数的值被复制到形参变量中。对于列表这意味着引用的副本用于参数。要查看此操作的结果请打开一个新的文件编辑器窗口输入以下代码并将其保存为passingReference.py。按F5运行代码。 passingReference.py def eggs(someParameter):someParameter.append(Hello) spam [1, 2, 3] eggs(spam) print(spam)当您运行代码时请注意当调用eggs()时返回值并不用于为spam分配新值。而是直接修改列表。运行时该程序产生以下输出: [1, 2, 3, Hello]尽管spam和someParameter包含不同的引用但它们都引用同一个列表。这就是为什么函数内部的append(Hello)方法调用即使在函数调用返回后也会影响列表。 请记住这种行为:忘记 Python 以这种方式处理列表变量会导致令人困惑的错误。 使用copy.deepcopy()复制一个列表 如果您想要复制一个列表值您可以导入copy模块来调用copy.deepcopy()函数该函数返回一个单独的列表副本: spam [0, 1, 2, 3, 4, 5]import copycheese copy.deepcopy(spam)cheese[1] Hello!spam [0, 1, 2, 3, 4, 5]cheese [0, Hello!, 2, 3, 4, 5]因为使用了copy.deepcopy()函数将spam中的列表复制到cheese所以当cheese中的一项发生变化时spam不受影响。 当我们破解简单的替换密码时我们将在第 17 章中使用这个函数。 random.shuffle()函数 有了关于引用如何工作的基础您现在可以理解我们接下来要使用的random.shuffle()函数是如何工作的。random.shuffle()函数是random模块的一部分它接受一个列表参数并随机重新排列其条目。在交互式 shell 中输入以下内容看看random.shuffle()是如何工作的: import randomspam [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]spam [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]random.shuffle(spam)spam [3, 0, 5, 9, 6, 8, 2, 4, 1, 7]random.shuffle(spam)spam [1, 2, 5, 9, 4, 7, 0, 3, 6, 8]需要注意的一个重要细节是shuffle() 不返回列表值。相反它改变传递给它的列表值因为shuffle()直接从传递给它的列表引用值修改列表。shuffle()函数在处修改列表*这就是为什么我们执行random.shuffle(spam)而不是spam random.shuffle(spam)。* 随机打乱字符串 让我们回到transpositionTest.py。为了打乱字符串值中的字符我们首先需要使用list()将字符串转换成一个列表: # Convert the message string to a list to shuffle it:message list(message)random.shuffle(message)message .join(message) # Convert the list back to a string.从list()返回的值是一个列表值传递给它的是字符串中每个字符的一个字符串所以在第 16 行我们将message重新赋值为它的字符列表。接下来shuffle()将message中的项目顺序随机化。然后程序使用join()字符串方法将字符串列表转换回字符串值。这种对message字符串的混排允许我们测试许多不同的消息。 测试每条消息 既然已经生成了随机消息程序就用它来测试加密和解密函数。我们将让程序打印一些反馈这样我们可以在测试时看到它在做什么: print(Test #%s: %s... % (i 1, message[:50]))第 20 行有一个print()调用显示程序的测试号我们需要给i加 1因为i从0开始测试号应该从1开始。因为message中的字符串可能很长所以我们使用字符串切片只显示message的前 50 个字符。 第 20 行也使用了字符串插值。i 1求值的值替换字符串中的第一个%smessage[:50]求值的值替换第二个%s。使用字符串插值时请确保字符串中的%s的数量与它后面的括号中的值的数量相匹配。 接下来我们将测试所有可能的密钥。尽管凯撒密码的密钥可以是从0到65的整数符号集的长度换位密码的密钥可以在1和消息长度的一半之间。第 23 行上的for循环使用密钥1运行测试代码直到但不包括消息的长度。 # Check all possible keys for each message:for key in range(1, int(len(message)/2)):encrypted transpositionEncrypt.encryptMessage(key, message)decrypted transpositionDecrypt.decryptMessage(key, encrypted)第 24 行使用encryptMessage()函数加密message中的字符串。因为这个函数在transpositonecrypt.py文件里面所以我们需要在函数名前面加上transpositionEncrypt.以句号结尾。 从encryptMessage()返回的加密字符串然后被传递给decryptMessage()。我们需要对两个函数调用使用同一个密钥。来自decryptMessage()的返回值存储在一个名为decrypted的变量中。如果函数正常message中的字符串应该与decrypted中的字符串相同。接下来我们将看看程序如何检查这一点。 检查密码是否有效并结束程序 在我们加密和解密消息之后我们需要检查两个过程是否都正常工作。为此我们只需检查原始消息是否与解密后的消息相同。 # If the decryption doesnt match the original message, display# an error message and quit:if message ! decrypted:print(Mismatch with key %s and message %s. % (key,message))print(Decrypted as: decrypted)sys.exit()print(Transposition cipher test passed.)第 29 行测试message和decrypted是否相等。如果不是Python 会在屏幕上显示一条错误消息。第 30 行和第 31 行打印了key、message和decrypted值作为反馈帮助我们找出哪里出错了。然后程序退出。 通常情况下程序在执行到代码末尾时退出并且不再有代码行要执行。然而当调用sys.exit()时程序会立即结束并停止测试新消息因为即使有一次测试失败你也会想要修复你的密码程序. 但是如果message和decrypted中的值相等程序执行会跳过if语句的阻塞和对sys.exit()的调用。程序继续循环直到完成所有的测试。循环结束后程序运行第 34 行您知道这在第 9 行的循环之外因为它有不同的缩进。34 行版画 Transposition cipher test passed.。 调用main()函数 与我们的其他程序一样我们希望检查程序是作为模块导入还是作为主程序运行。 # If transpositionTest.py is run (instead of imported as a module) call # the main() function: if __name__ __main__:main()第 39 和 40 行完成了这个任务检查特殊变量__name__是否被设置为__main__如果是调用main()函数。 测试测试程序 我们已经编写了一个测试换位密码程序的程序但是我们怎么知道这个测试程序有效呢如果测试程序有一个 bug它只是表明换位密码程序工作而实际上它们并不工作怎么办 我们可以通过故意在加密或解密函数中添加错误来测试测试程序。然后如果测试程序没有检测到问题我们知道它没有按预期运行。 为了给程序添加一个 bug我们打开transpositonecrypt.py并将 1添加到第 36 行: 换位 Encrypt.py # Move currentIndex over:currentIndex key 1既然加密代码被破解了当我们运行测试程序时它应该会打印一个错误就像这样: Test #1: JEQLDFKJZWALCOYACUPLTRRMLWHOBXQNEAWSLGWAGQQSRSIUIQ... Mismatch with key 1 and message JEQLDFKJZWALCOYACUPLTRRMLWHOBXQNEAWSLGWAGQQSRSIUIQTRGJHDVCZECRESZJARAVIPFOBWZ XXTBFOFHVSIGBWIBBHGKUWHEUUDYONYTZVKNVVTYZPDDMIDKBHTYJAHBNDVJUZDCEMFMLUXEONCZX WAWGXZSFTMJNLJOKKIJXLWAPCQNYCIQOFTEAUHRJODKLGRIZSJBXQPBMQPPFGMVUZHKFWPGNMRYXR OMSCEEXLUSCFHNELYPYKCNYTOUQGBFSRDDMVIGXNYPHVPQISTATKVKM. Decrypted as: JQDKZACYCPTRLHBQEWLWGQRIITGHVZCEZAAIFBZXBOHSGWBHKWEUYNTVNVYPDIKHYABDJZCMMUENZ WWXSTJLOKJLACNCQFEUROKGISBQBQPGVZKWGMYRMCELSFNLPKNTUGFRDVGNPVQSAKK在我们故意插入一个 bug 后测试程序在第一条消息时失败了所以我们知道它完全按照我们的计划工作 总结 除了编写程序你还可以使用新的编程技能。你也可以给计算机编程来测试你写的程序以确保它们适用于不同的输入。编写代码来测试代码是一种常见的做法。 在本章中您学习了如何使用random.randint()函数来产生伪随机数以及如何使用random.seed()来重置种子以创建更多伪随机数。虽然伪随机数在加密程序中不够随机但在本章的测试程序中足够好。 您还了解了列表和列表引用之间的区别以及copy.deepcopy()函数将创建列表值的副本而不是引用值。此外您还了解了random.shuffle()函数如何通过使用引用来打乱列表项的位置从而打乱列表值中各项的顺序。 到目前为止我们开发的所有程序都只加密短消息。在第 10 章中你将学习如何加密和解密整个文件。 练习题 练习题的答案可以在本书的网站www.nostarch.com/crackingcodes找到。 如果你运行下面的程序它打印出数字 8下次运行时会打印出什么 import random random.seed(9) print(random.randint(1, 10))下面的程序输出什么 spam [1, 2, 3] eggs spam ham eggs ham[0] 99 print(ham spam)哪个模块包含deepcopy()函数 下面的程序输出什么 import copy spam [1, 2, 3] eggs copy.deepcopy(spam) ham copy.deepcopy(eggs) ham[0] 99 print(ham spam)
http://www.hkea.cn/news/14313651/

相关文章:

  • 选择郑州网站建设Wordpress页面手机不适配
  • 福建省建设银行招聘网站企业建设网站有哪些费用
  • 网站建设需求单如何提高搜索引擎优化
  • 怎样策划一个营销型网站织梦dedecms教育培训网站模板
  • jsp做网站的优点水果零售电子商务网站综合评价与建设研究
  • 遵义网站搭建公司哪家好安徽网架公司
  • 做网站怎么租用服务器吗wordpress建设购物网站
  • 比较好的免费外贸网站网站建设的步骤图片过程
  • 网站建设与维护流程图wordpress添加验证码
  • 动漫网站首页设计网站建站之后需要维护吗
  • 建设部咨询资质网站网站建设_超速云建站
  • 电商网站开发的目的是wordpress要更新
  • 网站备案好不好什么是跨境电商主要做什么
  • 毕业设计做网站难吗新乡高端网站建设
  • 360网站服务监控alexa排名全球前50网站
  • 中国工程建设企业协会网站中小微企业纳税申报
  • 哈市哪里网站做的好江苏专业做网站的公司
  • 做网站容易学吗做网站需不需要云数据库
  • 做网站需要考虑哪些摄影网站建站
  • 赣州互联网哪家好seo深度优化服务
  • 域名网站空间佛山从事网站建设
  • 岑溪网站开发我的家乡网页制作代码
  • 沂源网站建设聊城门户网站建设
  • 网站信息化建设报送烟台网站seo外包
  • 贵阳网站建设-中国互联wordpress 导入 wiki
  • 网站推广的宣传途径网络网站关键词
  • 户外商品网站制作久久建筑网下载教程
  • 企业展示网站建设多少钱做外贸找工厂货源网站
  • 郑州网站建设口碑好机构ui设计培训
  • 平潭建设局网站首页做网站广告怎么做