DAY 02 · AUTHN / AUTHZ ROADMAP · 30 DAYS

哈希、加密 与签名——密码学三件套

第二天的目标是把后面所有协议依赖的"密码学零件"全部认清—— 通用哈希(SHA-256)和密码哈希(bcrypt / argon2)为什么不能混用; 对称(AES)和非对称(RSA / Curve25519)各自的取舍; HMAC 解决了"用哈希做认证"的什么问题; 数字签名又比 HMAC 多解决了什么。 这些零件是 JWT / OAuth / mTLS / API 签名所有协议的物理基础—— 不理解它们,就只能"按文档抄代码",踩到坑也找不到根因。

DURATION 75–120 min READ 40 min HANDS-ON 40 min REVIEW 20 min STACK Go · crypto/* · bcrypt · argon2
M ·

思维导图

OVERVIEW
DAY 02 · 密码学三件套HASH · PWHASH · ENCRYPT · SIGN01 · HASH通用哈希02 · PWHASH密码哈希03 · ENCRYPT对称 / 非对称04 · SIGNHMAC / 数字签名·三个安全性质·MD5 / SHA-1 已破·SHA-256 · BLAKE3·用途: 指纹 / 完整性·为何 SHA-256 不能存密码·盐 · 慢哈希 · memory-hard·bcrypt / scrypt / argon2·参数调优·AES-256-GCM (AEAD)·ChaCha20-Poly1305·RSA · Curve25519·混合加密 · KMS·HMAC-SHA256·长度扩展攻击·Ed25519 · ECDSA·不可否认性DELIVERABLES能讲清通用 vs 密码哈希跑通 argon2id 存校验用 AES-GCM 加解密写出 Ed25519 验签
FIG · Day 02 全景: 通用哈希 → 密码哈希 → 加密 → 签名
01 ·

通用哈希函数

20 MIN

哈希函数把任意长度的输入,映射到固定长度的输出——但"映射"二字不重要, 重要的是它需要满足的三个安全性质。 这三个性质既是哈希函数的"凭据",也是它的"局限"—— 理解它们,才能明白为什么 MD5 已被宣判死刑, 也才能明白为什么哪怕是最新的 SHA-3,也不该用来存密码

三个安全性质

性质定义被破意味着什么
原像抗性
Preimage Resistance
给定 h, 找到 x 使 H(x)=h 在算力上不可行攻击者可从密码哈希反推出密码
第二原像抗性
Second Preimage Resistance
给定 x, 找到另一个 x' 使 H(x)=H(x') 不可行攻击者可伪造一个不同的文档但哈希相同
抗碰撞
Collision Resistance
任意找到两个 x ≠ x' 使 H(x)=H(x') 不可行攻击者可主动构造两个哈希相同的输入(SHA-1 / MD5 实际事件)
历史事件 MD5 在 2004 年被中国学者王小云团队找到实际碰撞; SHA-1 在 2017 年被 Google "SHAttered" 攻击发布了第一组真实碰撞 PDF。 两者都是抗碰撞性质被破——原像抗性目前仍然成立。 所以"MD5 校验下载文件完整性"在非恶意场景下勉强能用,但有对手的场景一律不能用。

常见算法对比

算法输出长度状态推荐用途
MD5128 bit已破 (2004)仅用于非安全场景的"指纹"(如缓存键)
SHA-1160 bit已破 (2017)不要在新项目使用; 旧 Git 在迁移
SHA-256 (SHA-2)256 bit仍然安全通用首选; HMAC / JWT / TLS 的工作哈希
SHA-3 (Keccak)224–512 bit安全 · 不同设计需要 SHA-2 之外多样性时备选
BLAKE3可变安全 · 极快大文件 / 高吞吐场景, 比 SHA-256 快 4-10×

工程用途

INTEGRITY

完整性校验

下载文件后比对 sha256sum——传输过程任何一 bit 翻转,哈希都对不上。这是哈希最古老的用途。

FINGERPRINT

数据指纹 / 去重

把内容 hash 成 32 字节, 当作缓存键 / 唯一标识。CDN 的 ETag、容器的 image digest、IPFS 的 CID 全是这个套路。

ADDRESS

内容寻址

Git 用 SHA-1 / SHA-256 做 commit ID; 每个 commit 都"指向"父 commit 的 hash, 构成不可篡改的链条。

PROOF

Merkle Tree

把多个 hash 两两组合再 hash, 形成一棵树。区块链、TLS 证书透明度日志(CT)、Git 都在用——可以高效证明"某条记录确实在集合里"。

关键认知: 通用哈希追求"快"——SHA-256 在现代 CPU 上每秒能算十亿次。这是它做"完整性"的优点, 却恰好是它做"密码哈希"的致命缺点。下面立即展开
02 ·

密码哈希: bcrypt / scrypt / argon2

25 MIN

MD5(password) 或者 SHA-256(password) 存数据库, 是后端工程师最常见的"看起来安全实则裸奔"的代码。 问题不在"哈希了没有",而在用错了哈希—— 通用哈希为"快"而生,密码哈希必须为"慢"而生。 这一节就是要讲清这个反直觉的设计哲学。

用 SHA-256 存密码的攻击

SHA-256 存密码 · 被破场景数据库泄露: SHA256("password123")= ef92b778... (固定输出)攻击者拿出 10 亿条常见密码SHA256 (RTX 4090): 5 × 10⁹ / s10 亿条密码 = 200 毫秒跑完99% 弱密码立即被破ARGON2ID · 被破场景argon2id(pw, salt, m=64M, t=3, p=4)每次校验需 64MB 内存 + 数次迭代攻击者拿出 10 亿条常见密码argon2 (RTX 4090): ~1000 / s10 亿条密码 ≈ 11 天才跑完+ 加盐 ⇒ 必须对每个用户分别跑慢 ≈ 5 × 10⁷ 倍 · 让 GPU 不再划算
FIG · 同样 10 亿条密码字典: SHA-256 200ms,argon2id 11 天

三个对抗手段

  1. 加盐 (Salt)。 每个用户一个随机盐, 拼到密码上再哈希。作用: 让"全表彩虹表"失效——攻击者必须对每个用户单独跑字典, 不能复用结果。
  2. 慢哈希 (Slow Hashing)。 故意做几千~几十万次迭代, 让一次校验耗时 50-500ms。用户登录感觉不到, 攻击者却被拖慢几个数量级。
  3. 消耗内存 (Memory-Hard)。 算法故意需要大量 RAM(几十~几百 MB), 让 GPU / ASIC 优势失效——GPU 单卡显存只有几十 GB, 同时跑几百路就被卡死。

主流密码哈希算法

算法年代慢哈希Memory-Hard推荐度
PBKDF22000 (RFC 2898)✓ (迭代次数)FIPS 合规场景 · 否则不推荐
bcrypt1999✓ (cost 因子)部分 (4 KB)稳健 · 70 字符密码上限是已知缺陷
scrypt2009 (RFC 7914)可选 · 已基本被 argon2 取代
argon2id2015 PHC 冠军✓ + 抗侧信道现代首选 · OWASP 推荐第一
argon2 三个变种 argon2d: 抗 GPU 最强, 但有时序侧信道风险, 不适合存密码; argon2i: 抗侧信道, 但抗 GPU 弱一些; argon2id: 前两者的混合 —— 密码哈希一律选 argon2id。 参数推荐(OWASP 2024): m=19456 KiB (≈19 MB), t=2, p=1; 或更激进 m=65536 (64 MB), t=3, p=4。

Go 实战: bcrypt 与 argon2id

// password_bcrypt.go · 最简单 — golang.org/x/crypto/bcrypt
package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
)

func main() {
    pw := []byte("correct horse battery staple")

    // 1) 哈希存储 — cost=12 是 2024 年合理值
    hashed, _ := bcrypt.GenerateFromPassword(pw, 12)
    fmt.Println("DB:", string(hashed))
    // → $2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy

    // 2) 登录时校验 — 不需要单独取盐, hashed 字符串里自带
    err := bcrypt.CompareHashAndPassword(hashed, pw)
    fmt.Println("OK?", err == nil)
}
// password_argon2.go · 更现代 — golang.org/x/crypto/argon2
package main

import (
    "crypto/rand"
    "crypto/subtle"
    "encoding/base64"
    "fmt"
    "strings"

    "golang.org/x/crypto/argon2"
)

// OWASP 2024 推荐参数
const (
    memKiB   = 64 * 1024  // 64 MB
    iters    = 3
    parallel = 4
    saltLen  = 16
    keyLen   = 32
)

func Hash(pw string) string {
    salt := make([]byte, saltLen)
    _, _ = rand.Read(salt)
    h := argon2.IDKey([]byte(pw), salt, iters, memKiB, parallel, keyLen)

    // 标准编码格式 (PHC string format): $argon2id$v=19$m=...$salt$hash
    return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
        argon2.Version, memKiB, iters, parallel,
        base64.RawStdEncoding.EncodeToString(salt),
        base64.RawStdEncoding.EncodeToString(h))
}

func Verify(pw, encoded string) bool {
    parts := strings.Split(encoded, "$")
    // 简化版: 实际生产请解析参数, 这里假设跟 Hash 一致
    salt, _ := base64.RawStdEncoding.DecodeString(parts[4])
    expected, _ := base64.RawStdEncoding.DecodeString(parts[5])

    actual := argon2.IDKey([]byte(pw), salt, iters, memKiB, parallel, keyLen)
    return subtle.ConstantTimeCompare(actual, expected) == 1
}

func main() {
    enc := Hash("correct horse battery staple")
    fmt.Println("DB:", enc)
    fmt.Println("OK?", Verify("correct horse battery staple", enc))
}
关键代码: 校验阶段必须用 subtle.ConstantTimeCompare 而不是 bytes.Equal——前者耗时与输入相同,后者会有时序侧信道。Day 01 也提到过这一点
03 ·

对称 vs 非对称加密

20 MIN

加密分两大流派,区别只有一句话: 对称用同一把密钥加密和解密——快,但密钥必须事先约定; 非对称用一对公私钥,公钥加密私钥解密——慢,但公钥可以随便发。 现代世界用的是混合加密: 非对称协商一次性的对称密钥,后续通信走对称。TLS 1.3 就是这个套路。

两种加密的对比示意

SYMMETRIC · 一把钥匙AALICEBBOBSHARED KAES(K, msg)痛点: K 怎么传给 Bob?不能用同一条不安全链路ASYMMETRIC · 一对钥匙AALICEBBOBPUB · 可公开PRIV · 永不传出PUB (Bob)RSA(PUB_B, msg)优点: 公钥随便传只有 Bob 能用私钥解开
FIG · 对称: 钥匙共享; 非对称: 公钥公开, 私钥保留

主流算法对照

类型算法密钥长度状态 / 推荐
对称 · 分组AES-128 / 256128 / 256 bit行业标准 · 用 GCM 模式
对称 · 流ChaCha20-Poly1305256 bit移动 / 无 AES-NI 场景更快
对称 · 旧模式AES-CBC / ECB无认证 · 不再推荐裸用
非对称 · 加密RSA-OAEP2048 / 3072 bit稳健 · 但速度慢且密钥大
非对称 · 密钥交换X25519 / ECDH256 bitTLS 1.3 默认 · 短小快速
非对称 · 签名Ed25519256 bit现代首选 · 短小快速安全
什么是 AEAD? Authenticated Encryption with Associated Data —— 加密的同时顺便认证密文是否被篡改。 AES-GCM 和 ChaCha20-Poly1305 都是 AEAD。 为什么重要? 经典的 AES-CBC 只加密、不认证, 攻击者可以翻转密文的某些 bit, 让解密出"被改过但语法合法"的明文(著名的 Padding Oracle 攻击)。 2024 年的经验法则: 加密必选 AEAD, 不要再裸用 CBC / CTR。

Go 实战: AES-256-GCM

// aes_gcm.go · 标准库 crypto/aes + crypto/cipher
package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/hex"
    "fmt"
)

func main() {
    // 1) 准备 32 字节 (256-bit) 密钥 — 生产请从 KMS / 配置中心取
    key := make([]byte, 32)
    _, _ = rand.Read(key)

    block, _ := aes.NewCipher(key)
    gcm, _ := cipher.NewGCM(block)

    // 2) 12 字节随机 nonce — 同一密钥下绝不能重复
    nonce := make([]byte, gcm.NonceSize())
    _, _ = rand.Read(nonce)

    plaintext := []byte("hello, day02")
    aad := []byte("user_id=42")  // 附加认证数据 (不加密但参与认证)

    // 3) Seal: 密文 = nonce || ciphertext || tag
    ct := gcm.Seal(nil, nonce, plaintext, aad)
    fmt.Println("ciphertext:", hex.EncodeToString(ct))

    // 4) Open: 任何字节被篡改都会返回 error
    pt, err := gcm.Open(nil, nonce, ct, aad)
    if err != nil {
        fmt.Println("verify failed:", err); return
    }
    fmt.Println("plaintext:", string(pt))
}
陷阱 #1

nonce 不能重用

同一密钥下用同一个 nonce 加密两次, GCM 的安全性整体崩塌——攻击者可以恢复明文异或值。生产中 nonce 必须从 crypto/rand 来,12 字节随机已足。

陷阱 #2

密钥永远不要硬编码

放进代码就等于推到 Git, 推到 Git 就等于公开。生产请用 KMS(AWS KMS / 阿里云 KMS) 或 Vault, 启动时拉取, 内存中持有。

04 ·

HMAC 与数字签名

25 MIN

加密保证秘密, 但很多场景我们不需要秘密,只需要完整性 + 来源可证—— "这条消息确实是 Alice 发的,而且没被改"。 解决这件事的两个工具是 HMAC(对称密钥, 双方都能算) 和 数字签名(非对称密钥, 只有私钥持有方能签)。 JWT / Webhook / OAuth 签名 / TLS 证书全部建立在这两个工具之上。

HMAC vs 数字签名 · 对比示意

HMAC · 对称密钥 (双方共享 K)ASHARED KBSHARED Kmsg ‖ HMAC(K, msg)Bob 用同样的 K 重算 HMAC(K, msg), 比对是否相等DIGITAL SIGNATURE · 非对称 (A 私签 / B 公验)APRIV_A · 永不外传BPUB_A · 可公开msg ‖ Sign(PRIV_A, msg)Bob 用 PUB_A 验证签名 — 只有 Alice 能产生这个签名
FIG · HMAC 双方对称, 签名 A 私签 B 公验

HMAC: 为什么不是 H(K ‖ msg)?

最朴素的想法: 既然要"用密钥认证消息", 那 SHA256(key + msg) 不就行了? 不行。基于 Merkle–Damgård 结构的哈希函数(SHA-1 / SHA-256) 存在长度扩展攻击: 攻击者拿到 H(key + msg)msg, 不需要知道 key, 就能算出 H(key + msg + suffix)。 这意味着攻击者可以"在合法消息后面追加任意内容并伪造出有效 MAC"。

HMAC 的设计 RFC 2104 给出的解法: HMAC(K, m) = H( (K' ⊕ opad) ‖ H( (K' ⊕ ipad) ‖ m ) )。 两次哈希 + 两个固定填充 (ipad/opad) ——保证内部状态不能被外部继续追加。 看起来繁琐, 但只是一次性的设计代价, 用起来跟普通哈希一样简单, 并彻底堵住了长度扩展攻击。 所有"用对称密钥认证消息"的场景, 都该用 HMAC, 不要手搓拼接。

Go 实战: HMAC-SHA256

// hmac_sha256.go · 验证 Webhook 签名的标准套路
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func sign(key, msg []byte) string {
    mac := hmac.New(sha256.New, key)
    mac.Write(msg)
    return hex.EncodeToString(mac.Sum(nil))
}

func verify(key, msg []byte, sig string) bool {
    expected := sign(key, msg)
    return hmac.Equal([]byte(expected), []byte(sig))  // 常量时间比较
}

func main() {
    key := []byte("my-app-secret")
    msg := []byte("event=order.paid&order_id=42&ts=1234567890")

    sig := sign(key, msg)
    fmt.Println("X-Signature:", sig)

    fmt.Println("OK?", verify(key, msg, sig))
}

数字签名: 比 HMAC 多解决什么

性质HMAC数字签名
密钥模型对称 · 双方共享非对称 · 签发方有私钥, 验证方有公钥
完整性 + 来源
不可否认性✗ 双方都能产生 MAC✓ 只有私钥持有人能签
多方验证每对人都要分发密钥公钥发布一次, 全网验证
性能极快 (μs 级)慢一些 (ms 级)
典型用途Webhook · API 签名 · JWT HS256JWT RS256/ES256 · TLS 证书 · 软件包签名

现代签名算法

RSA-PSS

RSA 签名

2048+ bit 密钥; 兼容性最好, 但密钥和签名都大。新项目用 PSS 模式而不是老的 PKCS#1 v1.5。

ECDSA

椭圆曲线 DSA

P-256 (secp256r1) 是主流, JWT 的 ES256 用的就是它。必须用确定性 nonce (RFC 6979),否则烂随机数会泄露私钥(Sony PS3 事件)。

RECOMMENDED

Ed25519 (EdDSA)

RFC 8032 · 密钥 32 字节、签名 64 字节、速度快、API 不容易用错。新项目首选。SSH / Tailscale / Signal 都在用。

Go 实战: Ed25519 签名与验证

// ed25519_sign.go · 标准库 crypto/ed25519, 不需要任何外部依赖
package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "encoding/hex"
    "fmt"
)

func main() {
    // 1) 生成密钥对 — pub 公开, priv 严格保密
    pub, priv, _ := ed25519.GenerateKey(rand.Reader)
    fmt.Println("public  (", len(pub),  "B):", hex.EncodeToString(pub))
    fmt.Println("private (", len(priv), "B):", hex.EncodeToString(priv))

    msg := []byte("hello, day02")

    // 2) 签名 — 输出 64 字节
    sig := ed25519.Sign(priv, msg)
    fmt.Println("sig  (", len(sig), "B):", hex.EncodeToString(sig))

    // 3) 验证 — 只需要公钥
    ok := ed25519.Verify(pub, msg, sig)
    fmt.Println("verify?", ok)

    // 4) 改一个字符再验, 必然失败
    ok = ed25519.Verify(pub, []byte("hello, day03"), sig)
    fmt.Println("tampered verify?", ok)
}
观察任务: 跑起来后注意私钥实际占 64 字节(包含 32 字节 seed + 32 字节派生公钥)、公钥 32 字节、签名 64 字节——比 RSA 短了一个数量级。这就是为什么 SSH / Tailscale / 区块链都在转 Ed25519
Q ·

常见疑问

5 QUESTIONS
Q1 MD5 / SHA-1 都"已破"了, 是不是任何场景都不能再用? +
ANS

不必一棍子打死, 但要分场景:

必须避免: 任何"有对手"的安全场景——密码哈希、数字签名、证书、防篡改校验。攻击者可以主动构造碰撞,让"看起来正确的校验"通过假数据。

仍可用: 完全非对抗的场景, 比如缓存键HashMap 分桶非恶意来源的下载完整性(只为防网络传输位翻转)。这些场景不存在"主动构造碰撞的攻击者"。

但新项目里没必要再选 MD5—— xxHash / BLAKE3 速度更快, SHA-256 也只是慢一点。"避免使用 MD5"是最便宜的安全收益

Q2 SHA-256 这么快, 为什么不能用来存密码? 我加盐了也不行? +
ANS

"快"对哈希函数本身是优点, 但对密码哈希是致命缺陷

加盐确实能让彩虹表失效(攻击者不能一次跑出全表), 但加盐不能拖慢单次校验——攻击者拿到泄露的数据库后, 依然可以"对每个用户分别跑"。而 GPU 上 SHA-256 每秒能算几十亿次, 字典攻击照样 5 分钟跑完。

密码哈希需要的不是"加盐这一招", 而是三招齐上: 加盐 + 慢哈希 + memory-hard。bcrypt / argon2 之所以叫"密码哈希函数", 是因为它们故意设计成不可加速——单次校验 100ms, GPU 也救不了攻击者。

所以正确公式: "密码 + 盐 + 慢哈希 + 内存", 不是"密码 + 盐 + 任意哈希"。

Q3 bcrypt 和 argon2 选哪个? 我看 bcrypt 也很常见, 是不是不必折腾? +
ANS

OWASP 2024 的推荐顺序: argon2id ≻ scrypt ≻ bcrypt ≻ PBKDF2。但前三个都"足够安全",差别在边际收益。

选 argon2id 的理由: 现代设计、抗 GPU 最强(memory-hard 真做到位)、参数可调更灵活、PHC 比赛的官方冠军。新项目优先选它。

选 bcrypt 的理由: 历史悠久 / 集成最多 / 现有 ORM 多有内置 / 不需要额外依赖。维护已久的项目继续用 bcrypt 完全可以, 不必为换而换。已知缺陷只有一个: 密码 72 字节后被截断——如果你的系统允许超长密码, 要么先 SHA-256 一遍再 bcrypt(标准做法), 要么换 argon2id。

选 PBKDF2 的理由: 几乎只剩一个——必须满足 FIPS 140-2 合规(美国政府场景)。否则它的抗 GPU 性最差, 不该选。

Q4 为什么不能再用 AES-CBC? 我看老系统都在用啊。 +
ANS

AES-CBC 本身没有问题, 问题在于"裸用 CBC 没有认证"。攻击者可以翻转密文里的 bit, 让解密结果改变, 而你的应用完全不知道密文被篡改过。

经典攻击是 Padding Oracle: 攻击者通过观察"解密失败 vs 解密成功但业务报错"的差异, 一字节一字节恢复明文。历史上 ASP.NET、Java EE、OpenSSL 都中过

正确做法有两种: (1) 用 AEAD(AES-GCM / ChaCha20-Poly1305) ——加密的同时认证; (2) "Encrypt-then-MAC" 模式 —— 先 CBC 加密, 再 HMAC 整段。后者实现起来容易出错, 所以现代场景一律推荐前者。

老系统继续用 CBC 没必要立即翻新, 但新写的代码不要用裸 CBC。Go 的 crypto/cipher.NewGCM 跟 NewCBC 用起来一样简单。

Q5 RSA 这么经典, 为什么大家都在推 Ed25519? +
ANS

三个核心理由:

(1) 体积。 Ed25519 公钥 32 字节、签名 64 字节; RSA-2048 公钥 ~270 字节、签名 256 字节。在 JWT / HTTP Header / TLS Handshake 这种"每字节都要钱"的场景, 差一个数量级。

(2) 速度。 Ed25519 签名 / 验证比 RSA-2048 快 10-100×, 而且没有"算 RSA 加密慢一些, 算 RSA 签名快一些"这种细分坑。

(3) 难误用。 RSA 的椭圆曲线选错、padding 选错、nonce 选错都能炸; Ed25519 没有 padding 概念, nonce 是确定性派生的, API 几乎没有"用错"的方法。安全 = 默认正确。

那为什么 RSA 还活着? 因为兼容性: 大量旧系统、CA、嵌入式设备只支持 RSA。新建私有协议优先 Ed25519; 必须与外部互联的场景检查对端支持。

R ·

复盘问题

5 QUESTIONS
  1. 用一句话分别讲清哈希函数的三个安全性质——原像抗性 / 第二原像抗性 / 抗碰撞。SHA-1 在 2017 年"被破"指的是哪一个性质?
  2. 为什么 SHA256(key + msg) 不是安全的 MAC, 而 HMAC 解决了什么问题? (提示: 长度扩展攻击)
  3. argon2id 的三个关键参数(memory / time / parallelism)分别防什么? OWASP 2024 推荐的值是多少?
  4. AEAD 是什么, 为什么裸 AES-CBC 不是 AEAD? Padding Oracle 攻击为什么能恢复明文?
  5. 动手写一段 Go 代码: 用 argon2id 哈希一个密码, 然后写一个 Verify(pw, encoded string) bool 函数, 必须用常量时间比较。
✓ ·

今日检查清单

8 ITEMS
  • 能讲清通用哈希(SHA-256)和密码哈希(argon2)的本质差异——"快"与"慢"
  • 能列出 bcrypt / scrypt / argon2 各自的设计动机和取舍
  • 能解释 HMAC 为什么比 H(key ‖ msg) 安全
  • 能说出 HMAC 与数字签名的核心差别——是否支持不可否认
  • 跑通 Lab: bcrypt 或 argon2id 的密码哈希 + 校验
  • 跑通 Lab: AES-256-GCM 加密一段文本, 篡改密文后能看到 Open 返回 error
  • 跑通 Lab: Ed25519 签名 + 验证, 篡改消息后验证失败
  • 读完 OWASP Password Storage Cheat Sheet 和 RFC 2104 (HMAC, 11 页)
📖 ·

推荐阅读

5 ITEMS
MUST READ · OWASP

Password Storage Cheat Sheet

OWASP 出品, 2024 最新版。一页就讲完"密码该怎么哈希、参数怎么定"——直接能复制粘贴的工程结论。

MUST READ · 11 PAGES

RFC 2104 · HMAC

HMAC 的原始规范, 11 页读完。重点看为什么需要 ipad / opad 两层填充——理解后再也不会想"自己拼 hash"。

RFC · ARGON2

RFC 9106 · Argon2

argon2 三个变种的正式规范。重点看 §4 "Recommended Parameters", 一切疑问到这里找答案。

RFC · EDDSA

RFC 8032 · EdDSA / Ed25519

Ed25519 的规范文档。30 多页, 但前 10 页足以理解为什么这个算法"难误用"。后面是公式和参考实现。

BOOK · 入门首选

Serious Cryptography (Aumasson)

2017 No Starch 出版, 公认现代密码学的最佳入门书。没有公式恐惧症的话, 完整啃完一遍, 你就能成为团队的"密码学顾问"。

» ·

Day 03 预告

NEXT
TOMORROW · DAY 03

PKI 与 TLS / HTTPS

有了"对称 / 非对称 / 哈希 / 签名"四件套, 第三天把它们组合起来—— 看 CA 怎么用签名给一张证书背书, 看 TLS 1.3 怎么用非对称协商出对称密钥, 然后所有应用数据走AEAD加密。 之后再看 mTLS——服务间互验证书的现代做法。 看完 Day 03, 你应该能用 openssl 自己搭一套 CA + 服务端证书 + 客户端证书, 跑通 mTLS。

"密码学不是发明新算法,而是用对已有的——'用对'比'强大'重要十倍。"

DAY 02 · AUTHN / AUTHZ 30-DAY ROADMAP · OPEN PLATFORM