Skip to content

Latest commit

 

History

History
535 lines (378 loc) · 28 KB

File metadata and controls

535 lines (378 loc) · 28 KB

九、密码学和令牌

"Three may keep a Secret, if two of them are dead." – Benjamin Franklin, Poor Richard's Almanack

在这短短的一章中,我将简要概述 Python 标准库提供的加密服务。我还将谈到 JSON Web 令牌,这是一个非常有趣的标准,用于安全地表示双方之间的声明。

我们将特别探讨以下方面:

  • 哈希库
  • 秘密
  • HMAC
  • JSON Web 标记和 PyJWT,PyJWT 似乎是处理 JWT 最流行的 Python 库

让我们先花点时间讨论一下密码学以及它为什么如此重要。

密码学的必要性

根据网络上的统计数据,2019 年智能手机用户预计将达到 25 亿左右。这些人中的每一个人都知道解锁手机的 PIN 码,以及登录我们都用来做的应用程序的凭据,好吧,基本上所有的事情,从购买食物到寻找街道,从向朋友发送消息,到查看我们的比特币钱包自我们上次 10 秒前检查以来是否增值。

如果您是一名应用程序开发人员,您必须非常非常认真地对待安全问题。无论您的应用程序有多小或看起来有多微不足道,安全性始终是您关心的问题。

信息技术的安全性是通过使用几种不同的方法来实现的,但到目前为止,最重要的是密码学。你用电脑或手机做的每件事都应该包括一个加密层(如果没有,那就太糟糕了)。它被用来用信用卡在线支付,通过网络传输信息,即使有人截取信息,他们也无法读取,当你在云中备份文件时,它被用来加密你的文件(因为你这样做了,对吧?)。例子不胜枚举。

现在,这一章的目的不是教你散列和加密之间的区别,因为我可以写一整本关于这个主题的书。相反,它是向您展示如何使用 Python 提供的工具来创建摘要、标记,以及在需要实现与密码学相关的东西时,为了安全起见(r)。

有用的指导方针

始终记住以下规则:

  • 第一条规则:不要试图创建自己的哈希或加密函数。不要这样做。使用已有的工具和功能。要想出一个好的、可靠的、健壮的算法来进行散列或加密是非常困难的,所以最好将它留给专业的密码学家。
  • 第二条规则:遵循第一条规则。

这是你唯一需要的两条规则。除此之外,理解密码学是非常有用的,所以你需要尽可能多地学习这门学科。网上有很多信息,但为了方便起见,我会在本章末尾放一些有用的参考资料。

现在,让我们深入了解我想向您展示的第一个标准库模块:hashlib

哈希库

该模块向许多不同的安全哈希和消息摘要算法公开了一个公共接口。这两个术语的区别只是历史的:较旧的算法被称为摘要,而现代算法被称为哈希

通常,哈希函数是可以用于将任意大小的数据映射到固定大小的数据的任何函数。它是一种单向加密类型,因为在给定消息散列的情况下,它不能恢复消息。

有几种算法可用于计算散列,因此让我们看看如何找出您的系统支持哪些算法(注意,您的结果可能与我的不同):

>>> import hashlib
>>> hashlib.algorithms_available
{'SHA512', 'SHA256', 'shake_256', 'sha3_256', 'ecdsa-with-SHA1',
 'DSA-SHA', 'sha1', 'sha384', 'sha3_224', 'whirlpool', 'mdc2',
 'RIPEMD160', 'shake_128', 'MD4', 'dsaEncryption', 'dsaWithSHA',
 'SHA1', 'blake2s', 'md5', 'sha', 'sha224', 'SHA', 'MD5',
 'sha256', 'SHA384', 'sha3_384', 'md4', 'SHA224', 'MDC2',
 'sha3_512', 'sha512', 'blake2b', 'DSA', 'ripemd160'}
>>> hashlib.algorithms_guaranteed
{'blake2s', 'md5', 'sha224', 'sha3_512', 'shake_256', 'sha3_256',
 'shake_128', 'sha256', 'sha1', 'sha512', 'blake2b', 'sha3_384',
 'sha384', 'sha3_224'}

通过打开 Python shell,我们可以获得系统可用算法的列表。如果我们的应用程序必须与第三方应用程序对话,那么最好从保证的应用程序中选择一个算法,因为这意味着每个平台实际上都支持它们。请注意,其中很多都以sha开头,这意味着安全哈希算法。让我们继续在同一个 shell 中进行:我们将为二进制字符串b'Hash me now!'创建一个哈希,我们将通过两种方式进行:

>>> h = hashlib.blake2b()
>>> h.update(b'Hash me')
>>> h.update(b' now!')
>>> h.hexdigest()
'56441b566db9aafcf8cdad3a4729fa4b2bfaab0ada36155ece29f52ff70e1e9d'
'7f54cacfe44bc97c7e904cf79944357d023877929430bc58eb2dae168e73cedf'
>>> h.digest()
b'VD\x1bVm\xb9\xaa\xfc\xf8\xcd\xad:G)\xfaK+\xfa\xab\n\xda6\x15^'
b'\xce)\xf5/\xf7\x0e\x1e\x9d\x7fT\xca\xcf\xe4K\xc9|~\x90L\xf7'
b'\x99D5}\x028w\x92\x940\xbcX\xeb-\xae\x16\x8es\xce\xdf'
>>> h.block_size
128
>>> h.digest_size
64
>>> h.name
'blake2b'

我们使用了blake2b加密函数,它非常复杂,是在 Python 3.6 中添加的。创建哈希对象h后,我们分两步更新其消息。我们不需要这样做,但有时我们需要一次性散列不可用的数据,所以知道我们可以分步进行很好。

当消息是我们想要的时,我们得到摘要的十六进制表示。这将使用每个字节两个字符(因为每个字符代表 4 位,即半个字节)。我们还得到了摘要的字节表示,然后检查了它的细节:它的块大小(哈希算法的内部块大小,以字节为单位)为 128 字节,摘要大小(结果哈希的大小,以字节为单位)为 64 字节,还有一个名称。这一切能用一条简单的线来完成吗?当然可以:

>>> hashlib.blake2b(b'Hash me now!').hexdigest()
'56441b566db9aafcf8cdad3a4729fa4b2bfaab0ada36155ece29f52ff70e1e9d'
'7f54cacfe44bc97c7e904cf79944357d023877929430bc58eb2dae168e73cedf'

请注意相同的消息如何产生相同的哈希,这当然是意料之中的。

让我们看看如果我们使用sha256而不是blake2b函数,我们会得到什么:

>>> hashlib.sha256(b'Hash me now!').hexdigest()
'10d561fa94a89a25ea0c7aa47708bdb353bbb062a17820292cd905a3a60d6783'

产生的散列更短(因此更不安全)。

散列是一个非常有趣的话题,当然到目前为止我们看到的简单示例只是一个开始。blake2b功能允许我们在定制方面有很大的灵活性。这对于防止某些类型的攻击非常有用(有关这些威胁的完整解释,请参阅以下标准文档:https://docs.python.org/3.7/library/hashlib.html 用于hashlib模块)。让我们看看另一个例子,我们通过添加keysaltperson。所有这些额外信息都会导致哈希值与我们未提供时得到的哈希值不同,这对于为系统中处理的数据增加额外安全性至关重要:

>>> h = hashlib.blake2b(
...   b'Important payload', digest_size=16, key=b'secret-key',
...   salt=b'random-salt', person=b'fabrizio'
... )
>>> h.hexdigest()
'c2d63ead796d0d6d734a5c3c578b6e41'

产生的散列只有 16 字节长。在定制参数中,salt可能是最著名的一个。它是随机数据,用作对数据进行散列的单向函数的附加输入。它通常与结果散列一起存储,以便提供在给定相同消息的情况下恢复相同散列的方法。

如果您想确保正确散列密码,可以使用pbkdf2_hmac,这是一种密钥派生算法,允许您指定salt以及算法本身使用的迭代次数。随着计算机的功能越来越强大,随着时间的推移,增加迭代次数是非常重要的,否则,随着时间的推移,对数据进行暴力攻击的可能性会增加。下面是如何使用这种算法:

>>> import os
>>> dk = hashlib.pbkdf2_hmac(
...   'sha256', b'Password123', os.urandom(16), 100000
... )
>>> dk.hex()
'f8715c37906df067466ce84973e6e52a955be025a59c9100d9183c4cbec27a9e'

请注意,我使用了os.urandom来提供一个 16 字节的随机 salt,这是文档中推荐的。

我鼓励你探索和实验这个模块,因为迟早你会使用它。现在,让我们进入secrets一节。

秘密

这个漂亮的小模块用于生成加密性强的随机数,适用于管理密码、帐户身份验证、安全令牌和相关机密等数据。它是在 Python3.6 中添加的,主要处理三件事:随机数、标记和摘要比较。让我们快速探索它们。

随机数

我们可以使用三个函数来处理随机数:

# secrs/secr_rand.py
import secrets
print(secrets.choice('Choose one of these words'.split()))
print(secrets.randbelow(10 ** 6))
print(secrets.randbits(32))

第一个choice从非空序列中随机选取一个元素。第二个,randbelow生成一个介于0和您调用它的参数之间的随机整数,第三个,randbits生成一个包含n个随机位的整数。运行该代码会产生以下输出(总是不同的):

$ python secr_rand.py
one
504156
3172492450

当您在密码学环境中需要随机性时,您应该使用这些函数,而不是来自random模块的函数,因为这些函数是专门为此任务设计的。让我们看看模块为令牌提供了什么。

令牌生成

同样,我们有三个函数,它们都生成令牌,尽管格式不同。让我们来看一个例子:

# secrs/secr_rand.py
print(secrets.token_bytes(16))
print(secrets.token_hex(32))
print(secrets.token_urlsafe(32))

第一个,token_bytes简单地返回一个包含n字节的随机字节字符串(本例中为16)。其他两个也一样,但是token_hex返回十六进制格式的标记,token_urlsafe返回的标记只包含适合包含在 URL 中的字符。让我们看看输出(它是上一次运行的延续):

b'\xda\x863\xeb\xbb|\x8fk\x9b\xbd\x14Q\xd4\x8d\x15}'
9f90fd042229570bf633e91e92505523811b45e1c3a72074e19bbeb2e5111bf7
bl4qz_Av7QNvPEqZtKsLuTOUsNLFmXW3O03pn50leiY 

这一切都很好,所以我们为什么不找点乐子,用这些工具编写一个随机密码生成器呢?

# secrs/secr_gen.py
import secrets
from string import digits, ascii_letters

def generate_pwd(length=8):
    chars = digits + ascii_letters
    return ''.join(secrets.choice(chars) for c in range(length))

def generate_secure_pwd(length=16, upper=3, digits=3):
    if length < upper + digits + 1:
        raise ValueError('Nice try!')
    while True:
        pwd = generate_pwd(length)
        if (any(c.islower() for c in pwd)
            and sum(c.isupper() for c in pwd) >= upper
            and sum(c.isdigit() for c in pwd) >= digits):
            return pwd

print(generate_secure_pwd())
print(generate_secure_pwd(length=3, upper=1, digits=1))

在前面的代码中,我们定义了两个函数。generate_pwd通过将从包含字母表所有字母(小写和大写)和 10 位十进制数字的字符串中随机选取的length字符连接在一起,简单地生成给定长度的随机字符串。

然后,我们定义了另一个函数generate_secure_pwd,它只需继续调用generate_pwd,直到我们得到的随机字符串与需求匹配,这非常简单。密码必须至少有一个小写字符、upper大写字符、digits数字和length长度。

在我们进入while循环之前,值得注意的是,如果我们将要求(大写、小写和数字)相加,并且总和大于密码的总长度,那么我们就无法满足循环中的条件。所以,为了避免陷入无限循环,我在正文的第一行放了一个 check 子句,如果需要,我会抛出一个ValueError。你能想到如何为这个边缘案例编写一个测试吗?

*while循环的主体很简单:首先生成随机密码,然后使用anysum验证条件。any返回True,如果 iterable 中的任何项被调用,则使用True进行评估。这里使用 sum 实际上要稍微复杂一些,因为它利用了多态性。在你继续读之前,你能明白我在说什么吗?

很简单:TrueFalse在 Python 中是整数的子类,因此当对True/False值的可数求和时,sum函数会自动将它们解释为整数。这就是所谓的多态性,我们在第 6 章OOP、装饰器和迭代器中简要介绍了它。

运行该示例将产生以下结果:

$ python secr_gen.py
nsL5voJnCi7Ote3F
J5e

第二个密码可能不太安全。。。

最后一个例子,在我们进入下一个模块之前。让我们生成一个重置密码 URL:

# secrs/secr_reset.py
import secrets

def get_reset_pwd_url(token_length=16):
    token = secrets.token_urlsafe(token_length)
    return f'https://fabdomain.com/reset-pwd/{token}'

print(get_reset_pwd_url())

此函数非常简单,我只向您显示输出:

$ python secr_reset.py
https://fabdomain.com/reset-pwd/m4jb7aKgzTGuyjs9lTIspw

摘要比较

这可能非常令人惊讶,但在secrets中,您可以找到compare_digest(a, b)函数,这相当于通过简单地执行a == b来比较两个摘要。那么,为什么我们需要这个函数呢?这是因为它被设计用来防止定时攻击。这类攻击可以根据比较失败所需的时间推断出两个摘要在哪里开始不同的信息。因此,compare_digest通过消除时间和故障之间的相关性来防止这种攻击。我认为这是一个极好的例子,说明了攻击方法的复杂性。如果你惊讶地扬起眉毛,也许现在我说永远不要自己实现加密功能的原因就更清楚了。

就这样!现在我们来看看hmac

HMAC

此模块实现 HMAC 算法,如 RFC 2104(所述 https://tools.ietf.org/html/rfc2104.html )。由于它非常小,但仍然很重要,因此我将为您提供一个简单的示例:

# hmc.py
import hmac
import hashlib

def calc_digest(key, message):
    key = bytes(key, 'utf-8')
    message = bytes(message, 'utf-8')
    dig = hmac.new(key, message, hashlib.sha256)
    return dig.hexdigest()

digest = calc_digest('secret-key', 'Important Message')

正如您所看到的,接口总是相同或相似的。我们首先将密钥和消息转换为字节,然后创建一个digest实例,我们将使用该实例获取哈希的十六进制表示形式。没什么要说的,但为了完整起见,我还是想添加这个模块。

现在,让我们转到另一种类型的令牌:JWTs。

JSON Web 令牌

JSON Web 令牌JWT是一种基于 JSON 的开放标准,用于创建断言一定数量声明的令牌。您可以在网站(上了解这项技术的所有内容 https://jwt.io/ )。简言之,这种类型的令牌由三部分组成,由一个点分隔,格式为a.B.CB是有效载荷,我们在其中放置数据和声明。C是签名,用于验证令牌的有效性,a是用于计算签名的算法。ABC都使用 URL 安全的 Base64 编码(我称之为 Base64URL)进行编码。

Base64 是一种非常流行的二进制到文本编码方案,它通过将二进制数据转换为基数 64 表示,以 ASCII 字符串格式表示二进制数据。基数-64 表示法使用字母A-ZA-Z和数字0-9,加上两个符号*+/*,总共 64 个符号。因此,毫不奇怪,Base64 字母表是由这 64 个符号组成的。例如,Base64 用于对电子邮件中附加的图像进行编码。这是天衣无缝的,所以绝大多数人完全忘记了这一事实。

The reason why a JWT is encoded using Base64URL is because of the characters + and /, which in a URL context mean space, and path separator, respectively. Therefore in the URL safe version, they are replaced with - and _. Moreover, any padding character (=), which is normally used in Base64, is stripped out, as this too has a specific meaning within a URL. 

因此,这种类型的令牌的工作方式与我们使用哈希时的工作方式略有不同。事实上,令牌携带的信息总是可见的。您只需对AB进行解码即可得到算法和有效载荷。然而,安全性存在于部分C,这是令牌的 HMAC 哈希。如果您试图通过编辑有效负载、将其编码回 Base64 并在令牌中替换来修改B部分,则签名将不再匹配,因此令牌将无效。

这意味着我们可以构建一个负载,声明诸如以管理员的身份登录,或者类似的内容,并且只要令牌有效,我们知道我们可以相信该用户实际上是以管理员的身份登录的。

When dealing with JWTs, you want to make sure you have researched how to handle them safely. Things like not accepting unsigned tokens, or restricting the list of algorithms you use to encode and decode, as well as other security measures, are very important and you should take the time to investigate and learn them.

For this part of the code, you will have to have the PyJWT and cryptography Python packages installed. As always, you will find them in the requirements of the source code of this book.

让我们从一个简单的例子开始:

# tok.py
import jwt

data = {'payload': 'data', 'id': 123456789}

token = jwt.encode(data, 'secret-key')
data_out = jwt.decode(token, 'secret-key')
print(token)
print(data_out)

我们定义了data有效负载,它包含一个 ID 和一些有效负载数据。然后,我们使用jwt.encode函数创建一个令牌,它至少接受有效负载和一个用于计算签名的密钥。用于计算令牌的默认算法为HS256。让我们看看输出:

$ python tok.py
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoiZGF0YSIsImlkIjoxMjM0NTY3ODl9.WFRY-uoACMoNYX97PXXjEfXFQO1rCyFCyiwxzOVMn40'
{'payload': 'data', 'id': 123456789}

如您所见,令牌是一个二进制字符串,由 Base64URL 编码的数据段组成。我们已呼叫jwt.decode,提供正确的密钥。如果我们不这样做,解码就会中断。

有时,您可能希望能够在不验证令牌的情况下检查令牌的内容。您只需通过以下方式调用decode即可:

# tok.py
jwt.decode(token, verify=False)

例如,当需要令牌负载中的值来恢复密钥时,这是很有用的,但该技术非常先进,因此在本文中我不会花时间在这方面。相反,让我们看看如何为计算签名指定不同的算法:

# tok.py
token512 = jwt.encode(data, 'secret-key', algorithm='HS512')
data_out = jwt.decode(token512, 'secret-key', algorithm='HS512')
print(data_out)

输出是我们的原始有效负载字典。如果您希望在解码阶段允许多个算法,您甚至可以指定它们的列表,而不是仅指定一个。

现在,虽然您可以在令牌有效负载中自由放置任何您想要的内容,但是有一些声明已经标准化,它们使您能够对令牌进行大量控制。

登记债权

在撰写本书时,以下是已登记的权利要求:

  • iss:代币的发行人
  • sub主体关于该令牌携带信息的一方的信息
  • aud:代币的观众
  • exp到期时间,超过该时间后该令牌被认为无效
  • nbf:该*不在(时间)*之前,或者该令牌在该时间之前被认为是无效的
  • iat:代币发出的时间
  • jti:令牌ID

索赔也可分为公共索赔和私人索赔:

  • 私有:由 JWTs 的用户(消费者和生产者)定义的。换句话说,这些是用于特定案件的特别索赔。因此,必须注意防止碰撞。
  • Public:是指在 IANA JSON Web 令牌声明注册中心(用户可以在该注册中心注册其声明,从而防止冲突)注册的声明,或使用防冲突名称命名的声明(例如,在其名称前加上名称空间)。

有关索赔的所有信息,请访问官方网站。现在,让我们看两个涉及这些声明子集的代码示例。

与时间有关的索赔

让我们看看如何使用与时间相关的声明:

# claims_time.py
from datetime import datetime, timedelta
from time import sleep
import jwt

iat = datetime.utcnow()
nfb = iat + timedelta(seconds=1)
exp = iat + timedelta(seconds=3)
data = {'payload': 'data', 'nbf': nfb, 'exp': exp, 'iat': iat}

def decode(token, secret):
    print(datetime.utcnow().time().isoformat())
    try:
        print(jwt.decode(token, secret))
    except (
        jwt.ImmatureSignatureError, jwt.ExpiredSignatureError
    ) as err:
        print(err)
        print(type(err))

secret = 'secret-key'
token = jwt.encode(data, secret)

decode(token, secret)
sleep(2)
decode(token, secret)
sleep(2)
decode(token, secret)

在本例中,我们将发出的 at(iat声明设置为当前 UTC 时间(UTC代表世界时协调)。然后我们将不在前面(nbf和过期时间(exp分别设置为从现在起的13秒。然后,我们定义了一个 decode helper 函数,它通过捕获适当的异常来对令牌无效或过期做出反应,然后我们调用它三次,中间有两次对 sleep 的调用。这样,我们将尝试在令牌无效时解码令牌,然后在令牌有效时解码令牌,最后在令牌已过期时解码令牌。此函数还可以在尝试解密之前打印一个有用的时间戳。让我们看看它是如何运行的(为了可读性,添加了空行):

$ python claims_time.py
14:04:13.469778
The token is not yet valid (nbf)
<class 'jwt.exceptions.ImmatureSignatureError'>

14:04:15.475362
{'payload': 'data', 'nbf': 1522591454, 'exp': 1522591456, 'iat': 1522591453}

14:04:17.476948
Signature has expired
<class 'jwt.exceptions.ExpiredSignatureError'>

正如您所看到的,所有这些都按预期执行。我们从异常中获得漂亮的描述性消息,并在令牌实际有效时返回原始有效负载。

与授权相关的索赔

让我们来看另一个涉及发行人(iss和受众(aud索赔)的快速示例。该代码在概念上与前一个示例非常相似,我们将以相同的方式进行练习:

# claims_auth.py
import jwt

data = {'payload': 'data', 'iss': 'fab', 'aud': 'learn-python'}
secret = 'secret-key'
token = jwt.encode(data, secret)

def decode(token, secret, issuer=None, audience=None):
    try:
        print(jwt.decode(
            token, secret, issuer=issuer, audience=audience))
    except (
        jwt.InvalidIssuerError, jwt.InvalidAudienceError
    ) as err:
        print(err)
        print(type(err))

decode(token, secret)
# not providing the issuer won't break
decode(token, secret, audience='learn-python')
# not providing the audience will break
decode(token, secret, issuer='fab')
# both will break
decode(token, secret, issuer='wrong', audience='learn-python')
decode(token, secret, issuer='fab', audience='wrong')

decode(token, secret, issuer='fab', audience='learn-python')

如您所见,这次我们指定了issueraudience。事实证明,如果我们在解码令牌时不提供颁发者,则不会导致解码中断。然而,提供错误的发行者实际上会破坏解码。另一方面,无论是未能提供受众,还是提供了错误的受众,都会破坏解码。

与前面的示例一样,我编写了一个自定义解码函数,该函数对适当的异常作出反应。看看您是否可以跟随这些调用以及下面的相关输出(我将用一些空行来帮助您):

$ python claims_auth.py
Invalid audience
<class 'jwt.exceptions.InvalidAudienceError'>

{'payload': 'data', 'iss': 'fab', 'aud': 'learn-python'}

Invalid audience
<class 'jwt.exceptions.InvalidAudienceError'>

Invalid issuer
<class 'jwt.exceptions.InvalidIssuerError'>

Invalid audience
<class 'jwt.exceptions.InvalidAudienceError'>

{'payload': 'data', 'iss': 'fab', 'aud': 'learn-python'}

现在,让我们看一个更复杂用例的最后一个示例。

使用非对称(公钥)算法

有时,使用共享秘密不是最佳选择。在这些情况下,采用不同的技术可能是有用的。在本例中,我们将使用一对 RSA 密钥创建一个令牌(并对其进行解码)。

公钥加密或非对称加密是任何使用密钥对的加密系统:可以广泛传播的公钥和只有所有者知道的私钥。如果您有兴趣了解有关此主题的更多信息,请参阅本章末尾的建议。

现在,让我们创建两对键。一对将没有密码,另一对将没有密码。为了创建它们,我将使用 OpenSSH(中的ssh-keygenUTILhttps://www.ssh.com/ssh/keygen/ )。在本章脚本所在的文件夹中,我创建了一个子文件夹rsa。在其中,运行以下命令:

$ ssh-keygen -t rsa

为路径指定名称key(它将保存在当前文件夹中),当要求输入密码时,只需按输入键即可。完成后,再次执行相同操作,但这次使用名称keypwd作为密钥,并给它一个密码。我选择的是经典的Password123。完成后,切换回ch9文件夹,并运行以下代码:

# token_rsa.py
import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

data = {'payload': 'data'}

def encode(data, priv_filename, priv_pwd=None, algorithm='RS256'):
    with open(priv_filename, 'rb') as key:
        private_key = serialization.load_pem_private_key(
            key.read(),
            password=priv_pwd,
            backend=default_backend()
        )
    return jwt.encode(data, private_key, algorithm=algorithm)

def decode(data, pub_filename, algorithm='RS256'):
    with open(pub_filename, 'rb') as key:
        public_key = key.read()
    return jwt.decode(data, public_key, algorithm=algorithm)

# no pwd
token = encode(data, 'rsa/key')
data_out = decode(token, 'rsa/key.pub')
print(data_out)

# with pwd
token = encode(data, 'rsa/keypwd', priv_pwd=b'Password123')
data_out = decode(token, 'rsa/keypwd.pub')
print(data_out)

在前面的示例中,我们定义了两个自定义函数来使用私钥/公钥对令牌进行编码和解码。正如您在encode函数的签名中所看到的,我们这次使用的是RS256算法。我们需要使用特殊的load_pem_private_key功能打开私钥文件,它允许我们指定内容、密码和后端。.pem是创建密钥的格式名称。如果你看一下这些文件,你可能会认出它们,因为它们非常流行。

逻辑非常简单,我鼓励您至少考虑一个用例,在这个用例中,这种技术可能比使用共享密钥更合适。

有用的参考资料

如果您想更深入地了解迷人的密码学世界,您可以在这里找到一系列有用的参考资料:

网络上还有很多方法,你也可以学习很多书,但我建议你从主要概念开始,然后逐步深入到你想更透彻理解的细节。

总结

在这短短的一章中,我们探讨了 Python 标准库中的密码学世界。我们学习了如何使用不同的加密函数为消息创建哈希(或摘要)。我们还学习了如何创建令牌,以及在涉及加密上下文时如何处理随机数据。

然后,我们在标准库之外进行了一次小型参观,以了解 JSON Web 令牌,现代系统和应用程序在身份验证和声明相关功能中大量使用 JSON Web 令牌。

最重要的是要明白,当涉及到加密技术时,手动操作可能非常危险,因此最好将其留给专业人员,只需使用我们现有的工具即可。

下一章将全部介绍从软件执行的一行转移到另一行。我们将学习软件在现实世界中的工作方式,探索并发执行,并了解线程、进程和 Python 提供给我们的工具,可以说,Python 一次可以做多件事。*