受够那些乱七八糟的加密库了?cryptography解君愁

摘要

你是否曾在处理加密任务时被各种奇怪的 Python 加密库搞得头大?比如 API 风格各异、文档零碎、示例难懂,甚至还有一些库看上去很“高级”,实则早已停止维护?如果你也在项目中被这些乱七八糟的加密工具困扰,那么你并不孤单。

在 Python 的生态中,加密相关的库五花八门,从标准库 hashlib 到曾经风靡一时的 PyCrypto,再到现代主力选手 cryptographyPyCryptodome。选择哪个库往往不仅决定了你的开发效率,还直接影响系统的安全性与可维护性。错误使用或选型不当,轻则踩坑调试数小时,重则引发安全漏洞,影响上线稳定性。

本篇博客将帮助你梳理这些加密库的来龙去脉,比较它们在功能、易用性、稳定性、安全性等方面的优缺点,并结合真实项目经验分析各种“坑点”。最后,我们将重点介绍 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 路径

如果你同时安装了 pycryptopycryptodome,会出现莫名其妙的冲突(比如无法找到 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'

参数:

  • keybytesstr)—— 以 URL 安全的 base64 编码形式表示的 32 字节密钥。必须严格保密。拥有此密钥的任何人都可以创建和解密信息。
@classmethod generate_key()

生成一个新的 Fernet 密钥。请妥善保存!

如果你丢失了这个密钥,将无法再解密任何信息;如果别人获取了它,他们不仅可以解密你的所有消息,还可以伪造任意伪装成你发送的消息(并且这些消息会通过身份验证并成功解密)。

encrypt(data)

对传入的 data 进行加密。加密结果被称为“Fernet token”,它在隐私和真实性方面提供了强有力的保障。

参数:

  • databytes)—— 要加密的消息。

返回值:

  • bytes:一个经过安全加密的消息,只有持有密钥的人才能读取或修改。该值是 URL 安全的 base64 编码,称为 “Fernet token”。

异常:

  • TypeError —— 当传入的数据不是字节类型时会抛出此异常。

注意:
加密后的消息中包含了生成时的当前时间戳(明文形式),这意味着攻击者可能看到消息的生成时间

decrypt(token, ttl=None)

解密一个 Fernet token。如果解密成功,将返回原始的明文数据;如果失败,将抛出异常。在返回之前,Fernet 会验证数据是否被篡改,因此可以放心立即使用解密后的数据。

参数:

  • tokenbytesstr):Fernet token,即 encrypt() 方法生成的加密结果。
  • ttlint,可选):token 有效期(秒)。如果消息距离生成时间超过指定秒数,将抛出异常。如果不提供(或为 None),则不考虑 token 的生成时间。

返回值:

  • bytes:原始的明文数据。

异常:

  • cryptography.fernet.InvalidToken:当 token 无效时抛出。无效的原因可能包括:超过 ttl、格式错误或签名验证失败。
  • TypeError:当 token 不是 bytesstr 类型时抛出。
decrypt_at_time(token, ttl, current_time)

自版本 3.0 起新增。

解密一个 token,并使用明确传入的当前时间(current_time)进行校验。其他参数(tokenttl)的说明与 decrypt() 相同。

这个方法的主要用途是让客户端代码可以测试 token 的过期行为

⚠️注意:使用此方法可能导致不安全行为,务必在测试之外使用真实的当前时间(如 int(time.time()))作为 current_time

参数:

  • current_timeint):当前时间戳。
extract_timestamp(token)

自版本 2.3 起新增。

返回 token 中记录的时间戳(即生成时间)。调用者可以据此判断 token 是否即将过期,并据此,例如重新生成一个新 token。

参数:

  • tokenbytesstr):由 encrypt() 生成的 Fernet token。

返回值:

  • int:token 的 Unix 时间戳。

异常:

  • cryptography.fernet.InvalidToken:当 token 签名无效时抛出。
  • TypeError:当 token 不是 bytesstr 类型时抛出。
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!'

参数:

  • msgbytesstr):要重新加密的 token。

返回值:

  • bytes:加密后的 token(URL-safe base64 编码),无法被读取或篡改。

异常:

  • cryptography.fernet.InvalidToken:如果 token 无效,抛出此异常。
  • TypeError:如果 msg 不是 bytesstr 类型,抛出此异常。
class cryptography.fernet.InvalidToken

这是在 Fernet.decrypt()MultiFernet 操作中,token 无效时抛出的异常。详情见 Fernet.decrypt()

使用密码(password)与 Fernet 结合

Fernet 可以配合密码使用。为此你需要使用密钥派生函数(KDF),例如 PBKDF2HMACArgon2idScrypt,将密码转换成加密密钥。

示例代码:

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)获取证书时,一般流程如下:

  1. 你生成一对私钥/公钥。
  2. 你创建一个证书请求(CSR),并用你的私钥对其签名(以证明你拥有该私钥)。
  3. 你将 CSR 提交给 CA(注意,不提交私钥)。
  4. CA 验证你是否拥有你请求证书的资源(例如域名)。
  5. CA 向你颁发一张证书,该证书由 CA 签名,包含你的公钥和你所认证的资源信息。
  6. 你配置服务器使用该证书及私钥来服务访问请求。

如果你希望从一个典型的商业 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 证书配置通常包括两部分:

  1. 你的私钥(之前生成的 key.pem,自己保存,绝不能泄露)
  2. CA 返回的证书文件(比如 yourdomain.com.crt

可选:

  1. 中间证书链文件(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。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值