目录
摘要
你是否曾在处理加密任务时被各种奇怪的 Python 加密库搞得头大?比如 API 风格各异、文档零碎、示例难懂,甚至还有一些库看上去很“高级”,实则早已停止维护?如果你也在项目中被这些乱七八糟的加密工具困扰,那么你并不孤单。
在 Python 的生态中,加密相关的库五花八门,从标准库 hashlib 到曾经风靡一时的 PyCrypto,再到现代主力选手 cryptography 和 PyCryptodome。选择哪个库往往不仅决定了你的开发效率,还直接影响系统的安全性与可维护性。错误使用或选型不当,轻则踩坑调试数小时,重则引发安全漏洞,影响上线稳定性。
本篇博客将帮助你梳理这些加密库的来龙去脉,比较它们在功能、易用性、稳定性、安全性等方面的优缺点,并结合真实项目经验分析各种“坑点”。最后,我们将重点介绍 cryptography 库,它以安全、高效、现代化的 API 成为当前最值得信赖的加密工具之一。
🔐 Python 加密库对比分析
| 库名 | 优点 | 缺点 | 使用场景 | 踩坑点 ⚠️ |
|---|---|---|---|---|
| cryptography | - 高层 API 安全易用 - 封装 OpenSSL,性能强大 - 活跃社区,官方推荐 - 支持对称/非对称/哈希/签名等 | - 安装需编译依赖(Windows/macOS 可能略麻烦) | 通用加密需求(推荐默认选项) | 一些低级 API(如 ASN.1 编解码)略微底层,不适合新手 |
| PyCryptodome | - PyCrypto 安全替代 - 接口设计熟悉 - 功能全面、支持 AES/GCM 等现代算法 | - 文档不够友好 - 高阶封装较少,偏“低层工具箱” | 替代 PyCrypto、需要定制的加密逻辑 | 默认 padding 设置不同,可能造成与其他语言加密结果不一致 |
| PyCrypto | - 接口简单,适合教学入门 - 多算法支持 | - 已停止维护多年 - 安全性堪忧(已知漏洞) | 仅用于学习或分析旧代码 | 有 CVE 漏洞,不应再用于生产环境 ⚠️ |
| hashlib | - 标准库,无需安装 - 稳定可靠 - 简单易用 | - 仅支持哈希算法(SHA1、SHA256 等) - 不支持加密/解密 | 哈希摘要、签名前处理、完整性校验 | update() 不支持多种编码输入,需手动转码 |
| ssl / OpenSSL | - 底层功能强大 - 可定制 TLS 连接、证书校验等 | - 使用复杂,API 繁琐难记 - 出错时不易排查 | 自定义 TLS 客户端/服务端,HTTPS 通信 | Python ssl 对 OpenSSL 的版本依赖强,升级易踩坑 |
| M2Crypto | - OpenSSL 封装完整,功能丰富 | - 安装过程繁琐 - Python3 兼容性差 - 开发缓慢 | 压根就不建议新项目使用 | 安装失败率高,跨平台构建特别痛苦(尤其 Windows) |
你是否受够了类似这样导入from Crypto.Cipher import AES加密算法报的no module named xxx?
这个语法属于:
🔹 pycryptodome 的 API。
pycryptodome是对老旧库pycrypto的替代,API 几乎一致。pycrypto已废弃多年,不再维护,安全风险高。- 所以你应该始终使用
pycryptodome,而不是pycrypto。
📦 安装方式
pip install pycryptodome
安装后你就可以这样使用:
from Crypto.Cipher import AES
注意不是 from Cryptodome,而是 from Crypto,虽然包名是 pycryptodome,但它为了兼容 pycrypto 的老代码,使用的是相同的 import 路径。
如果你同时安装了 pycrypto 和 pycryptodome,会出现莫名其妙的冲突(比如无法找到 Crypto.Cipher 或加密失败等)。
建议:
pip uninstall pycrypto
pip install --upgrade pycryptodome
上文顺手提了一下Cryptodome的坑,而本博客主要介绍更加现代化的cryptography!!
“如果你想要一个安全、稳定、现代的加密库,别犹豫,直接用
cryptography;其余的,不是过时,就是偏门,要么慎用,要么绕行。”
cryptography
cryptography 是一个为 Python 开发者提供加密方案和加密原语的库。我们的目标是让它成为你的“加密标准库”。该库支持 Python 3.8 及以上版本,以及 PyPy3 7.3.11 及以上版本。
cryptography 同时提供了高级封装方案(recipes)和底层接口,用于实现常见的加密算法,如:对称加密、消息摘要(哈希)、密钥派生函数等。
例如,使用 cryptography 的高级接口对数据进行对称加密:
>>> from cryptography.fernet import Fernet
>>> # 把这个密钥保存在安全的地方!
>>> key = Fernet.generate_key()
>>> f = Fernet(key)
>>> token = f.encrypt(b"A really secret message. Not for prying eyes.")
>>> token
b'...'
>>> f.decrypt(token)
b'A really secret message. Not for prying eyes.'
官方github地址:https://github.com/pyca/cryptography,可以看到已经7.2k star了!

官方文档地址:https://cryptography.io/en/latest/

快速开始
在开始之前,确保你的Python环境已经安装好。cryptography库可以通过pip进行安装,这是Python的包管理工具。打开你的命令行界面,输入以下命令:
pip install cryptography
等待安装完成,你就可以开始使用cryptography库了。
cryptography 大致分为两个层级:
一是安全的(recipes layer)层,几乎不需要配置选项。这一层接口安全、易用,开发者几乎无需做出决策。
另一层是底层加密原语(cryptographic primitives),这类接口通常比较危险,容易被错误使用。使用它们需要开发者具备较强的密码学知识,并且要做出许多技术决策。由于其潜在的危险性,这一层也被称为“危险材料层”(hazardous materials,简称 hazmat)。它们位于 cryptography.hazmat 包中,相关文档顶部通常会带有警告说明。
我们建议尽可能使用recipes layer层,仅在确有必要时再使用 hazmat 层。
recipes layer
Fernet加密协议
Fernet 是 Python 中 cryptography 库提供的一种对称加密技术,专为加密和解密数据设计。它基于 AES(高级加密标准) 算法,结合 CBC(密码块链接)模式 和 PKCS7 填充,并通过 SHA256 的 HMAC 实现数据完整性验证。Fernet 提供了简单易用的接口,非常适合密码学初学者。
核心特点
-
对称加密:加密和解密使用相同的密钥,确保数据安全。
-
AES 加密:采用 128 位 AES 加密算法,结合随机生成的初始化向量(IV),增强安全性。
-
HMAC 验证:通过 SHA256 的 HMAC 验证数据完整性,防止篡改。
-
时间戳支持:可以附加时间戳,确保密钥的有效性。
-
URL 安全:生成的密文是 URL 安全的,便于通过网络传输。
Fernet 能确保使用它加密的信息,在没有密钥的情况下无法被篡改或读取。它是对称加密(也称为“共享密钥”加密)的一种经过认证的加密实现。Fernet 还支持通过 MultiFernet 实现密钥轮换(key rotation)。
示例代码:
from cryptography.fernet import Fernet
key = Fernet.generate_key()
f = Fernet(key)
token = f.encrypt(b"my deep dark secret")
token
b'...'
f.decrypt(token)
b'my deep dark secret'
class cryptography.fernet.Fernet(key)
此类提供加密和解密功能,并且是线程安全的。
from cryptography.fernet import Fernet
key = Fernet.generate_key()
f = Fernet(key)
token = f.encrypt(b"my deep dark secret")
token
b'...'
f.decrypt(token)
b'my deep dark secret'
参数:
key(bytes或str)—— 以 URL 安全的 base64 编码形式表示的 32 字节密钥。必须严格保密。拥有此密钥的任何人都可以创建和解密信息。
@classmethod generate_key()
生成一个新的 Fernet 密钥。请妥善保存!
如果你丢失了这个密钥,将无法再解密任何信息;如果别人获取了它,他们不仅可以解密你的所有消息,还可以伪造任意伪装成你发送的消息(并且这些消息会通过身份验证并成功解密)。
encrypt(data)
对传入的 data 进行加密。加密结果被称为“Fernet token”,它在隐私和真实性方面提供了强有力的保障。
参数:
data(bytes)—— 要加密的消息。
返回值:
bytes:一个经过安全加密的消息,只有持有密钥的人才能读取或修改。该值是 URL 安全的 base64 编码,称为 “Fernet token”。
异常:
TypeError—— 当传入的数据不是字节类型时会抛出此异常。
注意:
加密后的消息中包含了生成时的当前时间戳(明文形式),这意味着攻击者可能看到消息的生成时间。
decrypt(token, ttl=None)
解密一个 Fernet token。如果解密成功,将返回原始的明文数据;如果失败,将抛出异常。在返回之前,Fernet 会验证数据是否被篡改,因此可以放心立即使用解密后的数据。
参数:
token(bytes或str):Fernet token,即encrypt()方法生成的加密结果。ttl(int,可选):token 有效期(秒)。如果消息距离生成时间超过指定秒数,将抛出异常。如果不提供(或为None),则不考虑 token 的生成时间。
返回值:
bytes:原始的明文数据。
异常:
cryptography.fernet.InvalidToken:当 token 无效时抛出。无效的原因可能包括:超过ttl、格式错误或签名验证失败。TypeError:当token不是bytes或str类型时抛出。
decrypt_at_time(token, ttl, current_time)
自版本 3.0 起新增。
解密一个 token,并使用明确传入的当前时间(current_time)进行校验。其他参数(token 和 ttl)的说明与 decrypt() 相同。
这个方法的主要用途是让客户端代码可以测试 token 的过期行为。
⚠️注意:使用此方法可能导致不安全行为,务必在测试之外使用真实的当前时间(如 int(time.time()))作为 current_time。
参数:
current_time(int):当前时间戳。
extract_timestamp(token)
自版本 2.3 起新增。
返回 token 中记录的时间戳(即生成时间)。调用者可以据此判断 token 是否即将过期,并据此,例如重新生成一个新 token。
参数:
token(bytes或str):由encrypt()生成的 Fernet token。
返回值:
int:token 的 Unix 时间戳。
异常:
cryptography.fernet.InvalidToken:当 token 签名无效时抛出。TypeError:当token不是bytes或str类型时抛出。
class cryptography.fernet.MultiFernet(fernets)
自版本 0.7 起新增
此类用于为 Fernet 实现密钥轮换(key rotation)。它接收一个 Fernet 实例列表,并提供与 Fernet 相同的 API,额外增加了一个方法:MultiFernet.rotate()。
示例代码:
from cryptography.fernet import Fernet, MultiFernet
key1 = Fernet(Fernet.generate_key())
key2 = Fernet(Fernet.generate_key())
f = MultiFernet([key1, key2])
token = f.encrypt(b"Secret message!")
token
b'...'
f.decrypt(token)
b'Secret message!'
- 加密时:
MultiFernet会使用列表中第一个密钥进行加密。 - 解密时:会按顺序尝试列表中每个密钥进行解密。如果没有任何密钥能解出消息,会抛出
cryptography.fernet.InvalidToken异常。
密钥轮换的好处是可以方便地替换旧密钥。你可以把新的密钥放在列表前端,开始加密新的消息;而旧密钥可以在不再需要时移除。
通过 MultiFernet.rotate() 提供的token 轮换机制是一种最佳实践,有助于在密钥泄露未被察觉的情况下限制潜在影响,并提升系统抵抗攻击的难度。
例如:如果曾有员工拥有公司的 Fernet 密钥并已离职,你应该生成新的 Fernet 密钥、重新加密当前的所有 token,然后废弃旧密钥。
rotate(msg)
自版本 2.2 起新增
通过使用 MultiFernet 实例的主密钥(即列表第一个密钥)对 token 进行重新加密,实现轮换操作。token 原有的时间戳会被保留。
如果轮换成功,会返回新的 token;否则抛出异常。
示例代码:
from cryptography.fernet import Fernet, MultiFernet
key1 = Fernet(Fernet.generate_key())
key2 = Fernet(Fernet.generate_key())
f = MultiFernet([key1, key2])
token = f.encrypt(b"Secret message!")
token
b'...'
f.decrypt(token)
b'Secret message!'
key3 = Fernet(Fernet.generate_key())
f2 = MultiFernet([key3, key1, key2])
rotated = f2.rotate(token)
f2.decrypt(rotated)
b'Secret message!'
参数:
msg(bytes或str):要重新加密的 token。
返回值:
bytes:加密后的 token(URL-safe base64 编码),无法被读取或篡改。
异常:
cryptography.fernet.InvalidToken:如果 token 无效,抛出此异常。TypeError:如果msg不是bytes或str类型,抛出此异常。
class cryptography.fernet.InvalidToken
这是在 Fernet.decrypt() 或 MultiFernet 操作中,token 无效时抛出的异常。详情见 Fernet.decrypt()。
使用密码(password)与 Fernet 结合
Fernet 可以配合密码使用。为此你需要使用密钥派生函数(KDF),例如 PBKDF2HMAC、Argon2id 或 Scrypt,将密码转换成加密密钥。
示例代码:
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
password = b"password"
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=1_200_000,
)
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
token = f.encrypt(b"Secret message!")
token
b'...'
f.decrypt(token)
b'Secret message!'
注意: 在这种方式中,你需要将
salt保存在一个可恢复的位置,以便日后可以再次从相同密码中派生出相同密钥。
建议的迭代次数应尽可能高,只要服务器性能允许。Django 截至 2025 年 1 月推荐值为 1,200,000 次。
实现细节(Implementation)
Fernet 构建于多个标准加密原语之上,具体包括:
-
AES-CBC 模式 + 128 位密钥(用于加密)
- 使用 PKCS7 填充
-
HMAC-SHA256(用于认证)
-
初始化向量(IV)通过
os.urandom()随机生成
限制(Limitations)
Fernet 适用于可以完整加载进内存的数据。
由于其设计不会暴露未经认证的数据,所以整个消息内容必须完整加载进内存。因此,目前 Fernet 不适用于加密超大文件。
X.509
X.509 是一种公共密钥基础设施(PKI)的 ITU-T 标准。X.509 第三个版本(X.509v3)定义在 RFC 5280 中(该标准取代了 RFC 2459 和 RFC 3280)。X.509 证书通常用于 TLS 等协议中。
创建证书签名请求(CSR)
X.509 证书用于认证客户端和服务器,最常见的应用场景是 Web 服务器使用 HTTPS。
当你想从一个证书颁发机构(CA)获取证书时,一般流程如下:
- 你生成一对私钥/公钥。
- 你创建一个证书请求(CSR),并用你的私钥对其签名(以证明你拥有该私钥)。
- 你将 CSR 提交给 CA(注意,不提交私钥)。
- CA 验证你是否拥有你请求证书的资源(例如域名)。
- CA 向你颁发一张证书,该证书由 CA 签名,包含你的公钥和你所认证的资源信息。
- 你配置服务器使用该证书及私钥来服务访问请求。
如果你希望从一个典型的商业 CA 获取证书,流程如下:
首先,你需要生成一个私钥。下面是生成 RSA 密钥的方法(RSA 是目前最常用的类型):
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
# 生成私钥
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# 将私钥写入磁盘(妥善保存)
with open("path/to/store/key.pem", "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"),
))
如果你已经有一个生成好的私钥,可以使用 load_pem_private_key() 来加载它。
接下来我们要生成一个证书签名请求(CSR)。一个典型的 CSR 包含以下内容:
- 公钥信息(包含整个请求体的签名)
- 申请者的信息
- 请求的证书适用的域名
示例如下:
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
# 生成 CSR
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
# 填写申请者的详细信息
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"),
])).add_extension(
x509.SubjectAlternativeName([
# 申请证书适用的多个域名
x509.DNSName("mysite.com"),
x509.DNSName("www.mysite.com"),
x509.DNSName("subdomain.mysite.com"),
]),
critical=False,
# 使用私钥签名该 CSR
).sign(key, hashes.SHA256())
# 将 CSR 写入磁盘
with open("path/to/csr.pem", "wb") as f:
f.write(csr.public_bytes(serialization.Encoding.PEM))
现在你可以将 csr.pem 文件交给证书颁发机构(CA),CA 验证通过后会颁发证书给你。
当你将 csr.pem(证书签名请求) 提交给 证书颁发机构(CA) 后,CA 会进行身份验证(比如验证你是否真的拥有请求中的域名)。验证通过后,CA 会返回给你一个正式的 X.509 数字证书,通常是一个以 .crt、.cer 或 .pem 结尾的文件,例如:
yourdomain.com.crt
这个证书文件中包含了:
- 你的 公钥
- 你的 身份信息(比如域名、组织、国家等)
- 证书的 有效期
- CA 的 签名
- 证书的 序列号 等元信息
它本质上就是一个被 CA 签过名的 X.509 证书,用来证明:
你拥有该公钥,且你确实是 “example.com” 的合法使用者。
你最终在服务器上部署的 HTTPS 证书配置通常包括两部分:
- 你的私钥(之前生成的
key.pem,自己保存,绝不能泄露) - CA 返回的证书文件(比如
yourdomain.com.crt)
可选:
- 中间证书链文件(chain.crt / fullchain.pem):有些 CA 会让你一起配置中间证书,以构建完整的信任链。
示例:nginx 的配置
ssl_certificate /etc/ssl/certs/yourdomain.com.crt;
ssl_certificate_key /etc/ssl/private/key.pem;
创建自签名证书(Creating a self-signed certificate)
虽然大多数情况下你会希望使用由他人(如证书颁发机构,CA)签名的证书来建立信任,但有时候你可能只需要一个自签名证书。
自签名证书不是由 CA 签发的,而是由证书中嵌入的公钥所对应的私钥自行签名的。
这意味着其他人不会信任这种证书,但它生成非常简单。
通常,自签名证书只用于本地测试等无需第三方信任的场景。
和生成 CSR 类似,第一步是创建一对新的私钥:
# 生成私钥
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# 将私钥写入磁盘保存
with open("path/to/store/key.pem", "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"),
))
然后生成自签名证书本体:
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
# 填写证书身份信息(自签名证书中,subject 和 issuer 是同一个)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"),
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.now(datetime.timezone.utc)
).not_valid_after(
# 证书有效期为 10 天
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10)
).add_extension(
x509.SubjectAlternativeName([x509.DNSName("localhost")]),
critical=False,
# 使用私钥签名证书
).sign(key, hashes.SHA256())
# 写入证书文件
with open("path/to/certificate.pem", "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
现在,你就拥有了一对用于本地测试的私钥和自签名证书。
✅ Nginx 配置证书时通常会看到:
ssl_certificate /path/to/cert.crt;
ssl_certificate_key /path/to/key.pem;
但这并不意味着文件扩展名必须是 .crt 和 .pem,而是它们内容格式必须分别是:
ssl_certificate:是 X.509 公钥证书(通常.crt或.pem)ssl_certificate_key:是 私钥(通常.key或.pem)
这是因为:
| 扩展名 | 本质内容 | 用途说明 |
|---|---|---|
.crt | 其实就是 PEM 编码的证书 | 通常包含 X.509 公钥证书 |
.pem | 可以是证书或私钥 | 是一种 Base64 + header 的包装格式 |
.key | 其实也是 PEM 编码的私钥 | 通常仅包含私钥 |
所以本质上:
.crt和.pem都可能是 证书.key和.pem都可能是 私钥
🔧 Nginx 的证书配置不看扩展名,只看文件内容格式
比如以下是完全合法的:
ssl_certificate /etc/ssl/my_cert.pem; # 其实是证书
ssl_certificate_key /etc/ssl/my_key.pem; # 其实是私钥
你也可以这样写:
ssl_certificate /etc/ssl/my_cert.crt; # .crt扩展名
ssl_certificate_key /etc/ssl/my_key.key; # .key扩展名
只要里面的内容格式正确,Nginx 不会在乎你是 .crt、.pem 还是 .key。
🔨 用 OpenSSL 命令行生成的:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.crt -days 365
会明确输出两个文件:
key.pem:私钥cert.crt:自签名证书
这只是人为地用了不同扩展名来标识文件内容的角色(便于人类理解),你完全可以用 .pem + .pem,Nginx 一样接受。
示例 Nginx 配置:
ssl_certificate /etc/nginx/ssl/certificate.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
或者你愿意统一扩展名,更清楚点也可以:
ssl_certificate /etc/nginx/ssl/my-cert.crt;
ssl_certificate_key /etc/nginx/ssl/my-key.key;
(文件内容不变,重命名即可)
创建 CA体系
在构建你自己的根证书体系(CA hierarchy)时,你需要先生成一个根 CA(Certificate Authority),然后使用它签发其他证书(通常是中间 CA 证书)。以下示例展示了如何生成一个根 CA,一个签发用的中间 CA,以及从中间 CA 签发一个最终用户证书(Leaf Certificate)。
由于 X.509 是一个复杂的规范,下面的示例在实际环境中可能需要调整(比如修改扩展项)以适应不同的系统需求。
⚠️ 注意:本示例未包含 CRL 分发点(CRL Distribution Point)或 OCSP AIA 扩展项,也未将密钥/证书保存到持久化存储中。
🔐 创建根证书(Root CA)
import datetime
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.x509.oid import NameOID
from cryptography import x509
# 生成私钥
root_key = ec.generate_private_key(ec.SECP256R1())
# 设置证书主题与颁发者(自签)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
x509.NameAttribute(NameOID.COMMON_NAME, "PyCA Docs Root CA"),
])
# 构建根证书
root_cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
root_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.now(datetime.timezone.utc)
).not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365*10)
).add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
).add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True,
crl_sign=True,
encipher_only=False,
decipher_only=False,
),
critical=True,
).add_extension(
x509.SubjectKeyIdentifier.from_public_key(root_key.public_key()),
critical=False,
).sign(root_key, hashes.SHA256())
🪪 创建中间证书(Intermediate CA)
# 生成中间 CA 私钥
int_key = ec.generate_private_key(ec.SECP256R1())
# 设置中间 CA 主题
subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
x509.NameAttribute(NameOID.COMMON_NAME, "PyCA Docs Intermediate CA"),
])
# 构建中间证书
int_cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
root_cert.subject
).public_key(
int_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.now(datetime.timezone.utc)
).not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365*3)
).add_extension(
x509.BasicConstraints(ca=True, path_length=0), # 不允许继续创建子 CA
critical=True,
).add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True,
crl_sign=True,
encipher_only=False,
decipher_only=False,
),
critical=True,
).add_extension(
x509.SubjectKeyIdentifier.from_public_key(int_key.public_key()),
critical=False,
).add_extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
root_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value
),
critical=False,
).sign(root_key, hashes.SHA256())
📄 签发最终用户证书(End-Entity Certificate)
ee_key = ec.generate_private_key(ec.SECP256R1())
subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
])
ee_cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
int_cert.subject
).public_key(
ee_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.now(datetime.timezone.utc)
).not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10)
).add_extension(
x509.SubjectAlternativeName([
x509.DNSName("cryptography.io"),
x509.DNSName("www.cryptography.io"),
]),
critical=False,
).add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
).add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=True,
data_encipherment=False,
key_agreement=False,
key_cert_sign=False,
crl_sign=True,
encipher_only=False,
decipher_only=False,
),
critical=True,
).add_extension(
x509.ExtendedKeyUsage([
x509.ExtendedKeyUsageOID.CLIENT_AUTH,
x509.ExtendedKeyUsageOID.SERVER_AUTH,
]),
critical=False,
).add_extension(
x509.SubjectKeyIdentifier.from_public_key(ee_key.public_key()),
critical=False,
).add_extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
int_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value
),
critical=False,
).sign(int_key, hashes.SHA256())
✅ 验证整个证书链
from cryptography.x509 import DNSName
from cryptography.x509.verification import PolicyBuilder, Store
store = Store([root_cert])
builder = PolicyBuilder().store(store)
verifier = builder.build_server_verifier(DNSName("cryptography.io"))
chain = verifier.verify(ee_cert, [int_cert])
len(chain) # 应该输出 3,表示证书链包含 root, intermediate, leaf
创建证书体系(CA 层级结构)的主要目的是 建立一个可被信任的、公钥基础设施(PKI),其核心作用包括:
✅ 1. 支持多级信任链
-
根 CA(Root CA) 是最顶层的信任机构,它可以签发 中间 CA(Intermediate CA)。
-
中间 CA 再去签发最终的 服务器证书(End-Entity Certificates),比如
www.example.com的证书。 -
好处:
- 根 CA 私钥可以离线保存,降低泄露风险。
- 实际的签发任务由中间 CA 完成,安全性和灵活性更高。
✅ 2. 实现责任隔离
- 如果中间 CA 被泄露或滥用,只需吊销该中间证书,无需废除整个根 CA。
- 实现了“最小权限”原则,分发风险更可控。
✅ 3. 支持多租户、多业务或多子域名场景
-
不一定专门为了子域名,但确实可以这样用:
- 比如你拥有
example.com,可以用一个中间 CA 签发*.example.com的所有证书(如a.example.com,b.example.com)。
- 比如你拥有
-
也适用于多业务线、多部门之间证书管理的隔离。
✅ 4. 支持更复杂的合规性和扩展机制
-
可根据需要为中间 CA 添加不同的 策略限制、用途、路径长度。
-
支持比如:
- 一部分 CA 只允许签发客户端证书;
- 一部分只用于生产环境;
- 一部分在专有 VPN 网络中使用。
🔄 举个实际例子:
你是一个云服务商,想给每个客户独立签发证书:
Root CA(离线保存)
|
-----------------
| |
客户A中间CA 客户B中间CA
| |
服务A1证书 服务B1证书
这样每个客户都有独立的中间 CA:
- 不互相干扰;
- 万一客户A的中间CA泄露,吊销即可,不影响客户B;
- 统一受根CA信任,浏览器也认可。
判断证书或证书签名请求(CSR)的密钥类型
证书(Certificate)和证书签名请求(CSR)可以使用多种密钥类型签发。你可以通过 isinstance 检查来确定使用的密钥类型:
public_key = cert.public_key()
if isinstance(public_key, rsa.RSAPublicKey):
# 针对 RSA 密钥的处理逻辑
elif isinstance(public_key, ec.EllipticCurvePublicKey):
# 针对椭圆曲线(EC)密钥的处理逻辑
else:
# 别忘了处理其它未识别的类型
cryptographic primitives
底层加密操作可以参考官方文档,这里就不再赘述。

pycryptodome库简介
pycryptodome是一个自包含的Python加密库,提供了广泛的加密算法和协议。以下是pycryptodome库的详细介绍。
“自包含”指的是一个软件包或库在功能上是完整独立的,不依赖外部的其他库或组件就能正常运行。
具体来说,对于 pycryptodome 来说:
- 它内置了所有加密算法和功能实现,不需要你额外安装其他加密相关的库。
- 只要安装了 pycryptodome 本身,就能直接使用它提供的所有加密功能。
- 不像某些库可能需要依赖系统上的 OpenSSL 或其他第三方库,pycryptodome 自带了所有必要的实现。
简而言之,就是“自己带齐所有东西,使用时不需要额外依赖”。这样安装和使用更加简单方便,也更容易移植。
基本概念和特点
- pycryptodome是pycryptography库的分支。
- 提供包括AES、RSA、ECC在内的多种加密算法。
- 不依赖外部库,完全自包含。
安装步骤和依赖项
pip install pycryptodome
pycryptodome库可以直接通过pip安装,无需依赖其他第三方库。
常用加密算法和函数的使用方法
对称加密
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
# 生成密钥
key = get_random_bytes(16)
# 创建加密器实例
cipher = AES.new(key, AES.MODE_CBC)
# 加密数据
data = b"Secret Message"
ct_bytes = cipher.encrypt(pad(data, AES.block_size))
# 解密数据
ct = cipher.decrypt(ct_bytes)
pt = unpad(ct, AES.block_size)
非对称加密
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import Crypto.Random
# 生成密钥对
key = RSA.generate(2048)
private_key = key.export_key()
public_key = key.publickey().export_key()
# 加密数据
message = 'Secret Message'
rsa_public_key = RSA.import_key(public_key)
cipher = PKCS1_OAEP.new(rsa_public_key)
encrypted_message = cipher.encrypt(message.encode())
# 解密数据
rsa_private_key = RSA.import_key(private_key)
cipher = PKCS1_OAEP.new(rsa_private_key)
decrypted_message = cipher.decrypt(encrypted_message).decode()
示例代码
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
# 生成密钥
key = get_random_bytes(16)
# 创建加密器实例
cipher = AES.new(key, AES.MODE_CBC)
# 加密数据
data = b"Secret Message"
ct_bytes = cipher.encrypt(pad(data, AES.block_size))
# 解密数据
ct = cipher.decrypt(ct_bytes)
pt = unpad(ct, AES.block_size)
print(pt)
比较 cryptography 和 pycryptodome
优缺点对比
| 库名称 | 优点 | 缺点 |
|---|---|---|
| cryptography | 支持广泛的加密算法,安全性好,适合复杂需求 | API复杂,学习曲线陡峭,不太适合初学者 |
| pycryptodome | 简单易用,API直观,适合快速开发和测试 | 功能较少,不适合高级加密需求 |
适用的场景和案例
- cryptography:适合高度安全保障需求,如金融服务、国家安全等领域。
- pycryptodome:适合快速原型开发、小规模项目和简单加密需求。
开发者如何选择合适的库
- 根据项目复杂性和安全需求选择。
- 复杂安全需求选择cryptography。
- 简单需求、追求快速开发选择pycryptodome。
2659

被折叠的 条评论
为什么被折叠?



