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核心流程总览
实战小心得:我建议你在项目里统一用一种填充模式,比如OAEP + SHA256。
不要一个接口用PKCS1v15,另一个用OAEP,调度起来容易搞混淆,而且PKCS1v15在某些情况下有已知的攻击面。
4.7 避坑指南(我的血泪史)
- 密钥长度别省:2048位是底线,1024位别用。我曾经在一个老旧系统里发现还在用512位的RSA,当时就震惊了。
- 填充模式必须一致:加密时用的填充方案,解密时一定要用一样的。签名和验签也是一样。我记得有一次排查bug,发现就是加密填充用了PKCS1v15,解密却用的OAEP,死活解不开。
- 数据长度不能超:RSA加密有长度限制(2048位密钥最多245字节)。如果数据超过这个数,要么分段,要么改用混合加密。我习惯是用RSA加密AES密钥,再用AES加密实际数据,既快又不受长度限制。
- 私钥保护好:私钥一旦泄露,整个安全体系就崩塌了。我一般把私钥放在密钥管理服务(KMS)里,或者至少用密码保护着。