4. RSA加密算法详解:公钥/私钥、加密/解密、签名/验签

咱们这一章要啃一块硬骨头——RSA。很多人在做接口加密时,一听非对称加密就头大。我刚开始也是一样,看到公钥、私钥、模数、指数这些词,第一反应就是「这什么鬼」。但用多了你会发现,RSA其实没那么玄乎,说白了就是一对钥匙,一把公开,一把私藏。

4.1 RSA到底解决了什么问题?

想想看,如果有两个人隔着网线要传小纸条,又怕被别人偷看,该怎么办?最简单的办法是给纸条上个锁。但问题来了——锁和钥匙怎么同时给对方?

这就是RSA最妙的地方:它把「上锁」和「开锁」用的钥匙拆成了两把。公钥用来加密,公开出去谁都可以拿到;私钥用来解密,只有你自己知道。任何人只要拿着你的公钥,就能给你发加密消息,但只有你能解开看。

核心要点:公钥加密、私钥解密,这叫「加密通信」; 私钥签名、公钥验签,这叫「身份认证」。两者不能搞混。

4.2 RSA的数学基础(不讲太深)

我之前在面试人家的时候,最爱问的一个问题是:「RSA为什么安全?」答案其实一句话:大整数分解极难。

RSA的核心就是个大整数n,它是两个大素数p和q的乘积。有人想破解RSA,就得把n分解成p和q。这个分解有多难?这么说吧,如果n是2048位的,用现在最快的计算机算到宇宙毁灭也算不完。

概念 含义 在代码中的体现
p, q 两个大素数 由操作系统随机生成
n = p × q 模数(Modulus) 公钥和私钥都包含n
e 公钥指数(通常是65537) 公钥的一部分
d 私钥指数(私钥的核心) 私钥的核心秘密
φ(n) = (p-1)(q-1) 欧拉函数值 用于计算私钥d

4.3 生成密钥对

Python里最常用的RSA库就是cryptography。我个人习惯用这个库,因为它接口设计得比较现代,不用自己去捣鼓底层的数学运算。

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# 生成私钥
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
)

# 从私钥中提取公钥
public_key = private_key.public_key()

# 将私钥序列化为 PEM 格式(带密码保护)
private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.BestAvailableEncryption(b'my_password')
)

# 将公钥序列化为 PEM 格式
public_pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

print("私钥已生成,长度 2048 位")
print("公钥已提取,格式为 PEM")
我的经验:密钥长度推荐2048位起。1024位的RSA在学术界已经被认为不够安全了。 我曾经在项目里见过用1024位的,后来被安全审计打回来重做,血泪教训。

4.4 加密和解密

有了密钥对,接下来就是实操了。加密时,你得用对方的公钥;解密时,对方用他的私钥。我举个例子,假设你要给服务器发一条密文消息。

加密流程

from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes

# 假设你已经有了对方的公钥对象 public_key
message = b"这是要加密的敏感数据"

ciphertext = public_key.encrypt(
    message,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

print("加密完成,密文长度:", len(ciphertext))

解密流程

# 用你的私钥解密(注意:加密和解密必须用同一套填充方案)
plaintext = private_key.decrypt(
    ciphertext,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

print("解密结果:", plaintext.decode())
注意:RSA不能加密太长的数据。对于2048位的密钥,一次最多加密245个字节。 如果数据太长,你得先把它切成块,或者用混合加密(RSA + AES)来解决。 我曾经踩过这个坑,传个JSON进去直接报错,排查了半天才发现是数据长度超了。

4.5 签名和验签

加密是为了保密,签名是为了证明「这消息确实是我发的,而且没被人改过」。这就像你在合同上签了个名,有法律效力。

签名流程

from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes

message = b"这是要签名的订单数据"

signature = private_key.sign(
    message,
    padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    hashes.SHA256()
)

print("签名生成,长度:", len(signature))

验签流程

from cryptography.exceptions import InvalidSignature

public_key = private_key.public_key()

try:
    public_key.verify(
        signature,
        message,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print("验签通过 ✅,消息未被篡改")
except InvalidSignature:
    print("验签失败 ❌,消息已被篡改或来源不对")

你想想看,为什么签名要用私钥,验签要用公钥?因为私钥只有你一个人有——你用私钥签名,任何人都能用你的公钥来验证这个签名是不是你亲手做的。这就是「不可抵赖性」的核心。

4.6 RSA核心流程总览

RSA 核心流程与操作对应 密钥对生成 (Key Generation) generate_private_key → 公钥 + 私钥 公钥 (Public Key) 私钥 (Private Key) 明文 (Plaintext) 加密 (Encrypt) 密文 (Ciphertext) 密文 (Ciphertext) 解密 (Decrypt) 原文 (Plaintext) 待签名数据 签名 (Sign) 数字签名 签名 + 数据 验签 (Verify) 通过 / 失败 基本原则:公钥加密 + 私钥解密 = 保密通信;私钥签名 + 公钥验签 = 身份认证。
实战小心得:我建议你在项目里统一用一种填充模式,比如OAEP + SHA256。 不要一个接口用PKCS1v15,另一个用OAEP,调度起来容易搞混淆,而且PKCS1v15在某些情况下有已知的攻击面。

4.7 避坑指南(我的血泪史)

  • 密钥长度别省:2048位是底线,1024位别用。我曾经在一个老旧系统里发现还在用512位的RSA,当时就震惊了。
  • 填充模式必须一致:加密时用的填充方案,解密时一定要用一样的。签名和验签也是一样。我记得有一次排查bug,发现就是加密填充用了PKCS1v15,解密却用的OAEP,死活解不开。
  • 数据长度不能超:RSA加密有长度限制(2048位密钥最多245字节)。如果数据超过这个数,要么分段,要么改用混合加密。我习惯是用RSA加密AES密钥,再用AES加密实际数据,既快又不受长度限制。
  • 私钥保护好:私钥一旦泄露,整个安全体系就崩塌了。我一般把私钥放在密钥管理服务(KMS)里,或者至少用密码保护着。