密码学有什么帮助?

在本节中,你将学习一种保护数据安全的方法,即创建自己的加密密钥并在服务器和客户机上使用它们。虽然这不是你的最后一步,但它将帮助你为学会构建Python HTTPS应用程序奠定坚实的基础。

了解密码学基础知识

密码学是一种保护通信免受窃听或攻击的方法。另一种说法是,你获取正常的信息(称为明文),然后把它转换成加密的文本(称为密文)。

密码学一开始可能很吓人,但基本概念是很容易理解的。事实上,你以前可能已经练习过密码学。如果你曾经和你的朋友有过一种秘密语言,并在课堂上用它来传递笔记,那么你就已经练习过密码学。(如果你还没做到,别担心,你即将做到。)

不管什么理由,现在你需要把字符串fluffy tail转换成一些难以理解的东西。一种方法是将某些字符映射到不同的字符上,还有一种有效的方法是将字母向后移动一个位置,这种做法看起来是这样的:

image-15

此图显示如何从原始字母表转换为新字母表并返回。所以,如果你的信息是ABC,那么实际上发送的信息将会是ZAB。如果把这个应用到fluffy tail上,且长度不变,就得到ekteex szhk,虽然并不完美,但任何人看到都会觉得它是胡言乱语。

祝贺你!你已经创建了在密码学中称为密码的东西,它描述了如何将明文转换为密文并返回。在这种情况下,你的密码是用英语描述的。这种特殊类型的密码称为替换密码。基本上,这与Enigma机器(https://en.wikipedia.org/wiki/Enigma_Machine)中使用的密码类型相同,只是简单得多。

现在,如果你想把信息传给秘密松鼠,那么你首先需要告诉它们要移动多少个字母,然后把编码的信息发给它们。在Python中,这可能类似于以下内容:

CIPHER = {"a": "z", "A": "Z", "b": "a"} # And so on

def encrypt(plaintext: str):
    return "".join(CIPHER.get(letter, letter) for letter in plaintext)

在这里,你创建了一个名为encrypt()的函数,它将获取明文并将其转换为密文。想象一下,你有一本字典CIPHER,它把所有的字符都标出来了。类似地,你可以创建一个decrypt()

DECIPHER = {v: k for k, v in CIPHER.items()}

def decrypt(ciphertext: str):
    return "".join(DECIPHER.get(letter, letter) for letter in ciphertext)

此函数与encrypt()相反,它将接受密文并将其转换为明文。在这种形式的密码中,你有一个特殊的密钥,用户需要知道该密钥才能对消息进行加密和解密。对于上面的示例,该密钥是1。也就是说,密码指示你应该将每个字母移回一个字符。密钥对于保密非常重要,因为任何拥有密钥的人都可以轻松地解密你的信息。

注意:虽然你可以用它来加密,但这仍然不是很安全。这个密码使用频率分析很容易破解,并且对秘密松鼠来说太原始了。

在现代社会,密码学要先进得多,它依赖于复杂的数学理论和计算机科学来保证安全。虽然这些密码背后的数学不在本文的讨论范围内,但基本概念是相同的。你有一个密码,它描述了如何获取明文并将其转换为密文。

你的替换密码和现代密码的唯一真正区别是:现代密码在数学上被证明是无法被窃听者破解的。现在,让我们看看如何使用你的新密码。

image-17
预计 2020 年 3 月发行‌‌

在Python HTTPS应用中使用密码学

幸运的是,你不必成为数学或计算机科学的专家就可以使用密码学。Python有一个secrets模块,可以帮助你生成密码安全的随机数据。在本文中,你将了解一个名为cryptography的Python库,可以用pip安装它:

$ pip install cryptography

安装了cryptography之后,你现在可以使用Fernet方法以数学上安全的方式加密和解密。

记得你密码里的密钥是1。同样,你需要创建一个密钥,以便让Fernet正常运行:

>>> from cryptography.fernet import Fernet
>>> key = Fernet.generate_key()
>>> key
b'8jtTR9QcD-k3RO9Pcd5ePgmTu_itJQt9WKQPzqjrcoM='

在这段代码中,导入了Fernet并生成了一个密钥。密钥只是一个bytes对象,但是保持密钥的机密性和安全性是非常重要的。就像上面的替换示例一样,任何具有此密钥的人都可以轻松地解密你的信息。

注意:在现实生活中,你会把这个密钥保管得很安全。在这些例子中,查看密钥是有帮助的。但这是一个糟糕的做法,特别是如果你在公共网站上发布它!换言之,不要使用你在上面看到的确切的密钥来获得你想要的安全性。

这个密钥的运行方式与前面的密钥很相似,用它可以将明文转换为密文,并且能够解密返回明文。现在是有趣的部分了!你可以加密如下信息:

>>> my_cipher = Fernet(key)
>>> ciphertext = my_cipher.encrypt(b"fluffy tail")
>>> ciphertext
b'gAAAAABdlW033LxsrnmA2P0WzaS-wk1UKXA1IdyDpmHcV6yrE7H_ApmSK8KpCW-6jaODFaeTeDRKJMMsa_526koApx1suJ4_dQ=='

在这段代码中,创建了一个名为my_cipher的Fernet对象,然后可以使用它来加密信息。注意,你的秘密信息fluffy tail必须是bytes对象才能对其进行加密。加密后,可以看到“密文”是一个长字节流。

多亏了Fernet,这个密文没有密钥就不能被操作或阅读!这种加密要求服务器和客户端都有权访问密钥。当双方都需要相同的密钥时,这称为对称加密。在下一节中,你将看到如何使用这种对称加密来保证数据的安全。

确保数据安全

现在,你已经了解了Python中密码学的一些基础知识,可以将这些知识应用到你的服务器上。创建名为symmetric_server.py的新文件:

# symmetric_server.py
import os
from flask import Flask
from cryptography.fernet import Fernet

SECRET_KEY = os.environb[b"SECRET_KEY"]
SECRET_MESSAGE = b"fluffy tail"
app = Flask(__name__)

my_cipher = Fernet(SECRET_KEY)

@app.route("/")
def get_secret_message():
    return my_cipher.encrypt(SECRET_MESSAGE)

此代码将原始服务器代码与上一节中使用的Fernet对象组合在一起。现在使用os.environb将密钥作为bytes对象从环境变量中读取。扫清了服务器方面的障碍之后,你现在可以专注于客户端。将以下内容粘贴到symmetric_client.py中:

# symmetric_client.py
import os
import requests
from cryptography.fernet import Fernet

SECRET_KEY = os.environb[b"SECRET_KEY"]
my_cipher = Fernet(SECRET_KEY)

def get_secret_message():
    response = requests.get("http://127.0.0.1:5683")

    decrypted_message = my_cipher.decrypt(response.content)
    print(f"The codeword is: {decrypted_message}")

if __name__ == "__main__":
    get_secret_message()

这是修改后的代码,用于把你的早期客户端与Fernet加密机制相结合。get_secret_message()执行以下操作:

  • 向服务器发出请求。
  • 从响应中获取原始字节。
  • 尝试解密原始字节。
  • 打印解密的信息。

如果同时运行服务器和客户端,你将看到正在成功地加密和解密你的秘密信息:

$ uwsgi --http-socket 127.0.0.1:5683 \
    --env SECRET_KEY="8jtTR9QcD-k3RO9Pcd5ePgmTu_itJQt9WKQPzqjrcoM=" \
    --mount /=symmetric_server:app

在此调试中,你将再次在端口5683上启动服务器。这一次,传入的SECRET_KEY 必须至少是长度为32的base64编码字符串。重新启动服务器后,你现在可以查询它:

$ SECRET_KEY="8jtTR9QcD-k3RO9Pcd5ePgmTu_itJQt9WKQPzqjrcoM=" python symmetric_client.py
The secret message is: b'fluffy tail'

哇!你已经实现加密和解密了。如果尝试使用无效的SECRET_KEY运行此操作,则会出现错误:

$ SECRET_KEY="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" python symmetric_client.py
Traceback (most recent call last):
  File ".../cryptography/fernet.py", line 104, in _verify_signature
    h.verify(data[-32:])
  File ".../cryptography/hazmat/primitives/hmac.py", line 66, in verify
    ctx.verify(signature)
  File ".../cryptography/hazmat/backends/openssl/hmac.py", line 74, in verify
    raise InvalidSignature("Signature did not match digest.")
cryptography.exceptions.InvalidSignature: Signature did not match digest.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "symmetric_client.py", line 16, in <module>
    get_secret_message()
  File "symmetric_client.py", line 11, in get_secret_message
    decrypted_message = my_cipher.decrypt(response.content)
  File ".../cryptography/fernet.py", line 75, in decrypt
    return self._decrypt_data(data, timestamp, ttl)
  File ".../cryptography/fernet.py", line 117, in _decrypt_data
    self._verify_signature(data)
  File ".../cryptography/fernet.py", line 106, in _verify_signature
    raise InvalidToken
cryptography.fernet.InvalidToken

所以,你知道加密和解密是有效的。但它安全吗?是的。为了证明这一点,你可以回到Wireshark,使用与以前相同的过滤器开始新的捕获。完成捕获设置后,再次运行客户端代码:

$ SECRET_KEY="8jtTR9QcD-k3RO9Pcd5ePgmTu_itJQt9WKQPzqjrcoM=" python symmetric_client.py
The secret message is: b'fluffy tail'

你已经成功地发出了另一个HTTP请求和响应,并且再次在Wireshark中看到这些信息。由于加密信息只在响应中传输,你可以单击该信息查看数据:

image-16

在图片的中间一行,可以看到实际传输的数据:

gAAAAABdlXSesekh9LYGDpZE4jkxm4Ai6rZQg2iHaxyDXkPWz1O74AB37V_a4vabF13fEr4kwmCe98Wlr8Zo1XNm-WjAVtSgFQ==

棒极了!这意味着数据是加密的,窃听者不知道信息内容实际上是什么。不仅如此,这也意味着他们可能会花费大量的时间试图暴力破解这些数据,而且他们几乎永远不会成功。

你的数据是安全的!但是等一下——以前使用Python HTTPS应用时,不需要知道任何关于钥匙的事情。这是因为HTTPS不专门使用对称加密。事实证明,分享秘密是个难题。

要证明这个概念,请在浏览器中输入http://127.0.0.1:5683,你将看到加密的响应文本。这是因为你的浏览器对你的密钥一无所知。那么Python HTTPS应用程序到底是如何工作的呢?这就是非对称加密发挥作用的地方。

如何共享密钥?

在上一节中,你了解了如何使用对称加密来保证数据在Internet上的安全。尽管对称加密是安全的,但它并不是Python HTTPS应用用来保证数据安全的唯一加密技术。对称加密引入了一些不易解决的基本问题。

注意:记住,对称加密要求在客户端和服务器之间有一个共享密钥。不幸的是,安全性的工作强度取决于最弱的链接,而在对称加密中,弱链接尤其具有灾难性。一旦一个人泄露了密钥,那么每个密钥都会泄露。可以肯定的是,任何安全系统在某个时候都会受到损害。

那么,你怎么改变密钥?如果你只有一个服务器和一个客户端,这可能是一个快速的任务。然而,随着客户端和服务器的增多,为了有效地更改密钥和保护信息,需要进行越来越多的协调。

而且,你每次都要选择一个新的加密方式。在上面的示例中,你看到一个随机生成的密钥,几乎不可能试着让人们记住那个密钥。随着客户端和服务器数量的增长,可能会使用更容易记住和猜测的密钥。

如果处理好了更改密钥的问题,那么还有一个问题要解决,如何分享你的初始密钥?在秘密松鼠示例中,你通过对每个成员进行物理访问来解决了这个问题,可以亲自把密钥告诉每个成员,让他们保守秘密,但要记住,有人会是最薄弱的环节。

现在,假设你从另一个物理位置向秘密松鼠会添加一个成员,如何与这个会员分享这个秘密?每次更改密钥时,你都让他们搭飞机去找你吗?如果你能把密钥放在你的服务器上并自动共享,那就太好了。不幸的是,这会挫败加密的全部目的,因为任何人都可以得到密钥!

当然,你可以给每个人一个初始的主密钥来获取秘密信息,但现在你遇到的问题是以前的两倍。如果你为之头痛,别担心!你不是唯一一个。

你需要的是两个从未交流过的人有一个共同的秘密。听起来不可能,对吧?幸运的是,有三个人:拉尔夫·梅克尔、惠特菲尔德·迪菲和马丁·赫尔曼,他们支持你,他们证明了公钥加密(也就是所谓的非对称加密)是可能的。

注:虽然惠特菲尔德·迪菲和马丁·赫尔曼被广泛认为是第一个发现这一计划的人,但据1997年的披露,在GCHQ工作的三人:詹姆斯·H·埃利斯、克利福德·考克斯和马尔科姆·J·威廉森早在七年前就展示了这种功能!

非对称加密允许两个从未有过通信的用户共享一个共同的秘密。理解基本原理的最简单方法之一是使用颜色类比。假设你有以下场景:

image-18

在这个图表中,你试图与一个你从未见过的“秘密松鼠”成员交流,但间谍可以看到你发送的所有信息。你知道对称加密并且想使用它,但是首先需要共享一个密钥。幸运的是,你们俩都有私钥。不幸的是,你不能发送你的私钥,因为间谍会看到它。那你怎么办?

你需要做的第一件事就是同意使用你的伙伴的颜色,比如黄色:

image-19

注意这里间谍可以看到共享的颜色,你和秘密松鼠也可以。共享颜色实际上是公开的。现在,你和秘密松鼠将你的私钥与共享颜色结合起来:

image-20

你的颜色组合成绿色,而秘密松鼠的颜色组合成橙色。你们两个都使用了共享颜色,现在你们需要彼此共享组合的颜色:

image-21

你现在有了你的私钥和秘密松鼠的颜色组合。同样地,秘密松鼠有他们的私钥和你的组合颜色。你和秘密松鼠很快就把你们的颜色组合起来了。

然而,间谍只有这两种颜色。要想弄清楚你的原色是非常困难的,即使给定了最初的共享颜色。间谍得去商店买很多不同的蓝颜料来试试。即使这样,也很难知道他们在组合后是否看到了具有正确深浅度的绿色!简而言之,你的私钥仍然是私钥。

但是你和那个“秘密松鼠”成员呢?你们仍然没有一个共同的秘密!这是你的私钥重新派上用场的地方。如果你把你的私钥和你从秘密松鼠那里得到的颜色组合在一起,那么你俩最终会得到相同的颜色:

image-22

现在,你和这个“秘密松鼠”成员有着相同的秘密颜色。你现在已经成功地和一个完全陌生的人分享了一个安全的秘密。这对于公钥密码的工作方式来说是惊人的精确。这个事件序列还有另一个通用名称:Diffie-Hellman密钥交换。密钥交换由以下部分组成:

  • 私钥是示例中的私用颜色。
  • 公钥是你共享的组合颜色。

私钥是你始终保持私有的东西,而公钥可以与任何人共享。这些概念直接映射到Python HTTPS应用程序的现实世界。既然服务器和客户端有了一个共享的秘密,你可以使用你的“老伙计”对称加密来对所有信息进行加密!

注意:公钥密码术也依赖于一些数学知识来进行颜色混合。Diffie-Hellman密钥交换的维基百科词条有很好的解释,但是深入的解释不在本文的范围之内。

当你通过安全网站(如本网站)进行通信时,你的浏览器和服务器使用这些相同的原则设置安全通信:

  • 浏览器从服务器请求信息。
  • 浏览器和服务器交换公钥。
  • 浏览器和服务器生成共享私钥。
  • 浏览器和服务器使用此共享密钥通过对称加密对消息进行加密和解密。

幸运的是,你不需要实现这些细节。有许多内置库和第三方库可以帮助你保持客户端和服务器通信的安全。