3、核心库cryptography实战:生成RSA/ECC密钥对、创建自签名证书、解析X.509证书字段
说实话,很多运维同学写脚本时喜欢直接调 openssl 命令行。我个人习惯用 Python 的 cryptography 库来做——更可控,也更容易集成到自动化流程里。这一章我们直接上手,把密钥生成、自签名证书创建、证书字段解析这三个核心操作练熟。
3.1 环境准备与库导入
先装好库:
pip install cryptography
然后导入我们需要的模块:
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, ec
from cryptography.hazmat.backends import default_backend
import datetime
嗯,这里要注意:hazmat 这个名字看着吓人,其实它只是表示“底层密码学原语”,用的时候小心参数别传错就行。
3.2 生成 RSA 密钥对
RSA 目前还是最通用的选择。我在项目中遇到过有些老旧设备只支持 RSA 2048,所以轮换脚本里我一般会保留 RSA 的生成分支。
def generate_rsa_keypair(key_size=2048):
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=key_size,
backend=default_backend()
)
return private_key
为什么公钥指数固定用 65537?说白了,这是一个安全性和性能的平衡点。更小的指数有安全风险,更大的指数加解密太慢。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| public_exponent | 65537 | 固定值,不要改 |
| key_size | 2048 / 4096 | 2048 是底线,4096 更安全但慢 |
3.3 生成 ECC 密钥对
ECC 密钥更短,性能更好。我个人偏好用 SECP256R1(也叫 P-256),兼容性最好。
def generate_ecc_keypair(curve=ec.SECP256R1()):
private_key = ec.generate_private_key(
curve,
backend=default_backend()
)
return private_key
你想想看,同样的安全强度,RSA 需要 3072 位,ECC 只要 256 位。在 IoT 设备或高并发场景下,这个差距很明显。
3.4 创建自签名证书
自签名证书在内部测试、开发环境、临时服务里用得很多。核心逻辑就是:用私钥给自己签发一张证书。
def create_self_signed_cert(private_key, subject_name="example.com"):
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Beijing"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Beijing"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MyOrg"),
x509.NameAttribute(NameOID.COMMON_NAME, subject_name),
])
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer) # 自签名:subject == issuer
.public_key(private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
.add_extension(
x509.BasicConstraints(ca=True, path_length=None), critical=True
)
.sign(private_key, hashes.SHA256(), default_backend())
)
return cert
我曾经踩过一个坑:not_valid_after 忘记加时区,导致证书在 UTC 零点之后立刻过期。后来我统一用 datetime.datetime.utcnow() 配合 timedelta,避免时区问题。
3.5 解析 X.509 证书字段
轮换脚本里经常需要读取现有证书的信息,比如检查是否快过期、提取公钥、验证签名算法。下面这段代码可以帮你把证书的常用字段都解析出来:
def parse_certificate(cert_pem_bytes):
cert = x509.load_pem_x509_certificate(cert_pem_bytes, default_backend())
info = {
"subject": cert.subject.rfc4514_string(),
"issuer": cert.issuer.rfc4514_string(),
"serial_number": cert.serial_number,
"not_valid_before": cert.not_valid_before_utc,
"not_valid_after": cert.not_valid_after_utc,
"signature_algorithm": cert.signature_algorithm_oid._name,
"public_key_type": type(cert.public_key()).__name__,
}
# 提取公钥详细信息
pub_key = cert.public_key()
if isinstance(pub_key, rsa.RSAPublicKey):
info["key_size"] = pub_key.key_size
elif isinstance(pub_key, ec.EllipticCurvePublicKey):
info["curve_name"] = pub_key.curve.name
return info
你想想看,这个函数在证书轮换脚本里有多重要?轮换前先解析旧证书,拿到 subject、有效期、密钥类型,然后生成新证书时直接复用这些字段,保证无缝衔接。
3.6 完整流程:从生成到持久化
把上面几个函数串起来,就是一个完整的证书创建流程:
# 1. 生成密钥对
private_key = generate_rsa_keypair(2048)
# 2. 创建自签名证书
cert = create_self_signed_cert(private_key, "myservice.internal")
# 3. 持久化私钥(PEM 格式,加密保存)
with open("server.key", "wb") as f:
f.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.BestAvailableEncryption(b"your-password")
))
# 4. 持久化证书
with open("server.crt", "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
# 5. 解析验证
parsed = parse_certificate(open("server.crt", "rb").read())
print(parsed)
3.7 本章知识体系
下面这张图帮你理清本章的核心逻辑:
3.8 常见问题与避坑
- 私钥格式不兼容: 有些老系统只认 PKCS#1 格式,而 cryptography 默认输出 PKCS#8。用
PrivateFormat.TraditionalOpenSSL可以解决。 - 证书序列号重复: 手动指定序列号可能导致冲突。用
x509.random_serial_number()生成随机序列号,避免重复。 - 签名算法不匹配: RSA 密钥用 SHA256,ECC 密钥也用 SHA256,但 ECDSA 签名算法会自动选择。不要手动混用。