笃行
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
  • 通用后台基础(跨域)

    • 并发模型 · 进程 / 线程 / 协程
    • 操作系统核心与零拷贝
    • Go 语言基础与常见陷阱
    • C++11 语言基础与常见陷阱
    • C++20 语言基础与常见陷阱
    • Rust 语言基础与常见陷阱
    • 设计模型 · Actor / CSP / Reactor / 同步异步
    • GC 与 STW · Go / JVM
    • 可观测性
    • 时序异常检测(EWMA / ARIMA / 滑动窗口)
    • 数据库范式与存储引擎:从关系型到向量库
    • MySQL InnoDB 索引与事务
    • Redis 版本演进 & 分布式
    • 消息队列 · 可靠投递与选型
    • 分布式事务 · 2PC / TCC / Saga / 最终一致性
    • HTTP / HTTPS / TLS 与 RPC
    • 加密基础:对称 / 非对称 / 哈希与组合模式
    • 叙事主骨架轴选择方法论 SOP

加密基础:对称 / 非对称 / 哈希与组合模式

想象这样一天:你注册了一个新网站,密码用 SHA-256 存进 DB——第二天被拖库,攻击者用 GPU 集群三分钟破译;你写了一个内网 API,用 SHA256(密钥 + 请求体) 做签名——两周后被伪造调用,钱财转空;你上线 TLS,为了兼容老客户端保留了 RSA 密钥交换——三个月后服务器私钥泄露,去年一整年的历史流量被回放解密。这三种事故的根源都不是"忘了加密",而是把三大原语(对称/非对称/哈希)的组合方式搞错了。本篇是站点加密原理的单一事实源:一次讲通对称、非对称、哈希三块砖,再把 AEAD、HMAC、数字签名、密钥交换、KDF 这些组合方式搭起来,最后落到 TLS / JWT / mTLS 三个真实协议。

一句话结论

密码学面试的主线只有三条:对称快、非对称慢,所以工程上永远是"非对称谈钥匙 + 对称跑数据";哈希 ≠ MAC ≠ 签名——三者能防的攻击面不同(篡改 / 伪造 / 抵赖),选错就漏底;IV / Nonce 用错等同泄漏密钥。抓住这三条,AEAD / HMAC / ECDHE / JWT / mTLS 所有选型题都是同一道题的变形。

大白话入门

先用最土的话讲清楚这门学问在干什么、发展到今天为止解决了哪些问题。术语一律先翻译成中文,读到后面就不会被吓退。

密码学到底在解决什么?

抛开所有术语,密码学只有四个目标:

目标一句话生活比喻靠什么原语
机密性(Confidentiality)让第三方看不懂把信装进上锁的箱子对称加密(AES)、非对称加密(RSA)
完整性(Integrity)让第三方改不了(改了能发现)信封上贴防伪封条哈希(SHA-256)、MAC(HMAC)
认证(Authentication)确认对方是谁看身份证、验签名数字签名(RSA/ECDSA/Ed25519)、证书
不可抵赖(Non-repudiation)事后你没法说"这不是我发的"手写签名 + 公证数字签名(HMAC 做不到)

关键洞察:一个真实协议(TLS、JWT、mTLS)通常要同时满足前三个目标(TLS 还额外要"前向安全")。所以你会看到 TLS 里同时用了对称、非对称、哈希、签名、密钥交换、KDF——不是炫技,是因为这四个目标要用不同的工具。

一张图看懂发展史(1976 → 2024)

演进主线三句话:

  1. 对称 → 非对称(1970s):解决"陌生人怎么协商密钥"这个死结。
  2. 手拼加密+MAC → AEAD(2000s→2018):一步做完,杜绝 Padding Oracle 类实现坑。
  3. RSA → 椭圆曲线 → 抗量子(1977→2012→2024):性能提升 + 应对未来量子威胁。

术语速查表(先扫一遍,读正文时随时翻回来)

这一节读者可以直接跳过,遇到不认识的词再回来查。

分类 1:基础对象

术语中文/含义生活比喻举例
明文(plaintext)原始可读的数据信的内容"hello world"
密文(ciphertext)加密后不可读的数据锁进箱子的信一堆看不懂的字节
密钥(key)加/解密用的秘密钥匙32 字节随机数
公钥(public key)可公开的钥匙(非对称)挂锁挂网站上给人下载
私钥(private key)严格保密的钥匙(非对称)只有你有的钥匙存本地文件+权限 600
IV(Initialization Vector,初始向量)让相同明文加密出不同密文的随机数加密时的"起点标记"CBC 模式必需
Nonce(Number used ONCE,一次性数)每次加密都必须不重复的数一次性密码GCM/CTR 模式必需
Salt(盐)让相同密码哈希出不同结果的随机数密码前加的随机料Argon2/bcrypt 存密码时用
Tag(认证标签)AEAD 输出的完整性校验字节一次性防伪封条GCM 的 16 字节 tag

IV vs Nonce vs Salt 的差别:都是"随机数",但要求不同:

  • IV:唯一即可,可以公开(CBC)
  • Nonce:唯一即可,可以公开,但同密钥下绝不能重复(GCM/CTR)
  • Salt:唯一即可,可以公开,用于密码存储

分类 2:算法类别

术语全称/中文一句话
对称加密Symmetric encryption加密解密同一把钥匙
非对称加密Asymmetric / Public-key encryption一对公私钥,方向相反
哈希Hash / Digest / 摘要单向压缩成固定长度指纹
MACMessage Authentication Code / 消息认证码带密钥的哈希,防伪造
AEADAuthenticated Encryption with Associated Data加密+完整性+认证三合一
AADAssociated Authenticated DataAEAD 里"认证但不加密"的字段(如 HTTP 头)
KDFKey Derivation Function / 密钥派生函数从一个秘密派生出多把子密钥
KEMKey Encapsulation Mechanism / 密钥封装非对称加密的现代叫法,专用于传对称密钥
PFSPerfect Forward Secrecy / 前向安全长期私钥泄露也解不了历史流量
PRFPseudo-Random Function / 伪随机函数输出看起来像随机数的函数(HMAC 就是 PRF)
PRKPseudo-Random Key(HKDF Extract 的输出)HKDF 第一步压出来的均匀密钥
OKMOutput Keying Material(HKDF Expand 的输出)HKDF 第二步展开出的最终子密钥
IKMInput Keying Material(HKDF Extract 的输入)原始的、熵不均匀的秘密(如 ECDH 共享秘密)

分类 3:具体算法

术语类别一句话
AES对称加密分组密码,工业界默认,AES-128/192/256
ChaCha20对称加密流密码,移动端首选(无 AES 硬件时)
RSA非对称加密 + 签名老牌,密钥长(2048/3072 bit)
ECC椭圆曲线密码学(一类)短密钥、快,涵盖 ECDSA/ECDH/Ed25519 等
ECDSA签名(椭圆曲线)需每次签名随机数 k,k 泄露 = 私钥泄露(PS3 案)
Ed25519签名(椭圆曲线)确定性签名(无 k 依赖)、现代默认
DH密钥交换1976 年 Diffie-Hellman 首创,基于整数域
ECDH密钥交换(椭圆曲线版)更短更快
ECDHEECDH + Ephemeral(临时)每次会话用新密钥,实现前向安全
X25519ECDH 的现代曲线实现TLS 1.3 默认,Bernstein 设计
SHA-1哈希已弃用(2017 被破)
SHA-2(SHA-256/384/512)哈希工业界主力
SHA-3哈希海绵结构,抵御长度扩展,性能不如 SHA-2
BLAKE2/BLAKE3哈希极快,非官方标准但工程流行
HMACMAC哈希+密钥的标准做法(RFC 2104)
Poly1305一次性 MAC组合 ChaCha20 → ChaCha20-Poly1305 AEAD
HKDFKDFHMAC 底子,TLS 1.3 官选(RFC 5869)
PBKDF2密码存储 KDF老,只调迭代次数
bcrypt密码存储 KDF曾是默认,输入 72 字节截断坑
scrypt密码存储 KDF时间+内存双硬
Argon2id密码存储 KDF当前推荐默认,PHC 冠军

分类 4:工作模式与结构

术语含义
ECB(Electronic Codebook)最原始的分组模式,每块独立加密,图案泄露,禁用
CBC(Cipher Block Chaining)每块与前一块密文异或,需 IV,无认证需叠 HMAC
CTR(Counter Mode)把 AES 当流密码用,需 Nonce
GCM(Galois/Counter Mode)CTR + GMAC = AEAD,现代默认
SPN(Substitution-Permutation Network)AES 底层的加密结构,"替换+置换"层层叠加,不用记细节
Merkle-Damgård 结构SHA-1/2 的底层链式结构,长度扩展攻击的成因
海绵结构(Sponge)SHA-3 的底层结构,抵御长度扩展

分类 5:协议与工程

术语含义
TLS(Transport Layer Security)HTTPS 的底层,加密整个连接
mTLS(mutual TLS)双向 TLS,客户端也提交证书
JWT(JSON Web Token)自包含的签名令牌,HS256/RS256 两种签法
JWKS(JSON Web Key Set)公钥公开分发的 URL 端点(配 RS256 用)
JWE(JSON Web Encryption)加密版 JWT(不只签名,还加密载荷)
CA(Certificate Authority)证书颁发机构(DigiCert / Let's Encrypt)
X.509证书的国际标准格式
CRL(Certificate Revocation List)证书吊销列表,CA 定期发布
OCSP(Online Certificate Status Protocol)实时查证书吊销状态
OCSP Stapling服务器代查 OCSP 并在握手时附上应答,事实标准
SPIFFE(Secure Production Identity Framework for Everyone)服务网格的身份标准,每个服务一个 SPIFFE ID
PKI(Public Key Infrastructure)公钥基础设施,指 CA + 证书 + 吊销的整套体系
PQC(Post-Quantum Cryptography)抗量子密码,如 NIST 已标准化的 Kyber/Dilithium
TOFU(Trust On First Use)首次连接时信任对方(SSH 的做法,无 CA 时用)

一个新手最常混淆的对比

在读正文前,先把最容易搞混的三对说清楚:

混淆点 1:加密 vs 编码 vs 哈希

  • 编码(Base64、URL encode):不是加密,任何人可逆。目的是"让二进制能在文本协议里传输"。
  • 加密(AES):可逆(有密钥才能解开)。目的是保密。
  • 哈希(SHA-256):不可逆。目的是产生指纹,不是保密。

混淆点 2:签名 vs 加密

  • 都用非对称密钥对,但方向相反(前面 SVG 图讲过)。
  • 加密:接收方公钥 → 私钥(保密)
  • 签名:签名方私钥 → 公钥(认证)
  • 签名不加密内容——原消息还是明文传,签名只是附加的一段可验证字节。

混淆点 3:HMAC vs 数字签名

  • HMAC:双方共享密钥(对称的思路,只是用哈希实现)
  • 数字签名:私钥签、公钥验(非对称)
  • 区别的核心是"信任模型":HMAC 双方互信,签名单向。

场景问题

一个能秒懂的类比:三种"锁"

假设你要给朋友寄东西,市面上有三种"锁",选错了就出事:

类比密码学对应一句话
一把钥匙开关同一把锁(你和朋友共用)对称加密快,但要先想办法把钥匙安全递过去
公开的挂锁 + 只有自己的钥匙(谁都能锁上,只有你能开)非对称加密慢,但解决了"怎么把钥匙递过去"
一次性防伪封条(撕过就看得出来,但封条本身不锁东西)哈希不加密,只用来"验证有没有被动过"

工程上永远是这三者混着用——用挂锁把钥匙寄过去(非对称),用钥匙锁大件(对称),加封条防拆(哈希)。TLS / SSH / mTLS / JWT 都是这个套路的变种。

面试高频翻车题

题目考点典型错答正解要点
RSA 加密和 RSA 签名钥匙一样吗公私钥语义"都用公钥加密"加密用接收方公钥、签名用自己私钥——方向相反
用 SHA256(K + m) 拼一个 MAC 行不行长度扩展攻击"行,够简单"不行,SHA-2 可被扩展,必须用 HMAC
AES-GCM 的 Nonce 能不能重复Nonce 复用"随便什么都行,反正是随机数"同密钥下绝不能重复,重复 = 密钥泄漏
ECDHE 里那个 E 是什么前向安全"椭圆的意思"Ephemeral,每次会话临时密钥
JWT 用 HS256 还是 RS256信任模型"都能用,看喜好"内网 HS256、对外 RS256/ES256
密码存 DB 用 SHA-256 够不够慢哈希"够,反正不可逆"不够,需 Argon2/bcrypt + 加盐
签名能防篡改吗、能防抵赖吗能力矩阵"都能防篡改就行"签名比 HMAC 多"不可抵赖"能力

实现方案

下面开始正式讲。先看整体地图,再逐块拆解——这样后面每讲一个原语你都知道它落在哪里:

读图窍门:紫色是砖(原语)、蓝色是灰浆(组合方式)、绿色是最终房子(真实协议)。任何"这个协议为什么这样设计"都能顺着箭头往回追。


一、对称加密:一把钥匙开关

一分钟大白话

  • 是什么:加密和解密用同一把钥匙的算法。你和朋友事先约好一句暗号(钥匙),之后就用它加密聊天。
  • 什么时候诞生:几千年前就有(凯撒密码),现代版 DES 1977 年、AES 2001 年。
  • 解决什么问题:大批量数据的高速加密——CPU 一秒能处理 GB 级数据。
  • 典型场景:TLS 握手完之后传数据、磁盘全盘加密(BitLocker/FileVault)、数据库字段加密。
  • 最大死穴:"第一次怎么把钥匙告诉对方"——这个问题一直到 1976 年 DH 密钥交换才解决(见第七节)。

一分钟大白话

  • 是什么:加密和解密用同一把钥匙的算法。你和朋友事先约好一句暗号(钥匙),之后就用它加密聊天。
  • 什么时候诞生:几千年前就有(凯撒密码),现代版 DES 1977 年、AES 2001 年。
  • 解决什么问题:大批量数据的高速加密——CPU 一秒能处理 GB 级数据。
  • 典型场景:TLS 握手完之后传数据、磁盘全盘加密(BitLocker/FileVault)、数据库字段加密。
  • 最大死穴:"第一次怎么把钥匙告诉对方"——这个问题一直到 1976 年 DH 密钥交换才解决(见第七节)。

故事场景

你和朋友想私下聊天,事先在见面时对好了一句暗号(对称密钥)。之后就用暗号把信息编码后发出去——谁截获都读不懂,除非他也知道暗号。

问题只有一个:第一次怎么把暗号安全地告诉朋友?(——这就是后面非对称加密要解决的问题。)

直觉画面

关键点:加密和解密用的是同一把钥匙——所以叫"对称"。

术语与家族

  • 分组密码 vs 流密码:AES 属于分组密码——一次处理固定长度的一块(AES 是 16 字节 = 128 bit);ChaCha20 属于流密码——像一条无穷长的伪随机比特流,逐字节 XOR 你的明文。
  • AES 家族:AES-128 / AES-192 / AES-256(数字是密钥长度),底层结构叫 SPN(Substitution-Permutation Network),面试不用深挖细节,知道"AES-256 是当前工业界默认最强对称算法"就够。
  • 工作模式:AES 一次只能加 16 字节。真实消息几 KB 几 MB,就需要"工作模式"告诉 AES 怎么把一块一块拼起来。这里就有大坑:
模式是否需要 IV/Nonce是否带认证常见坑
ECB(最简单:每块独立加密)否❌相同明文块 → 相同密文块,图案会泄露出来(企鹅图梗),禁用
CBC(每块与前一块的密文异或)需要 IV,唯一即可(可公开)❌无完整性,需再叠 HMAC;必须先加密再 MAC,否则有 Padding Oracle 攻击
CTR(把 AES 当伪随机流用)需要 Nonce,必须唯一❌Nonce 复用 → 两条明文异或直接暴露
GCM(CTR + 内置认证 = AEAD)需要 Nonce,必须唯一✅Nonce 复用 = 灾难,比 CTR 复用还严重

深入:ECB 为什么禁用(一张图讲透)

看出问题了吗?明文块 1 和 2 相同 → 密文块也相同。这在加密位图时最直观:一张企鹅图片加密完还是能看出企鹅的轮廓(对应像素颜色相同的地方,密文也相同)。

CBC 怎么修:让每一块都异或前一块的密文,第一块异或一个随机 IV:

即使明文块 1 和 2 相同,因为前一块密文不同 → 异或结果不同 → 输入 AES 的东西不同 → 密文不同。图案泄露解决。

现代默认:AEAD(一步到位)

AEAD = Authenticated Encryption with Associated Data(带关联数据的认证加密)。一步做完三件事:机密性 + 完整性 + 认证。主流两个:

  • AES-256-GCM:有 CPU 硬件加速(AES-NI)时首选,云端和现代 x86/ARM 服务器都有。
  • ChaCha20-Poly1305:没硬件加速的场景(老手机、嵌入式)首选,纯软实现比 AES 快。

类比:AEAD = 快递箱 + 一次性防伪封条

明文 plaintext密钥 Key (32B)Nonce (12B)AAD (可选)AES-GCM加密 + 认证(一步完成)密文 ciphertexttag16B← 拼在密文尾收到方拆包时,先验 tag 一致再解密;tag 不对直接丢弃,绝不吐半字节明文

AAD(Associated Data):参与认证但不加密的字段。典型用途:HTTP 头、连接 ID——你不想加密它(否则中间节点看不到路由信息),但改动它 tag 会失效。

GCM 的 tag 挂在密文哪里(Go 伪码,非可运行程序):

// 加密:Seal(dst, nonce, plaintext, additionalData) → dst = ciphertext || tag
block, _ := aes.NewCipher(key32)          // 32 字节 = AES-256
aead, _ := cipher.NewGCM(block)           // 默认 12-byte nonce, 16-byte tag
nonce := make([]byte, aead.NonceSize())
rand.Read(nonce)                          // MUST 每次唯一:随机 or 单调计数器
ct := aead.Seal(nil, nonce, plaintext, aad) // ct 末尾自带 16 字节 tag
// 传输:nonce || ct(含 tag)

// 解密:Open() 内部先验 tag,失败直接返回 error,不吐半字节明文
pt, err := aead.Open(nil, nonce, ct, aad)

要点:tag 拼在密文尾部、Open 先验后拆——tag 不对直接失败,绝不返回"半解密"的中间结果。这是 AEAD 相对"CBC + HMAC 手拼"的关键防御边界(后面 HMAC 章节会讲为什么)。

Nonce 复用 = 密钥泄漏

"同一密钥下 Nonce 重复"在 GCM 里是灾难:两条消息用同一段密钥流 XOR 加密 → 两条密文相 XOR 就消掉密钥流,还原明文异或;更糟的是 GHASH 的认证密钥也会泄露,攻击者可任意伪造 tag。所以 Nonce 要么用真随机(96-bit 空间足够)、要么用单调计数器,同一密钥下绝不复用。


二、非对称加密:公开挂锁 + 私钥

一分钟大白话

  • 是什么:加密解密用一对钥匙(公钥+私钥),方向相反。公钥可以贴到全世界看,只有私钥能解开。
  • 什么时候诞生:1976 年 Diffie-Hellman 论文提出概念,1977 年 RSA 算法给出首个实用实现。这是密码学历史上最重要的突破——之前根本没人相信"陌生人在公开信道能保密通信"。
  • 解决什么问题:"和陌生人首次交换密钥"——终于不用先见面对暗号了。也顺便解决了"证明消息真的是你发的"(数字签名)。
  • 典型场景:TLS 握手协商对称密钥、SSH 免密登录、比特币钱包地址、JWT RS256 签名。
  • 最大死穴:慢。RSA-2048 加密单核每秒才几千次,AES-GCM 每秒 GB 级——所以现实中永远是"非对称谈钥匙 + 对称跑数据"。

一分钟大白话

  • 是什么:加密解密用一对钥匙(公钥+私钥),方向相反。公钥可以贴到全世界看,只有私钥能解开。
  • 什么时候诞生:1976 年 Diffie-Hellman 论文提出概念,1977 年 RSA 算法给出首个实用实现。这是密码学历史上最重要的突破——之前根本没人相信"陌生人在公开信道能保密通信"。
  • 解决什么问题:"和陌生人首次交换密钥"——终于不用先见面对暗号了。也顺便解决了"证明消息真的是你发的"(数字签名)。
  • 典型场景:TLS 握手协商对称密钥、SSH 免密登录、比特币钱包地址、JWT RS256 签名。
  • 最大死穴:慢。RSA-2048 加密单核每秒才几千次,AES-GCM 每秒 GB 级——所以现实中永远是"非对称谈钥匙 + 对称跑数据"。

故事场景

回到刚才的坑:Alice 和 Bob 想用对称加密聊天,但他们没见过面——怎么把对称密钥安全地告诉对方?直接寄过去中间人一截获就完蛋。

Diffie 和 Hellman 1976 年提出了神奇的方案:Bob 造一把"任何人都能锁上、只有 Bob 能打开"的挂锁,把挂锁的图纸公开挂在网上。Alice 想聊天时:

  1. 下载 Bob 的挂锁图纸(公钥)
  2. 用挂锁把自己生成的对称密钥锁进一个箱子(加密)
  3. 把箱子寄给 Bob

中间人截获箱子——没用,因为只有 Bob 的钥匙(私钥)能开。

直觉画面:RSA 加密

最大的坑:加密和签名的"钥匙方向相反"

RSA 有两个用途,钥匙方向正好相反——这是面试最爱考的一点。

加密(保密)目标:只有一个人能读Alice发送方Bob接收方加密后的消息用 Bob 公钥加密 🔒用 Bob 私钥解密 🔑发送方用「接收方公钥」加密签名(认证)目标:证明只有一个人能写Alice签名方Bob验证方消息 + 签名用 Alice 私钥签 ✍用 Alice 公钥验 ✓签名方用「自己私钥」签

记忆口诀:

  • 加密:"公锁私开" —— 谁想给我发密文,就用我的公钥(公开的锁)加密;我用自己的私钥(唯一的钥匙)打开。
  • 签名:"私签公验" —— 我要证明这消息是我发的,就用我的私钥签名;任何人拿我的公钥都能验证。

想想这个对称性为什么合理:

  • 加密目标是 "只有一人能读" → 用只有一人有的东西解密 → 用私钥解
  • 签名目标是 "只有一人能写" → 用只有一人有的东西加密 → 用私钥签

术语与家族

算法密钥长度用途特点
RSA-2048/3072长(256/384 字节)加密、签名老牌,兼容性最好,签名慢
ECDSA (P-256)短(32 字节)签名快、短,但依赖每次签名的随机数 k——k 复用/可泄露 = 私钥泄漏(Sony PS3 就栽在这里)
Ed25519短(32 字节)签名快、短、确定性签名(无 k 随机数依赖)、无坏曲线陷阱——现代首选
X25519短(32 字节)密钥交换(ECDH)TLS 1.3 默认

为什么非对称"只用于握手":性能差距太大。AES-GCM 单核吞吐 GB/s,RSA-2048 签名单核每秒几千次——差 5 个数量级。全程非对称直接不可用。所以工程上永远是:非对称谈钥匙 + 对称跑数据。


三、哈希:单向指纹

一分钟大白话

  • 是什么:把任意长度的输入压成固定长度的"指纹"(比如 SHA-256 永远输出 32 字节),且不可逆。
  • 什么时候诞生:MD5 1992 年、SHA-1 1993 年、SHA-2 家族 2001 年、SHA-3 2015 年。MD5 和 SHA-1 都已被攻破,现在不能用。
  • 解决什么问题:"验证数据有没有被动过"——你我各算一遍,指纹一样就说明没变。注意:哈希不是加密——它是单向的,无法还原。
  • 典型场景:文件完整性校验(下载 ISO 后核对哈希)、Git 提交寻址、区块链、密码存储(配慢哈希)、密钥派生(配 HKDF)。
  • 最大陷阱:新手最容易把"用 SHA-256 加密"挂嘴上——哈希不加密任何东西,只是产生指纹。想加密请回第一或第二节。

一分钟大白话

  • 是什么:把任意长度的输入压成固定长度的"指纹"(比如 SHA-256 永远输出 32 字节),且不可逆。
  • 什么时候诞生:MD5 1992 年、SHA-1 1993 年、SHA-2 家族 2001 年、SHA-3 2015 年。MD5 和 SHA-1 都已被攻破,现在不能用。
  • 解决什么问题:"验证数据有没有被动过"——你我各算一遍,指纹一样就说明没变。注意:哈希不是加密——它是单向的,无法还原。
  • 典型场景:文件完整性校验(下载 ISO 后核对哈希)、Git 提交寻址、区块链、密码存储(配慢哈希)、密钥派生(配 HKDF)。
  • 最大陷阱:新手最容易把"用 SHA-256 加密"挂嘴上——哈希不加密任何东西,只是产生指纹。想加密请回第一或第二节。

故事场景

你从网上下载了一个 5GB 的镜像文件。怎么确认下载过程中没有被中间人替换?镜像的官方页面上贴了一段 64 位十六进制字符串——那就是哈希值。你本地也算一遍:一样,说明下载完整;不一样,说明有变化(无论是无意损坏还是恶意替换)。

哈希函数满足两个特性:

  • 单向:给你哈希值 h,反推原始数据 m 计算不可行(不可能用 SHA-256 值倒推出源镜像)。
  • 雪崩:输入变一个 bit,输出完全变样(一半以上的 bit 翻转)。

直觉画面

无论输入 11 字节还是 5GB,输出都是固定 32 字节。输入哪怕改一个字符,输出完全不一样。

术语与家族

  • SHA-2(SHA-256/384/512):2001 年至今主力,未被攻破。
  • SHA-3(Keccak):2015 年标准化,用了完全不同的"海绵结构",抗性更强,但性能通常不如 SHA-2。
  • BLAKE2 / BLAKE3:速度极快,工程界流行,非官方标准。
  • MD5 / SHA-1:已弃用。MD5 (2004 年被攻破,秒级构造碰撞)、SHA-1 (2017 年 Google SHAttered 攻破)。仅限非安全场景(如 Git 对象寻址、CDN 缓存 key)。

三性质:能防什么、不能防什么

性质攻击者能力一句话
抗原像(preimage)给 h,找不到 m 使 H(m)=h"反推明文" 做不到
抗第二原像(2nd preimage)给 m1,找不到 m2 ≠ m1 使 H(m1)=H(m2)"改造已有消息使摘要不变" 做不到
抗碰撞(collision)找不到任何 m1 ≠ m2 使 H(m1)=H(m2)"预制两份摘要相同的消息" 做不到

抗碰撞是最强要求,MD5/SHA-1 就栽在这里。

红线:哈希 ≠ MAC(下一节详细讲)

一个新手最容易犯的错:"用 SHA-256 加密"。哈希不是加密——它是单向的、无法还原。哈希只能做完整性校验(你我都算一遍看结果一样不一样),不能防主动攻击——攻击者拿到消息可以自己重算一份哈希,让你以为没被改。要防主动攻击,得让哈希"知道秘密"——那就是 HMAC 或数字签名。


四、HMAC:给哈希加一把密钥

一分钟大白话

  • 是什么:"哈希 + 密钥" 的组合,得到一个只有知道密钥的人才能算出的"带密码的指纹"。
  • 什么时候诞生:1996 年 Bellare-Canetti-Krawczyk 论文提出,1997 年成为 RFC 2104。
  • 解决什么问题:"让接收方能验证消息真的是你发的、路上没被改过"。哈希只能验证"没被无意改坏"(谁都能重算),HMAC 加了密钥后,攻击者不知道密钥就伪造不出合法 HMAC。
  • 典型场景:API 计费签名(腾讯米大师、AWS S3 请求签名)、JWT HS256、内网服务鉴权、Cookie 防篡改(Django SECRET_KEY)。
  • 为什么不直接 SHA256(密钥+消息):SHA-2 有长度扩展攻击漏洞,攻击者不知道密钥也能伪造合法输出。HMAC 的双层结构就是为了封住这个洞。

一分钟大白话

  • 是什么:"哈希 + 密钥" 的组合,得到一个只有知道密钥的人才能算出的"带密码的指纹"。
  • 什么时候诞生:1996 年 Bellare-Canetti-Krawczyk 论文提出,1997 年成为 RFC 2104。
  • 解决什么问题:"让接收方能验证消息真的是你发的、路上没被改过"。哈希只能验证"没被无意改坏"(谁都能重算),HMAC 加了密钥后,攻击者不知道密钥就伪造不出合法 HMAC。
  • 典型场景:API 计费签名(腾讯米大师、AWS S3 请求签名)、JWT HS256、内网服务鉴权、Cookie 防篡改(Django SECRET_KEY)。
  • 为什么不直接 SHA256(密钥+消息):SHA-2 有长度扩展攻击漏洞,攻击者不知道密钥也能伪造合法输出。HMAC 的双层结构就是为了封住这个洞。

故事场景

内网服务 A 调用服务 B 的计费 API,怎么让 B 确认"这个请求真的是 A 发的、路上没被改过"?

方案一(错):让请求带一个 signature = SHA256(secret + body)——A 和 B 共享 secret。B 收到后自己算一遍对得上就通过。这有致命漏洞:SHA-2 存在长度扩展攻击——攻击者拿到 SHA256(secret + body) 输出,即使不知道 secret,也能算出 SHA256(secret + body + padding + evil_data),让 B 以为是合法扩展。

方案二(对):用 HMAC(Hash-based Message Authentication Code),双层哈希封住这个漏洞。

直觉画面:HMAC 的双层结构

公式:HMAC(K, m) = SHA256( (K ⊕ opad) || SHA256( (K ⊕ ipad) || m ) )

  • ipad = 0x36 重复到密钥长度("inner padding")
  • opad = 0x5C 重复到密钥长度("outer padding")
  • ⊕ 是异或

为什么这个双层结构能抵御长度扩展:攻击者拿到的是外层哈希的输出,看不到内层哈希的状态——就无法从"外层输出"扩展一个消息使 HMAC 依然合法。SHA-3 因用海绵结构天然免疫,但工程界仍统一走 HMAC 保持算法族无关。

术语与选型

算法一句话
HMAC-SHA256事实标准,性能好,安全
HMAC-SHA512更慢但没什么额外收益,除非有特殊合规要求
HMAC-SHA1弃用(SHA-1 本身已破,虽然 HMAC-SHA1 目前仍算安全,但没人会为它买单)
Poly1305一次性 MAC(每条消息需一次性密钥);组合 ChaCha20 得 ChaCha20-Poly1305 AEAD

Go API 示例:

import "crypto/hmac"
import "crypto/sha256"

mac := hmac.New(sha256.New, sharedSecret)
mac.Write([]byte(message))
sig := mac.Sum(nil) // 32 字节的认证码

// 验证时(重要!用 hmac.Equal,防时序侧信道)
ok := hmac.Equal(receivedSig, sig)

验证 MAC 要用「常时间比较」

bytes.Equal(a, b) 会在第一个不同字节就返回,攻击者可以通过测量响应时间逐字节猜出签名。hmac.Equal 保证无论哪里不同都花同样时间。


五、AEAD 内部:为什么"先加密再 MAC"

一分钟大白话

  • 是什么:AEAD(Authenticated Encryption with Associated Data,带关联数据的认证加密)= 对称加密 + 消息认证一体化。一个函数同时做完"加密、防篡改、抗伪造"三件事。
  • 什么时候诞生:概念 2002 年学界提出,AES-GCM 2007 年 NIST 标准化,ChaCha20-Poly1305 2013 年 Google 部署,2018 年 TLS 1.3 只允许 AEAD 套件。
  • 解决什么问题:"手拼加密+MAC 太容易写错"——TLS 1.2 时代 BEAST/Lucky13/POODLE 一堆漏洞都是"顺序拼错"导致的,AEAD 把顺序封装在一个函数里,用户不可能拼错。
  • 典型场景:TLS 1.3 应用数据、QUIC、Signal 消息加密、SSH 现代套件、AWS S3 SSE-C 客户端加密。
  • 一句话选型:有 AES-NI 硬件加速时选 AES-256-GCM,移动端/无硬件时选 ChaCha20-Poly1305。

上面讲了对称、HMAC,现在回头看 AEAD 为什么"一步到位"更安全。要 既加密又认证,历史上有三种拼接顺序,只有一种真正安全:

顺序别名是否安全代表
Encrypt-then-MACEtM✅ 通用安全TLS 1.3、SSH(部分)、AEAD 都是这种
MAC-then-EncryptMtE⚠️ 历史多次翻车TLS 1.2 CBC 套件(BEAST / Lucky13 攻击)
Encrypt-and-MACE&M⚠️ MAC 会泄露明文信息早期 SSH

为什么 EtM 最优:解密方拿到消息后先验 MAC 再解密。MAC 不对直接丢弃——攻击者无法通过"送畸形密文观察解密行为"来构造侧信道。这是历史上 Padding Oracle 攻击(BEAST/Lucky13/POODLE)的成因:它们都发生在"手拼 MtE"上,因为解密方要先解密到 padding 再验证。

AEAD 就是把 EtM 一体化封装:AES-GCM 内部结构 = CTR 模式加密 + GMAC 认证 + 一起产 tag。你只调 Seal/Open,不可能拼错。


六、数字签名:只有一个人能写

一分钟大白话

  • 是什么:签名方用私钥对消息生成一段"签名字节",任何拿到公钥的人都能验证"这消息真是他写的、没被改过"。
  • 什么时候诞生:概念随 RSA 1977 年诞生,DSA 1994 年标准化,ECDSA 2005 年,Ed25519 2012 年。
  • 解决什么问题:"证明消息来源 + 无法抵赖"——HMAC 双方共享密钥,收方也能签所以可抵赖;数字签名只有私钥持有者能签,事后签名方无法否认。
  • 典型场景:TLS 证书(CA 用私钥签服务器证书)、代码签名(Windows 驱动、iOS 应用、Docker 镜像)、JWT RS256/ES256、区块链交易、Git commit 签名(git commit -S)、软件更新包(APT/Homebrew)。
  • 和 HMAC 的关键差别:HMAC 快但双方共享密钥;签名慢但公钥可公开,适合"一方签、多方验"场景。

一分钟大白话

  • 是什么:签名方用私钥对消息生成一段"签名字节",任何拿到公钥的人都能验证"这消息真是他写的、没被改过"。
  • 什么时候诞生:概念随 RSA 1977 年诞生,DSA 1994 年标准化,ECDSA 2005 年,Ed25519 2012 年。
  • 解决什么问题:"证明消息来源 + 无法抵赖"——HMAC 双方共享密钥,收方也能签所以可抵赖;数字签名只有私钥持有者能签,事后签名方无法否认。
  • 典型场景:TLS 证书(CA 用私钥签服务器证书)、代码签名(Windows 驱动、iOS 应用、Docker 镜像)、JWT RS256/ES256、区块链交易、Git commit 签名(git commit -S)、软件更新包(APT/Homebrew)。
  • 和 HMAC 的关键差别:HMAC 快但双方共享密钥;签名慢但公钥可公开,适合"一方签、多方验"场景。

故事场景

Alice 要发一个软件更新包给全世界的用户。用户怎么确认"这真的是 Alice 发的,不是黑客伪造"?

用 HMAC 不行——HMAC 需要双方共享密钥,Alice 不可能给每个用户都发一份共享密钥(发出去就等于所有用户都能伪造 Alice 的签名)。

用数字签名:

  1. Alice 私钥签一次
  2. 公钥挂在官网人人可下载
  3. 任何用户拿公钥验签

关键差别:签名做到了 HMAC 做不到的一件事——不可抵赖。因为签名需要私钥,只有 Alice 有;HMAC 双方共享密钥,收方也能签,所以事后 Alice 可以说"这不是我签的"。

直觉画面

签名≠加密

数字签名不加密内容——消息还是明文传输,签名只是附加的一段可验证字节。想要保密还得叠加密(如 AEAD + 签名分层,或直接走 TLS)。

术语与选型

算法密钥长度特点
RSA-PSS2048/3072 bit兼容老系统;PSS padding 有安全证明(PKCS#1 v1.5 已不推荐)
ECDSA (P-256/384)256/384 bit短、快;依赖每次签名随机 k,k 泄露/复用 = 私钥泄漏(Sony PS3 案)
Ed25519256 bit短、快、确定性签名(无 k 随机数依赖)、无坏曲线陷阱——现代默认

HMAC vs 数字签名:什么时候用哪个

维度HMAC数字签名
密钥形态双方共享同一把对称密钥私钥(保密)+ 公钥(公开)
信任模型双方互信(任一方泄露即失效)单向验证(验证方只需公钥)
可抵赖性✅ 可抵赖(收方也能签,事后可否认)❌ 不可抵赖(只有私钥持有者能签)
密钥分发需安全信道预共享公钥可任意公开(配 CA 更好)
性能极快慢 1-2 个数量级
典型场景内网服务鉴权、API 计费签名JWT RS256/ES256、TLS 证书、代码签名

七、密钥交换:ECDHE 与前向安全

一分钟大白话

  • 是什么:让两个陌生人在公开信道上协商出一个共享秘密,中间人虽然看得到所有消息,但算不出这个秘密。
  • 什么时候诞生:1976 年 Diffie & Hellman 论文(DH),1985 年 Miller & Koblitz 提出椭圆曲线版(ECDH),1990s ECDHE 加入 Ephemeral 概念,2018 年 TLS 1.3 只保留 ECDHE。
  • 解决什么问题:"陌生人怎么协商对称密钥"——这是密码学史上最重要的问题。DH 之前根本没人相信这能做到。
  • 典型场景:TLS 握手(每次连接协商会话密钥)、Signal 端对端加密、WireGuard VPN、SSH 密钥协商。
  • 前向安全(PFS):ECDHE 里的 E = Ephemeral(临时) —— 每次会话生成一次性密钥对,用完销毁。长期私钥泄露时,攻击者也解不了历史流量(因为临时密钥早已销毁)。RSA 密钥交换没有这个特性,是 TLS 1.3 直接砍掉它的原因。

一分钟大白话

  • 是什么:让两个陌生人在公开信道上协商出一个共享秘密,中间人虽然看得到所有消息,但算不出这个秘密。
  • 什么时候诞生:1976 年 Diffie & Hellman 论文(DH),1985 年 Miller & Koblitz 提出椭圆曲线版(ECDH),1990s ECDHE 加入 Ephemeral 概念,2018 年 TLS 1.3 只保留 ECDHE。
  • 解决什么问题:"陌生人怎么协商对称密钥"——这是密码学史上最重要的问题。DH 之前根本没人相信这能做到。
  • 典型场景:TLS 握手(每次连接协商会话密钥)、Signal 端对端加密、WireGuard VPN、SSH 密钥协商。
  • 前向安全(PFS):ECDHE 里的 E = Ephemeral(临时) —— 每次会话生成一次性密钥对,用完销毁。长期私钥泄露时,攻击者也解不了历史流量(因为临时密钥早已销毁)。RSA 密钥交换没有这个特性,是 TLS 1.3 直接砍掉它的原因。

故事场景

回到最开头 Alice 和 Bob 的问题:他们没见过面,怎么协商出一个共享的对称密钥?

RSA 方式:Bob 挂公钥,Alice 用它加密对称密钥发过去。有个隐患:如果 Bob 的私钥明年被偷了,攻击者去年录下的整箱流量今年全部可解(因为对称密钥当时就是用 Bob 那把私钥的公钥加密的)。

Diffie-Hellman 密钥交换的神奇之处:让 Alice 和 Bob 通过公开信道算出一个共享秘密,中间人虽然全程能看到通信,但算不出这个秘密。

直觉画面:ECDHE 一次会话

为什么中间人算不出:给你 a·G,反推 a 需要解"椭圆曲线离散对数难题"——目前没有已知的多项式时间算法(除了量子计算机,见文末 PQC 章节)。

术语解释:ECDHE 里的每个字母

  • DH = Diffie-Hellman,1976 年的原始算法
  • EC = Elliptic Curve(椭圆曲线),把整数域换成椭圆曲线群,密钥更短更快
  • E = Ephemeral(临时)——每次会话生成一次性密钥对,用完即弃

这个 E 就是前向安全的关键:

这就是 TLS 1.3 直接砍掉 RSA 密钥交换、只保留 ECDHE 的原因。


八、KDF:从秘密派生子密钥

一分钟大白话

  • 是什么:Key Derivation Function(密钥派生函数)——把一个"秘密"变成一把或多把"密钥"。
  • 两种截然不同的 KDF:
    1. 快 KDF(HKDF):追求快、追求安全域分离。从 ECDH 共享秘密派生 TLS 会话密钥就是它。
    2. 慢 KDF(Argon2id / bcrypt / scrypt / PBKDF2):追求故意慢。用于存密码——让攻击者拖库后爆破一个密码要花几十年。
  • 什么时候诞生:PBKDF2 2000 年、bcrypt 1999 年、HKDF 2010 年 RFC 5869、scrypt 2009 年、Argon2 2015 年(PHC 冠军)。
  • 典型场景:
    • HKDF:TLS 1.3 会话密钥派生、Signal 协议、Noise 协议
    • Argon2id:用户密码存 DB、加密卷密码派生密钥
  • 红线:存密码永远用慢 KDF + 加盐——直接用 SHA-256 存密码 = 拖库后分钟级被破。

一分钟大白话

  • 是什么:Key Derivation Function(密钥派生函数)——把一个"秘密"变成一把或多把"密钥"。
  • 两种截然不同的 KDF:
    1. 快 KDF(HKDF):追求快、追求安全域分离。从 ECDH 共享秘密派生 TLS 会话密钥就是它。
    2. 慢 KDF(Argon2id / bcrypt / scrypt / PBKDF2):追求故意慢。用于存密码——让攻击者拖库后爆破一个密码要花几十年。
  • 什么时候诞生:PBKDF2 2000 年、bcrypt 1999 年、HKDF 2010 年 RFC 5869、scrypt 2009 年、Argon2 2015 年(PHC 冠军)。
  • 典型场景:
    • HKDF:TLS 1.3 会话密钥派生、Signal 协议、Noise 协议
    • Argon2id:用户密码存 DB、加密卷密码派生密钥
  • 红线:存密码永远用慢 KDF + 加盐——直接用 SHA-256 存密码 = 拖库后分钟级被破。

故事场景

Alice 和 Bob 通过 ECDHE 算出了一段共享秘密 S(比如 32 字节)。他们需要:一把加密密钥、一把 MAC 密钥、一个初始 IV……能不能直接把 S 切成几段用?

不能。原因:

  1. S 是椭圆曲线上一个点的 x 坐标,分布不均匀(不是均匀随机的),直接切片会让某些子密钥有偏。
  2. 不同用途密钥必须"域分离"——否则会有跨用途的攻击(拿加密密钥当 MAC 密钥用能出事)。

HKDF(HMAC-based Key Derivation Function,RFC 5869)就是干这个的:

直觉画面:HKDF 两步

  • Extract:PRK = HMAC(salt, S)——把熵不均匀的输入压成均匀伪随机
  • Expand:OKM = HMAC(PRK, info || counter)——按需展开任意长子密钥,info 做域分离

TLS 1.3、Noise 协议、Signal 协议全部用 HKDF。为什么不直接 SHA-256 派生:SHA-256 不是 PRF,直接切片没有形式化安全证明;HKDF 有 IETF 标准证明。

密码存储:为什么要"故意慢"

普通 KDF 追求快,密码存储的 KDF 反着来——故意慢,才能抗离线爆破。

场景:DB 被拖了,攻击者拿到一堆 (用户名, 密码哈希)。他想还原你的密码,只能一个一个试:

  • 如果你的密码哈希是 SHA-256(password):GPU 集群每秒能试数百亿个密码,一个 8 位密码几分钟就爆掉
  • 如果是 Argon2id(password, salt, mem=64MB, iter=3):GPU 每次尝试要花 100ms + 64MB 显存,同样 8 位密码要几十年
算法内存硬状态
PBKDF2❌老,只能调迭代次数;FIPS 白名单
bcrypt❌72 字节输入截断坑;曾是默认
scrypt✅时间 + 内存
Argon2id✅2015 PHC 冠军,当前推荐默认

必配加盐:每个密码一份随机 salt,与哈希一起存 DB 明文即可。salt 不是"秘密",它的作用是让并行爆破无法共享预计算表——每个密码都得单独跑一遍完整的 Argon2。


九、TLS 1.3 握手:把上面所有东西串起来

一分钟大白话

  • 是什么:TLS(Transport Layer Security)是 HTTPS 的底层加密协议。你打开一个 https:// 页面,浏览器和服务器就在你不知情时做完了 TLS 握手。
  • 演进史:SSL 2.0 (1995) → SSL 3.0 (1996) → TLS 1.0 (1999) → TLS 1.1 (2006) → TLS 1.2 (2008) → TLS 1.3 (2018)。SSL 早年漏洞百出,已被彻底淘汰;TLS 1.0/1.1 也在 2020 年被主流浏览器禁用。现在只用 TLS 1.2/1.3。
  • 一次握手做了三件事:
    1. 协商密钥(ECDHE):算出一个只有你俩知道的对称密钥。
    2. 认证服务器(证书 + 签名):确认对方真的是 google.com,不是黑客冒充。
    3. 派生密钥(HKDF):把共享秘密变成加密密钥 + MAC 密钥。
  • 握手完之后:所有应用数据走 AEAD(AES-GCM 或 ChaCha20-Poly1305),高速对称加密。
  • 1.3 相对 1.2 的四大改进:只留 5 个套件(全 AEAD + PFS)、1-RTT 握手(比 1.2 少一轮)、握手全加密、签名与密钥交换解耦。

一分钟大白话

  • 是什么:TLS(Transport Layer Security)是 HTTPS 的底层加密协议。你打开一个 https:// 页面,浏览器和服务器就在你不知情时做完了 TLS 握手。
  • 演进史:SSL 2.0 (1995) → SSL 3.0 (1996) → TLS 1.0 (1999) → TLS 1.1 (2006) → TLS 1.2 (2008) → TLS 1.3 (2018)。SSL 早年漏洞百出,已被彻底淘汰;TLS 1.0/1.1 也在 2020 年被主流浏览器禁用。现在只用 TLS 1.2/1.3。
  • 一次握手做了三件事:
    1. 协商密钥(ECDHE):算出一个只有你俩知道的对称密钥。
    2. 认证服务器(证书 + 签名):确认对方真的是 google.com,不是黑客冒充。
    3. 派生密钥(HKDF):把共享秘密变成加密密钥 + MAC 密钥。
  • 握手完之后:所有应用数据走 AEAD(AES-GCM 或 ChaCha20-Poly1305),高速对称加密。
  • 1.3 相对 1.2 的四大改进:只留 5 个套件(全 AEAD + PFS)、1-RTT 握手(比 1.2 少一轮)、握手全加密、签名与密钥交换解耦。

这是最能看清"原语如何组合成协议"的地方。看完这张时序图,前面每一节都会瞬间连起来:

每个原语在做什么(对照前面各节):

  • ECDHE(第七节):交换密钥份额,双方各自算共享秘密 g^(ab) = 会话根秘密
  • HKDF(第八节):从根秘密派生一堆子密钥
  • 服务器证书 + CA 签名链(第六节 + 下面证书章):证明"这个公钥属于该域名"
  • CertificateVerify(第六节):服务器用证书私钥签整个握手记录 → 防中间人拿别人证书冒充
  • Finished(第四节):用 HKDF 派生的 MAC 密钥做 HMAC → 校验握手过程未被篡改
  • Application Data(第一节):AEAD 加密应用数据

TLS 1.2 → 1.3 的四条大改动:

  1. 只留 5 个套件:全部 AEAD + PFS,砍掉 RSA 交换、CBC、RC4、MD5、SHA-1、导出级弱套件。
  2. 1-RTT 握手(1.2 是 2-RTT):ClientHello 直接带上密钥份额;支持 0-RTT 恢复(但有重放风险,不能用于非幂等)。
  3. 握手全加密:ServerHello 之后所有帧(含证书)都在派生密钥下加密。
  4. 签名与密钥交换解耦:1.2 密码套件同时选中三者组合爆炸,1.3 拆开分别协商。

十、JWT / mTLS / 证书链

一分钟大白话

  • JWT(JSON Web Token):自包含的签名令牌——一段编码后的 JSON + 签名字节,携带用户身份和权限信息。服务端不用查 DB,验签就知道是不是合法用户。典型场景:SSO 单点登录、微服务鉴权、API 访问令牌。注意:JWT payload 不加密,只签名——想保密请走 HTTPS。
  • mTLS(mutual TLS):双向 TLS——普通 TLS 只验证服务器,mTLS 客户端也提交证书,服务端也验证。典型场景:服务网格内部通信(Istio 用 SPIFFE ID 给每个服务发短期证书)、B2B API、金融专线。是"零信任内网"的技术底座。
  • 证书链:一张 example.com 的证书是中间 CA 签的,中间 CA 是根 CA 签的,根 CA 预装在你的操作系统里。这套逐级验签就叫证书链(也叫 PKI 公钥基础设施)。CA 可以吊销证书,通过 CRL / OCSP / OCSP Stapling 三种机制通告吊销状态。
  • HS256 vs RS256 一句话:内网选 HS256(HMAC,快),对外选 RS256/ES256(数字签名,公钥可公开)。永远拒绝 alg: none。

JWT:一张自包含的签名令牌

Base64URL(header) . Base64URL(payload) . Base64URL(signature)
      alg=RS256          {sub:123,...}       RSA/HMAC 签名

payload 不加密——只是签名保完整性。需要保密请套 JWE 或走 HTTPS。

HS256 vs RS256 选型:

场景选理由
内网服务间 / 单发单验HS256(HMAC-SHA256)一把共享密钥,简单快;但验签方能签,不适合多方
对外 API / SSO / 多方验签RS256 或 ES256私钥只在签发方;公钥可通过 JWKS 公开分发

JWT 两大历史坑

  • alg: none 攻击:早期库若信任 header 里的 alg 字段,攻击者把 alg 改成 none 就能提交无签名 token。修复:服务端 hard-code 期望算法。
  • 算法降级:把 alg: RS256 改成 alg: HS256,用服务器公钥当 HMAC 密钥重签——某些库会把公钥误当共享密钥验签成功。修复:验证前显式绑定算法族。

mTLS:双向 TLS

普通 TLS:只验证服务器证书(防冒充服务器),客户端身份靠应用层认证(用户名密码 / API Key)。

mTLS:客户端也提交证书 → 服务端也验证——双向证书身份。典型场景:

  • 服务网格内部身份(Istio / Linkerd 用 SPIFFE ID)——每个服务发一张短期证书,服务间调用全量 mTLS,是"零信任内网"的技术底座
  • B2B API
  • 金融专线

证书链:谁给谁背书

客户端验证:从叶子证书往上一层层验签,一直验到"内置的可信根证书"停。任何一层验签失败 = 证书不可信。

证书吊销机制(如果 CA 想中途取消一张证书):

机制谁维护谁去查代价
CRL(吊销列表)CA 定期发布全量列表客户端下载列表大、更新滞后
OCSPCA 维护实时状态客户端 → OCSP 服务器实时查询增加 RTT + 隐私泄露(CA 知道你访问了谁)
OCSP StaplingCA 签发 OCSP 应答服务器代查、握手时把应答附上无隐私泄露、无额外 RTT,事实标准

SSH host key 的 TOFU(Trust-On-First-Use)反例:SSH 没有 CA,客户端"第一次连接时把 host key 记下来",后续变了就报警。适用于无中心信任锚的场景,代价是"首次连接必须可信信道"。

为什么这么做

  • 为什么工程上永远是"非对称谈钥匙 + 对称跑数据":性能差 5 个数量级,全程非对称不可用;纯对称又无法在不安全信道首次交换密钥。混合方案取两者之长,是唯一工程可行的组合。
  • 为什么 AEAD 淘汰"CBC + HMAC 手拼":AEAD 把 EtM 一体化封装,杜绝"实现顺序错 / 侧信道 / 中间态泄露"三类历史事故(Padding Oracle、BEAST、Lucky13 都发生在"手拼"上)。TLS 1.3 只留 AEAD 是必然。
  • 为什么 HMAC 不是"哈希 + 密钥拼接":MD5/SHA-1/SHA-256 的 Merkle-Damgård 结构存在长度扩展攻击。HMAC 的双层结构让内外哈希独立、抵御扩展,且有形式化证明。
  • 为什么现代默认 ECDHE 而非 RSA 密钥交换:ECDHE 提供前向安全——今天服务器被攻破偷走私钥,无法反解历史流量;RSA 密钥交换则历史抓包整箱可解。
  • 为什么密码存储要慢哈希 + 加盐:SHA-256 单核每秒数亿次,拖库后 GPU 集群离线爆破仅需分钟级;Argon2id 故意消耗 100ms + 数十 MB 内存,把爆破成本抬到 GPU 都跑不动。加盐让每条哈希不能共享预计算表。
  • 为什么 JWT 用 RS256 更适合对外 SSO:私钥只在身份提供方(IdP),所有依赖方(Relying Party)用公钥验签——不需要每加一个业务方就派发密钥,运维大幅简化。
  • 为什么 TLS 1.3 只留 5 个密码套件:过去"配置一堆套件、按需协商"给了降级攻击(POODLE / FREAK / Logjam)可乘之机。TLS 1.3 直接把不安全套件从标准里删干净,实现无从"协商到弱套件"。

为什么别的选择不行

  • 为什么不干脆全程用非对称加密(省得混合):性能差 5 个数量级——AES-GCM 单核 GB/s,RSA-2048 单核每秒几千次;1MB 数据 RSA 加密数百 ms,全程非对称 = TLS 卡死。且 RSA 一次最多加密(密钥长度 / 8)字节,本身不适合传大数据。
  • 为什么不用"哈希 + 密钥拼接"自制 MAC:对 SHA-1/2 存在长度扩展攻击——攻击者不知道 secret 也能扩展你的消息并算出合法 MAC。历史上出过 Flickr API 签名漏洞(2009)。正解是 HMAC。
  • 为什么内部服务用 HS256、不图省事全上 RS256:RS256 验签比 HMAC 慢一个数量级——内网每秒万级 QPS 的鉴权累积可观 CPU;且内部服务共享密钥可控,HMAC 的"共享密钥缺陷"在信任域内是可接受权衡。
  • 为什么密码不用 AES 加密存 DB、要用慢哈希:一是"加密"意味着可解密——DB 泄露 = 密码全泄;二是密码本身不需要还原,只需"验证一致",单向哈希才是对的语义。可逆加密只用于确实需要还原的字段(如银行卡号)。
  • 为什么不继续用 MD5 做安全场景:签名/证书/密码存储零容忍已破算法;非对抗性寻址场景(Git 内部指针、CDN 缓存 key)碰撞代价可控,暂未强制升级。
  • 为什么不给 JWT 加密(JWE)替代签名:签名的目标是可公开验证,加密的目标是保密——两者正交。签名 + HTTPS 已同时提供完整性与传输保密性;JWE 只在"传输后仍需保密载荷"(如票据经不可信中转)时才用。且 JWE 复杂度高、库实现质量参差。

沉淀结论

三张辨析对照表(面试速答模板)

表 1 · RSA 加密 vs RSA 签名

维度RSA 加密RSA 签名
发送方(做动作)用接收方公钥用自己私钥
接收方(验/解)用自己私钥用发送方公钥
目的保密(只有私钥拥有者能读)认证 + 不可抵赖
典型 API(Go)rsa.EncryptOAEP / rsa.DecryptOAEPrsa.SignPSS / rsa.VerifyPSS
记忆公锁私开私签公验

表 2 · HMAC vs 数字签名

维度HMAC数字签名
密钥形态双方共享同一把对称密钥私钥 + 公钥
信任模型双方互信(谁都能签、谁都能验)单向(只有签名方能签,验证方只需公钥)
可抵赖✅ 可抵赖(接收方也能伪造)❌ 不可抵赖(除私钥持有者外无人能签)
密钥分发需安全信道预共享公钥可任意公开(配 PKI 更好)
性能极快慢 1-2 个数量级
典型场景内网服务鉴权、API 计费签名(见 business-proxy)JWT RS256/ES256、TLS 证书、代码签名

表 3 · 哈希 vs MAC vs 签名 能力矩阵

能力哈希(SHA-256)MAC(HMAC / AEAD tag)数字签名
抗篡改(无意/传输错误)✅✅✅
抗伪造(攻击者无密钥不能改)❌(谁都能重算)✅(需密钥)✅(需私钥)
不可抵赖(签名方无法否认)❌❌(收方也能签)✅(只有私钥持有者能签)

面试快速反应表(场景 → 原语选择)

场景首选备选一句话理由
内网服务鉴权 / API 计费签名HMAC-SHA256HMAC-SHA512对称高性能、双方互信可接受
对外公开 API 令牌 / SSO 断言RS256 或 ES256Ed25519(EdDSA)一方签、多方验、公钥可公开
客户端密码存 DBArgon2idbcrypt / scrypt慢 + 加盐 + 抗 GPU
传输机密 + 完整性一体AES-256-GCMChaCha20-Poly1305(移动/无硬件加速)AEAD 一步到位
文件完整性校验 / Git-style 寻址SHA-256BLAKE3(追求速度时)只需检测无意损坏
密钥派生(从共享秘密派生多子密钥)HKDF-SHA256HKDF-SHA512有形式化证明,TLS 1.3 官选
长期归档 / 不可抵赖法律证据Ed25519 签名 + 时间戳服务RSA-PSS短、快、无坏 k 坑

面试速答清单

  • 三大原语:对称快 / 非对称慢 / 哈希不可逆——工程组合永远是"非对称谈钥匙 + 对称跑数据 + 哈希做指纹"
  • RSA 加密与签名钥匙方向相反:加密公钥锁、私钥开;签名私钥签、公钥验
  • AEAD(GCM / ChaCha20-Poly1305)= 加密 + 完整性 + 认证一体,tag 拼密文尾,Open() 先验后拆
  • Nonce 复用等同密钥泄漏(GCM 场景)
  • HMAC ≠ "哈希 + 密钥拼接":双层结构防长度扩展
  • 签名 ≠ HMAC:信任模型差在"可抵赖 / 不可抵赖"、"共享 / 私有"
  • ECDHE 的 E 是 Ephemeral:每次会话临时密钥 → 前向安全
  • HKDF 两步 Extract / Expand,info 做域分离
  • 密码存储 Argon2id + 加盐;SHA-256 单调直存 = 被 GPU 秒破
  • JWT 用 HS256(内网单验)或 RS256/ES256(对外多验),永远拒绝 alg: none
  • TLS 1.3 只留 5 个套件,全 AEAD + PFS,砍 RSA 交换和 CBC

关于 PQC / 同态 / 零知识证明

抗量子密码(NIST 已标准化的 Kyber KEM、Dilithium 签名)、全同态加密(BFV/CKKS)、零知识证明(zk-SNARK/STARK)是独立且深的方向,本篇作为面试基础不深挖;工程侧 2024 起 TLS 已开始试点 X25519+Kyber 混合密钥交换(Chrome / Cloudflare),面试遇到只需知道"存在混合过渡方案、保守不激进"即可。

记忆口诀

  • 三大原语:对称快跑数据 / 非对称慢谈钥匙 / 哈希单向做指纹
  • RSA 钥匙方向:加密"公锁私开",签名"私签公验",方向反了就废
  • AEAD 一步到位:GCM/ChaCha20-Poly1305 = 加密 + MAC 一体,tag 拼密文尾
  • Nonce 复用 = 泄漏:GCM 同密钥下 Nonce 重复即等同密钥公开
  • 哈希 ≠ MAC ≠ 签名:能防篡改/防伪造/防抵赖,三项能力逐级递增
  • ECDHE 的 E:Ephemeral 临时密钥,用完即弃 → 前向安全
  • HKDF 两步:Extract 压熵 + Expand 分域
  • 密码存储:Argon2id + 加盐;MD5/SHA-256 直存等于裸奔
  • JWT 三坑:alg: none / 算法降级 / HS256 对外滥用
  • TLS 1.3 收敛:只留 AEAD + PFS,砍掉 RSA 交换与 CBC

内容来源

综合整理自:RFC 5246 (TLS 1.2) / RFC 8446 (TLS 1.3) / RFC 2104 (HMAC) / RFC 5869 (HKDF) / RFC 7519 (JWT) / RFC 8017 (RSA-PSS) / RFC 8032 (EdDSA) / RFC 8439 (ChaCha20-Poly1305)、NIST SP 800-38A/D (AES 模式与 GCM)、NIST SP 800-131A (弃用清单)、PHC (Password Hashing Competition, Argon2)、《Cryptography Engineering》(Ferguson / Schneier / Kohno)、Cloudflare / Google 公开博客关于 PQC 混合过渡的部署报告。加密原语跨专题引用点:TLS 协议层视角见本站 http-tls-rpc,接入网关加密落地见 access-gateway,计费签名工程实践见 business-proxy。请以各 RFC 与 NIST 标准原文为准。

自测:合上资料能说清楚吗?

  1. RSA 加密和 RSA 签名分别用哪把钥匙?为什么"钥匙方向相反"?
参考答案

加密:发送方用接收方公钥加密,接收方用自己私钥解密——公钥人人可取,只有私钥拥有者能读。签名:签名方用自己私钥签名,验证方用签名方公钥验证——只有私钥拥有者能签、公钥可公开验证。两个操作用的"公私钥语义"正好相反:加密需要"只有一个人能读"→ 用接收方钥匙对;签名需要"只有一个人能写"→ 用签名方钥匙对。

  1. 为什么 AES-GCM 的 Nonce 绝对不能重复?重复了会发生什么?
参考答案

GCM 内部是 CTR 模式 + GMAC。CTR 用 AES(Key, Nonce || counter) 生成密钥流,再与明文 XOR。同密钥下 Nonce 复用意味着两条消息用同一密钥流 XOR 加密,攻击者拿到两条密文相 XOR 就直接消掉密钥流、得到两条明文的 XOR——只要知道其中一条(或部分),另一条就恢复。更糟的是 GHASH 的认证密钥 H 也随之泄露,可任意伪造 tag。所以 GCM 的 Nonce 要么随机(96-bit 空间足够)要么单调计数器,但同一密钥下绝不复用。

  1. 为什么"哈希 + 密钥拼接"(SHA256(K || m))不是合法的 MAC?HMAC 怎么修的?
参考答案

SHA-2 家族用 Merkle-Damgård 结构,存在长度扩展攻击:攻击者拿到 SHA256(K || m) 输出,即使不知道 K,也能计算 SHA256(K || m || padding || m') ——所以能伪造合法 MAC。HMAC 用双层结构 H( (K ⊕ opad) || H( (K ⊕ ipad) || m ) ) ——外层再哈希一次,让攻击者拿到的最终输出不再是"内部哈希状态",长度扩展无从下手。SHA-3 因用海绵结构天然免疫,但工程界仍统一走 HMAC 以保持算法族无关。

  1. 什么是前向安全(PFS)?ECDHE 相对 RSA 密钥交换的核心差异是什么?
参考答案

前向安全指长期私钥泄露也无法解开历史流量。RSA 密钥交换:客户端用服务器公钥加密 pre-master 发过去——服务器长期私钥若泄,历史抓包全部可解,无 PFS。ECDHE:双方每次会话各自生成一次性 ECDH 密钥对(Ephemeral 就是这个 E),协商完销毁,历史会话根本不依赖长期私钥来解密——长期私钥只用于签名 ECDHE 参数证明服务器身份,泄露后未来能被冒充,但过去的会话安全。这就是 TLS 1.3 直接砍掉 RSA 交换的原因。

  1. JWT 用 HS256 还是 RS256?alg: none 和算法降级攻击是怎么回事?
参考答案

HS256(HMAC-SHA256)适合内网单发单验——双方共享一把密钥,快但共享密钥泄露即失效、验证方能签。RS256(或 ES256/Ed25519)适合对外多方验签 / SSO——签发方持私钥、依赖方拿公钥(JWKS 分发)验签,公钥可任意公开。alg: none 攻击:早期库信任 header 里的 alg,攻击者改成 none 提交无签名 token 就蒙混过关;算法降级:alg: RS256 改成 alg: HS256,用服务器公钥作 HMAC 密钥重算——某些库会用公钥当"共享密钥"验签成功。修复:服务端 hard-code 期望算法、绝不信任 header 里的 alg。

  1. 密码存 DB 为什么必须用 Argon2id / bcrypt 这类慢哈希,而不是 SHA-256?
参考答案

SHA-256 单核每秒数亿次,GPU 集群每秒数百亿次——攻击者拖库后离线爆破 8 位密码只需分钟级。慢哈希(Argon2id / bcrypt / scrypt)故意消耗时间 + 内存(Argon2id 单次可配 100ms + 数十 MB),把 GPU/ASIC 的规模优势打掉,爆破成本抬到经济不可行。加盐是另一半:每密码一份随机 salt 与哈希一起存,让并行爆破无法共享预计算表——每个密码都得单独跑一遍。目标不是"存不可逆的哈希"(SHA-256 也不可逆),而是"存对离线爆破足够昂贵的哈希"。

  1. AEAD 的 tag 到底藏在哪里?Open() 失败时能拿到部分明文吗?
参考答案

以 AES-GCM 为例:Seal(nonce, plaintext, aad) → ciphertext || tag(tag 一般 16 字节,拼在密文末尾)。传输时 nonce || ciphertext || tag 一起送。解密时 Open() 先用 GHASH 重算 tag、与收到的 tag 比对,不合直接返回 error,绝不吐半个字节明文——这是 AEAD 相对"CBC + HMAC 手拼"的关键防御边界:杜绝 Padding Oracle 类侧信道(攻击者无法通过观察"解密到一半失败"的行为差异来构造攻击)。这就是"先验后拆(Encrypt-then-MAC)"的一体化实现。

  1. mTLS 和普通 TLS 的差别是什么?为什么服务网格喜欢用它?
参考答案

普通 TLS:客户端验证服务器证书(防冒充服务器),客户端身份靠应用层认证(用户名密码 / API Key / JWT)。mTLS:客户端也提交证书,服务端也验证——双向证书身份,把身份认证下沉到传输层。服务网格(Istio / Linkerd)用 SPIFFE ID 给每个服务发一张短期证书,服务间调用全量 mTLS——一是自动获得零信任内网身份(不再靠 IP 或 header 声明"我是谁")、二是加密与身份统一在 sidecar / eBPF 数据面完成、三是证书轮转自动化。这就是"零信任内网"的技术底座。

  1. TLS 1.3 相对 1.2 到底改了哪些密码学决定?
参考答案

四条大改动:一,砍套件——只留 5 个 AEAD + PFS 套件,删除 RSA 密钥交换、CBC 模式、RC4、MD5、SHA-1、导出级弱套件。二,握手 1-RTT(1.2 是 2-RTT),ClientHello 就带上密钥份额,避免"先协商再交换"的额外一轮;且支持 0-RTT 会话恢复(但 0-RTT 数据有重放风险,不能用于非幂等请求)。三,握手全加密——ServerHello 之后所有帧(含证书)都在派生密钥下加密,防握手中间人窥探证书内容。四,签名与密钥交换解耦——1.2 密码套件同时选中签名算法 + 密钥交换 + AEAD,组合爆炸;1.3 拆开分别协商,简化实现。

  1. 抗量子密码(PQC)是不是需要现在立刻切?工程上怎么过渡?
参考答案

不需要立刻全切。量子计算机对RSA / ECDH / ECDSA 有致命威胁(Shor 算法一次性搞定大整数分解与离散对数),但对对称密钥 + 哈希 只是安全强度减半(Grover 加速),AES-256、SHA-256 层面仍安全。过渡策略是"混合密钥交换":TLS 1.3 已试点 X25519+Kyber 组合密钥交换(Chrome、Cloudflare、AWS 2024 起大规模灰度),共享秘密由两者拼接派生 → 只要其中一个没被破就仍安全,兼顾"实用性能"与"抗量子过渡"。签名侧 NIST 已标准化 Dilithium,但证书体系迁移周期以年计。工程侧当前只需知道"有过渡方案、走混合、不激进推 PQC-only"即可。

最近更新: 2026/9/10 11:38
Prev
HTTP / HTTPS / TLS 与 RPC
Next
叙事主骨架轴选择方法论 SOP