Python 密码学实用指南(全)(1)https://developer.aliyun.com/article/1507496
base64 编码
我们现在将讨论将 ASCII 数据编码为字节,并对这些字节进行 base64 编码。我们还将涵盖二进制数据的 base64 编码和解码,以恢复原始输入。
ASCII 数据
在 ASCII 中,每个字符变成一个字节:
A
在十进制中是65
,在二进制中是0b01000001
。这里,你在最高位没有128
,然后在下一个位上有64
的1
,最后有1
,所以你有64 + 1=65。- 接下来是
B
,基数为66
,C
,基数为67
。B
的二进制是0b01000010
,C
的二进制是0b01000011
。
三个字母的字符串ABC
可以解释为一个 24 位的字符串,看起来像这样:
我们添加了这些蓝线只是为了显示字节的分隔位置。要将其解释为 base64,你需要将其分成 6 位一组。6 位有 64 种组合,所以你需要 64 个字符来编码它。
使用的字符如下:
我们使用大写字母表示前 26 个,小写字母表示另外 26 个,数字表示另外 10 个,总共 62 个字符。在最常见的 base64 形式中,最后两个字符使用+
和/
:
如果你有一个 ASCII 字符串有三个字符,它变成 24 位,解释为 3 组 8 位。如果你把它们分成 4 组 6 位,你有 4 个 0 到 63 之间的数字,在这种情况下,它们变成Q
、U
、J
和D
。在 Python 中,你只需要一个字符串,后面跟着命令:
>>> "ABC".encode("base64") 'QUJD\n'
这将进行编码。然后在最后添加一个额外的回车,这既不重要也不影响解码。
如果你有的不是 3 个字节的组合呢?
等号=
用于指示填充,如果输入字符串长度不是 3 个字节的倍数。
如果输入有四个字节,那么 base64 编码以两个等号结束,只是表示它必须添加两个填充字符。如果有五个字节,就有一个等号,如果有六个字节,那么就没有等号,表示输入完全适合 base64,不需要填充。填充是空的。
你取ABCD
进行编码,然后你取ABCD
并加上一个显式的零字节。x00
表示一个具有八位零的单个字符,你得到相同的结果,只是多了一个A
和一个等号,如果你用两个零字节填满它,你会得到大写的A
。记住:大写的A
是base64
中的第一个字符。它代表六位零。
让我们来看看 Python 中的 base64 编码:
- 我们将启动
python
并创建一个字符串。如果你只是用引号创建一个字符串并按Enter,它会立即打印出来:
>>> "ABC" 'ABC'
- Python 会自动打印每次计算的结果。如果我们用
base64
对其进行编码,我们会得到这个结果:
>>> "ABC".encode(""base64") 'QUJD\n'
- 它变成
QUJD
,最后有一个额外的回车,如果我们让它更长:
>>> "ABCD".encode("base64") 'QUJDRA==\n'
- 这里有两个等号,因为我们从四个字节开始,它必须再添加两个字节使其成为 3 的倍数:
>>> "ABCDE".encode("base64") 'QUJDREU=\n' >>> "ABCDEF".encode("base64") 'QUJDREVG\n'
- 有五个字节的输入,我们有一个等号;有六个字节的输入,我们没有等号,而是一共有八个字符使用
base64
。 - 让我们回到带有两个等号的
ABCD
:
>>>"ABCD".encode("base64") 'QUJDRA==\n'
- 你可以看到填充是如何通过在这里明确放置它来完成的:
>>> "ABCD\x00\x00".encode("base64") 'QUJDRAA=\n'
有一个零的第一个字节,现在我们得到另一个单个等号。
- 让我们再加入一个字节的零:
>>> "ABCD\x00\x00".encode("base64") 'QUJDRAAA\n'
这里没有填充,我们看到最后的字符都是A
,表明已经填充了二进制零。
二进制数据
下一个问题是处理二进制数据。可执行文件是二进制的,而不是 ASCII。此外,图像、电影和许多其他文件都包含二进制数据。ASCII 数据始终以第一个位为零开始,但base64
可以很好地处理二进制数据。这是一个常见的可执行文件,一个法医实用程序;它以MZê
开头,并且有不可打印的 ASCII 字符:
由于这是一个十六进制查看器,您可以看到十六进制的原始数据,在右侧,它尝试将其打印为 ASCII。Windows 程序在开头有这个字符串,并且这个程序不能在 DOS 模式下运行,但它们有很多不可打印的字符,比如FF
和0
,这对 Python 来说并不重要。像这样编码数据的简单方法是直接从文件中读取它。您可以使用with
命令。它将使用文件名和读取二进制模式打开一个文件,并使用句柄f
读取它。with
命令在这里只是告诉 Python 打开文件,并且如果由于某些错误无法打开文件,则关闭句柄,然后以完全相同的方式解码它。要解码以这种方式编码的数据,只需取输出字符串,并将.encode
替换为.decode
。
现在让我们看看如何处理二进制数据:
- 我们将首先退出 Python,以便我们可以查看文件系统,然后我们将使用以下命令查找
Ac
文件:
>>> exit() $ ls Ac* AccessData Registry Viewer_1.8.3.exe
这是文件名。由于这是一个比较长的块,我们只需复制并粘贴它。
- 现在我们启动 Python 并使用以下命令
clear
屏幕:
$ clear
- 我们将重新开始
python
:
$ python
- 好的,现在我们使用以下命令:
>>> with open("AccessData Registry Viewer_1.8.3.exe", "rb") as f: ... data = f.read() ... print data.encode("base64")
这里我们首先输入文件名,然后是读取二进制模式。我们将给它一个文件名句柄f
。我们将获取所有数据并将其放入一个单一变量数据中。我们可以只对数据进行base64
编码,它会自动打印出来。如果您在 Python 中有一个预期的块,您必须按Enter键两次,以便它知道块已完成,然后base64
对其进行编码。
- 您会得到一个很长的
base64
块,这不太可读,但这是处理这种数据的一种方便方式;比如,如果您想要通过电子邮件发送它或将其放入其他文本格式中。因此,为了进行解码,让我们编码一些更简单的东西,以便我们可以轻松地看到结果:
>>> "ABC".encode("base64") 'QUJD\n'
- 如果我们想要使用它,可以使用以下命令将其放入一个
c
变量中:
>>> c = "ABC".encode("base64") >>> print c QUJD
- 现在我们可以打印
c
以确保我们得到了预期的结果。我们有QUJD
,这是我们预期的结果。所以,现在我们可以使用以下命令对其进行解码:
>>> c.decode("base64") 'ABC'
base64
不是加密。它不隐藏任何东西,而只是另一种表示方法。在下一节中,我们将介绍 XOR。
XOR
本节解释了 XOR 在单个位上的真值表,然后展示了如何在字节上进行操作。XOR 可以撤销自身,因此解密与加密是相同的操作。您可以使用单个字节或多个字节密钥进行 XOR,并且我们将使用循环来测试密钥。以下是 XOR 的真值表:
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
如果您输入两个位,并且这两个位相同,则答案是0
。如果位不同,则答案是1
。
XOR 一次操作一个位。Python 使用^
运算符表示 XOR。
真值表显示了它的工作原理。您输入可能是0
和1
的位,并将它们进行异或运算,然后最终得到 50%的 1 和 0,这意味着异或不会破坏任何信息。
这是字节的异或:
A 0b01000001
B 0b01000010
XOR 0b00000011
A
是数字65
,所以你有64
的1
和1
的1
;B
大 1,如果你将它们进行 XOR 操作,所有的位匹配前 6 位,它们都是0
。最后两位不同,它们变成了1
。这是二进制值3
,它不是一个可打印的字符,但你可以将它表示为一个整数。
密钥可以是单字节或多字节。如果密钥是单字节,比如B
,那么你可以使用相同的字节来加密每个明文字符。只需一直重复使用密钥:
为这个字节重复B
,那个字节也是B
,依此类推。如果密钥是多字节的,那么你就重复这个模式:
你用B
代表第一个字节,C
代表下一个字节,然后再次用B
代表下一个字节,C
代表下一个字节,依此类推。
在 Python 中,你需要循环遍历字符串的字节并计算一个索引来显示你所在的字节。然后我们从用户那里输入一些文本,计算它的长度,然后遍历从1
到字符串长度的索引,从0
开始。然后我们取文本字节并在这里打印出来,这样你就可以看到循环是如何工作的。所以,如果我们给它一个五个字符的明文,比如HELLO
,它就会一个接一个地打印出字符。
要进行异或操作,我们将输入一个明文和一个密钥,然后取一个文本字节和一个密钥字节,进行异或操作,然后打印出结果。
注意%len( key)
,这可以防止你超出密钥的末尾。它将一直重复密钥中的字节。因此,如果密钥是三个字节长,这将是模三,所以它将计数为0
,1
,2
,然后回到0 1 2 0 1 2
,依此类推。这样,你可以处理任意长度的明文。
如果你结合大写和小写字母,你经常会发现 XOR 产生无法打印的字节的情况。在接下来的例子中,我们使用了HELLO
,Kitty
和一个qrs
的密钥。请注意,其中一些字节是可以打印的,而其中一些包含奇怪的字符,比如Esc和Tab,这些很难打印。因此,处理输出的最佳方式不是尝试将其作为 ASCII 打印,而是将其作为hex
编码的值打印。我们不是一个接一个地打印字节,而是将它们组合成一个cipher
变量,最后,我们以hex
形式打印出整个明文,整个密钥,然后是整个密文。这样,它可以正确处理这些难以打印的奇怪值。
让我们在 Python 中尝试这个循环:
- 我们打开终端并输入以下命令:
$ nano xor1.py
- 当你运行它时,你会得到以下输出:
- 这是第一个
xor1.py
,所以我们从用户那里输入文本,计算它的长度,然后一个接一个地打印出字节,以查看循环是如何工作的。让我们运行它并给它HELLO
:
- 它只是一个接一个地打印出字节。现在,让我们看一下下一个 XOR 2:
这里输入text
和key
,然后以相同的方式进行处理,遍历text
的每个字节,使用模运算挑选出key
的正确字节,执行异或操作,然后打印出结果。
- 所以如果我们在这里运行相同的文件,我们取
HELLO
和一个key
如下所示:
$ nano xor2.py $ python xor2.py
因此,输出如下:
它逐个计算字节。请注意,这里我们得到了两个等号,这就是为什么你会使用多字节key
的原因,因为明文在变化,但密钥也在变化,而这种模式在输出中没有反映出来,所以它是更有效的混淆。
- 清除并查看第三个
xor2a.py
文件:
这样你就可以看到,这解决了无法打印的字节的问题。
- 因此,我们创建了一个名为
cipher
的变量,在这里组合了每个输出字节,最后,我们用hex
编码它,而不是直接尝试将其打印出来:
- 如果你给它
HELLO
,然后输入一个qrs
的键,它会给你明文HELLO Kitty
,键,然后是十六进制编码的输出,这可以轻松处理有趣的字符,比如0 7
和0 5
。在下一节中,你将看到挑战 1 – 凯撒密码。
挑战 1 – 凯撒密码
经过凯撒密码的复习,我们将有一个解决它的例子,然后是你的挑战。记住凯撒密码是如何工作的。你有一个可用字符的字母表,你输入消息和一个shift
值,然后你只需将字符向前移动那么多步,如果超出字母表的末尾就回到开头。我们最终得到的脚本适用于任何shift
值,包括正常的数字,比如3
,甚至大于26
的数字;它们只是循环并且可以混淆你输入的任何数据。
这是一个例子:
- 对于密文,你可以尝试从
0
到25
的所有shift
值,其中一个将是可读的。这是一个简单的暴力攻击。让我们来看看。
在这里,在 Python 中,去caesar4
脚本,我们之前有过。它接受一个字符串并将其按你指定的任何值进行移位。如果我们使用那个脚本,我们可以运行它如下:
- 然后,如果我们输入
HELLO
并将其移位3
,它就会变成KHOOR
。 - 如果我们想要破解它,我们可以使用以下解决方案脚本:
- 所以,如果我们使用那个脚本,我们可以运行它:
- 如果我们输入
KHOOR
,它将以各种值进行移位,你可以看到在23
时可读的值是HELLO
。所以,我们之前讨论的更长的密文等等的例子,在3
时变得可读,你会看到它是DEMONSTRATION
:
- 你的挑战是解密这个字符串:
MYXQBKDEVKDSYXC
。
在下一节中,我们将有一个关于base64
的挑战。
挑战 2 – base64
经过base64
的复习,我们将进行一个例子,向你展示如何解码一些混淆的文本,然后我们为你准备了一个简单的和一个困难的挑战。
这是base64
的复习:
base64
编码文本会变得更长。这是要解码的示例文本:
U2FtcGxliHRleHQ=
它解码成示例文本字符串。让我们看看。
参考以下步骤:
- 如果你在立即模式下运行
python
,它将执行四个简单的任务:
$ python
- 所以,如果我们取
ABC
并用base64
编码,我们会得到这个字符串:
>>> "ABC".encode("base64") 'QUJD\n'
- 如果我们用
base64
解码它,我们会得到原始文本:
>>> "QUJD".decode("base64") 'ABC'
- 所以,挑战文本如下,如果你解码它,你会得到示例文本字符串:
>>> "U2FtcGxliHRleHQ=".decode("base64") 'Sample text'
- 这对于简单情况足够了;你的第一个挑战看起来是这样的:
Decode this: VGhpcyBpcyB0b28gZWFzeQ==
- 这是一个要解码的长字符串,用于你的更长的挑战:
Decode this: VWtkc2EwbEliSFprVTJeFl6SlZaMWxUUW5OaU1qbDNVSGM5UFFvPQo=
这个长字符串之所以这么长,是因为它被base64
编码了不止一次,而是多次。所以,你需要尝试解码它,直到它变成可读的内容。在下一节中,我们将有挑战 3 – 异或。
挑战 3 – 异或
在这一节中,我们将复习异或的工作原理,然后给你一个例子,然后提出两个挑战。
所以,这是我们之前讨论过的一个异或程序:
你输入任意文本和一个任意的键,然后逐个字节地遍历它们,挑选出一个文本字节和一个键字节,然后用异或结合它们并打印出结果。所以,如果你输入HELLO
和qrs
,你会得到用异或加密的东西。
这里有一个例子:
它会解密成EXAMPLE
。所以,这是解密;记住异或会解开自己。
如果你想破解其中一个,一个简单的方法就是尝试每个键并打印出每个结果,然后读出可读的键。
所以,我们尝试从0
到9
的所有单个数字键。
结果是你输入密文,用每个值加密它,当你得到正确的键值时,它将变成可读的文本。
让我们来看看:
这是解密例程,它简单地从用户输入文本,然后尝试这个字符串中的每个密钥,0
到9
。对于这些中的每一个,它将 XOR 文本组合成一个名为clear
的变量,以便可以为每个密钥打印一行,然后清晰结果。因此,如果我们运行它并输入我的密文,它会给我们 10 行。
我们只是浏览了这些行并看到哪一个变得可读,您可以看到正确的密钥和正确的明文在6
处。第一个挑战就在这里:
这与我们之前看到的类似。密钥是一个数字,它将解密为可读的内容。这是一个以十六进制格式的更长的示例:
密钥是两个 ASCII 数字,因此您将不得不尝试 100 种选择来找到将其转换为可读字符串的方法。
总结
在本章中,设置 Python 之后,我们介绍了简单的替换密码、凯撒密码,然后是base64
编码。我们每次收集六位数据而不是八位数据,然后我们看了 XOR 编码,其中位根据密钥逐个翻转。我们还看到了一个非常简单的真值表。您完成的挑战是破解凯撒密码而不知道密钥,通过将base64
反向解码以获取原始字节,并尝试所有可能的密钥进行暴力攻击来破解 XOR 加密。在第二章 哈希中,我们将介绍不同类型的哈希算法。
第二章:哈希
哈希有两个主要目的:第一个是在文件上放置一个指纹,以便您可以判断它是否已被更改,第二个是隐藏密码,以便您仍然可以识别正确的密码并启用登录,但是窃取哈希的人不能轻松地从中恢复密码。
在本章中,我们将涵盖以下主题:
- MD5 和 SHA 哈希
- Windows 密码哈希
- Linux 密码哈希
- 挑战 1 - 破解 Windows 哈希
- 挑战 2 - 破解多轮哈希
- 挑战 3 - 破解 Linux 哈希
MD5 和 SHA 哈希
在解释哈希函数是什么之后,我们将处理 MD5,然后是 SHA 系列:SHA-1,SHA-2 和 SHA-3。我们还将获取一些关于破解哈希的信息。
哈希是什么?
如前所述,使用哈希的一个目的是在文件上放置一个指纹。您可以使用哈希算法将文件中的所有字节组合在一起,从而创建一个固定的哈希值。如果更改文件的任何部分并重新计算哈希,则会得到完全不同的值。因此,如果您有两个应该相同的文件,您可以计算每个文件的哈希值,如果两个文件的哈希值匹配,则文件相同。
一个非常常见的哈希是 MD5;它已经存在了几十年。它的长度为 128 位,对于哈希函数来说相当短,对于大多数目的来说足够可靠。人们用它来对下载和恶意软件样本等进行指纹识别,有时也用于隐藏密码。它不是一个完美的哈希函数:已知有一些碰撞,并且有一些算法可以在一些计算时间的代价下创建碰撞,这些碰撞是哈希到相同值的文件对。因此,如果您找到两个具有匹配 MD5 的文件,您并不完全确定它们是相同的文件,但它们通常是。
在 Python 中计算它们非常容易。您只需导入哈希库,然后进行计算。您调用哈希库来创建一个新对象。第一个参数是使用的算法,即 MD5。第二个参数是要进行哈希处理的数据的内容。
在这里,我们将使用HELLO
作为示例,然后您需要在末尾使用十六进制摘要,否则它将只打印数据结构的地址,而不是显示实际值。我们将使用HELLO
的哈希,MD5 和十六进制,它有 128 位长。因此,这是 128 除以 4,或 32 个十六进制字符,如果您向HELLO
添加另一个字符,比如感叹号,哈希将完全改变;一个值的哈希与下一个值的哈希之间没有任何相似之处。
安全哈希算法(SHA)旨在改进 MD5,直到大约一年前,SHA-1 没有发生碰撞,当时一些谷歌公司的研究人员发现了如何在 SHA-1 中发生碰撞,因此谨慎的人们正在转向 SHA-2。还有另一个由国家标准技术研究所批准的算法,称为SHA-3,几乎没有人使用,因为据所有人的预期,SHA-2 将在很长一段时间内保持安全。但是,如果发生了危及 SHA-2 的情况,SHA-3 将可供我们使用。SHA-2 和 SHA-3 都有各种长度,但最常见的长度是 256 和 512 位。
您可以在 Python 中轻松计算 SHA-1 和 SHA-2 哈希,但 SHA-3 并不常用,它还不是这个哈希库的一部分。因此,如果您使用 SHA-1 算法,您将得到一个 SHA-1 哈希。它看起来像 MD5 哈希,但更长。然后有 SHA-256 和 SHA-512,它们都是 SHA-2 哈希。您可以看到,尽管它们更安全,但它们更长,而且有些不太方便:
所以,让我们来看看。
打开终端并执行python
命令以启动 Python 终端:
然后,您可以运行以下命令:
您必须导入hashlib
。然后,您可以添加hashlib.new
。第一个参数是算法,这种情况下是md5
。下一个参数是要进行哈希的数据,这里是HELLO
,然后添加hexdigest
以查看十六进制值。所以,这是HELLO
的哈希,如果我们在末尾添加另一个字符,使其变成HELLOa
,那么我们会得到一个完全不同的答案。
如果我们想使用不同的算法,我们只需输入 SHA-1:
现在我们得到了一个很长的哈希值,如果我们添加sha256
作为字符,我们会得到一个更长的哈希值:
这些哈希对于几乎任何目的都足够了。
如果您有某物的哈希值,并且想要计算它来自哪些数据,原则上,这并没有唯一的解决方案。不过,在实践中,对于像密码这样的短对象,是有的。因此,如果有人使用MD5
函数来隐藏密码,这是一些旧的 Web 应用程序所做的,那么您可以通过猜测密码来反转它,直到找到匹配项。没有数学方法可以撤消哈希函数,因此您只需制作一个库。在MD5
哈希HELLO
的示例中,如果您只是进行一系列猜测,您将得到正确的答案。这就是哈希破解的工作原理;这不是一个复杂的想法,只是有点不方便。
我们可以获取HELLO
的 MD5 哈希并继续猜测:
如果我们在猜测单词,可能需要猜测数百万个单词才能得到所示的值,但是如果我们能够猜到正确的值,当哈希值匹配时,我们就知道它是正确的。这个的难度取决于您每秒可以计算多少个哈希值,而 MD5 和 SHA 系列都设计为非常快速计算,因此您实际上可以尝试数百万个密码。在下一节中,我们将讨论 Windows 密码哈希。
Python 密码学实用指南(全)(3)https://developer.aliyun.com/article/1507501