权限映射到密钥:细粒度访问控制实战 - 第4节
权限与密钥的关系:密钥在访问控制中的作用
聊到权限和密钥,我首先得说一个常见的误区:很多人以为权限就是“谁可以做什么”,密钥就是“加密用的密码”。嗯,这么理解其实也没错,但不够精准。你想想看,在一个细粒度访问控制系统里,密钥本质上是权限的物理载体——你持有某把密钥,就等于被授予了对应的操作权利。
我在项目里遇到过一种设计:把每个用户的权限集合直接加密成一个令牌,再用第二个密钥来验证这个令牌的完整性。说白了,权限的赋予、验证、撤销,每一步都绕不开密钥。密钥管理得不好,权限系统就是纸糊的。
为什么需要密钥?权限与密钥的映射模型
传统权限系统用数据库表来存角色和权限,认证通过后查表就行了。但一旦系统需要跨域、跨服务、甚至跨组织共享数据,这种“查表”模式就不太行了。密钥可以帮我们做一件事:把权限声明“封印”在密码学结构里,让验证方不需要联网查库。
- 对称密钥模式:服务端用一把密钥加密权限声明,客户端持有同一把密钥解密。速度快,但分发密钥是难题。
- 非对称密钥模式:私钥签名权限声明,公钥验证签名。公钥可以公开,私钥必须保密。适合多方验证场景。
对称加密与非对称加密基础
这部分是工具层。只有理解了两种加密的特点,你才能选对工具来保护权限数据。
对称加密:又快又简单,但密钥分发很头疼
对称加密用同一把密钥做加密和解密。我常用的算法是 AES-256-GCM,它同时提供加密和完整性校验。
# Python 示例:用 AES-256-GCM 加密权限数据
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# 密钥必须 32 字节,妥善保存
key = os.urandom(32)
aesgcm = AESGCM(key)
nonce = os.urandom(12)
plaintext = b'{"role":"admin","resource":"/api/v2/orders","action":"read"}'
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
# 解密
decrypted = aesgcm.decrypt(nonce, ciphertext, None)
print(decrypted.decode())
对称加密的缺点很明显:两个服务之间需要共享同一个密钥,密钥泄漏后所有历史数据都完蛋。我在项目中就踩过这个坑——两个微服务把密钥写死在配置中心里,结果被人拖了配置文件,整个权限令牌都能伪造了。嗯,从那以后我再也不敢让对称密钥跨进程共享了,除非用专门的密钥管理服务。
非对称加密:私钥签权、公钥验证
非对称加密有公钥和私钥两个钥匙。私钥用于签名或解密,公钥用于验证签名或加密。在权限场景里,最经典的做法是用私钥对权限声明做数字签名,任何人用公钥都能验证声明的真实性。
| 特性 | 对称加密 | 非对称加密 |
|---|---|---|
| 密钥数量 | 2 个(两个实体持有同一把) | 2 个(一对,公钥公开) |
| 典型算法 | AES-256, ChaCha20 | RSA-2048, ECDSA-P256 |
| 加解密速度 | 快(硬件加速) | 慢(百倍差异) |
| 密钥分发难度 | 高(需要安全信道) | 低(公钥随意分发) |
| 权限验证典型用法 | 加密整个权限令牌 | 签名权限声明(JWT 场景) |
非对称加密的典型代码(使用 Python 的 cryptography 库):
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
# 生成私钥和公钥
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# 签名权限声明
permission = b'{"user":"alice","action":"write","resource":"/docs/report.pdf"}'
signature = private_key.sign(permission, ec.ECDSA(hashes.SHA256()))
# 验证签名
try:
public_key.verify(signature, permission, ec.ECDSA(hashes.SHA256()))
print("权限声明有效")
except Exception:
print("签名无效,可能被篡改或密钥错误")
密钥生命周期管理
有密钥不等于安全,管不好密钥反而更危险。我个人把密钥生命周期分成四个阶段:生成 → 分发与存储 → 使用与轮换 → 销毁。下面这张图你可以保存下来,每次设计系统时对照着看。
每个阶段我都踩过坑,下面展开说说。
密钥生成:不能偷偷用 random()
生成密钥的关键是熵源质量。我曾经见过一个项目用 Python 的 random.seed(time()) 来生成密钥,上线不到一天就被攻破了。正确做法是使用操作系统提供的 CSPRNG(比如 Linux 的 /dev/urandom ,Python 的 os.urandom 或 secrets 模块)。
- 对称密钥:至少 256 位(AES-256)
- 非对称密钥:RSA 至少 2048 位,ECC 选 P-256 或 Curve25519
- 生成后立即提取公钥并安全存储私钥
密钥分发与存储:锁好保险箱
密钥存储的黄金法则是:永远不要落在明文文件或环境变量里。我建议用密钥管理服务(KMS),比如 AWS KMS 或 HashiCorp Vault。你还可以用硬件安全模块(HSM)来保护根密钥。小规模项目至少也要用加密存储(比如用cryptography的Fernet封装一层)。
密钥使用:别在日志里打印密钥
使用密钥时,尽量把它保持在受限的内存区域里,不要序列化到字符串。比如在 Python 中,我习惯用 bytearray 并在用完后 memset 覆盖。另外,每个使用点都要加日志审计(记录密钥 ID 和操作时间,但不记录密钥本身)。
密钥轮换与销毁:有备无患
轮换周期通常按业务风险等级定。比如对账单签名密钥,我建议 90 天轮换一次;内部调试密钥可以放宽到 1 年。销毁时不是“删文件”就够了——要覆写磁盘扇区或用 KMS 的 schedule_key_deletion。我曾经犯过一个错:销毁旧密钥前没有确认所有缓存已经被清除,结果遗留的数据解密失败被业务方追着问……所以,销毁前务必确认没有引用。