密码学基础:编码、哈希、加密与数字签名
学习 JWT 和 Argon2 前,必须先区分编码、哈希、加密和数字签名。它们看起来都能把原始文本变成“不容易看懂的字符串”,但目的完全不同。
1. 明文与密文
- 明文(Plaintext):可直接理解的原始数据,例如
hello。 - 密文(Ciphertext):明文经过加密算法和密钥处理后的结果。
- 密钥(Key):控制加密、解密或签名结果的秘密参数。
加密的核心目标是保密性:没有正确密钥的人不应读懂数据。
2. 编码不是加密
编码是为了让数据适合传输或存储,任何知道规则的人都能还原。
例如 Base64:
hello -> aGVsbG8=
Base64URL 是适合 URL 和 HTTP 场景的 Base64 变体,通常会:
- 用
-代替+。 - 用
_代替/。 - 省略尾部的
=填充。
JWT 的 Header 和 Payload 使用 Base64URL 编码。因此拿到 JWT 的人通常都能读取它们,不能把密码、银行卡号或密钥放入 Payload。
3. 哈希
哈希函数把任意长度的数据映射为固定长度摘要:
原始数据 --哈希函数--> 摘要
密码学哈希通常追求:
- 相同输入得到相同输出。
- 很难从摘要反推出原文。
- 原文发生微小变化,摘要会明显变化。
- 很难找到两个产生相同摘要的不同输入。
常见通用哈希算法有 SHA-256。它适合文件完整性、随机 Token 摘要等场景,但它计算很快,所以不适合直接哈希人类密码。密码通常较短且可猜测,攻击者可以高速尝试大量候选值。
4. 密码哈希
Argon2 属于专门的密码哈希算法。它故意消耗时间和内存,从而提高批量猜密码的成本。
密码哈希通常会使用随机盐(Salt):
密码 + 随机盐 + 成本参数 -> 密码哈希
盐不是密码,不需要保密。它用于确保:
- 两个用户使用相同密码时,得到的哈希通常也不同。
- 攻击者难以复用提前计算好的哈希表。
node-argon2 默认会生成安全随机盐,并把算法类型、版本、成本参数、盐和结果编码在 PHC 字符串中:
$argon2id$v=19$m=65536,t=3,p=4$...盐...$...哈希...
数据库保存这一整串即可,不需要另外增加 salt 字段。
5. 对称加密
对称加密使用同一把密钥进行加密和解密:
明文 + 密钥 -> 密文
密文 + 同一密钥 -> 明文
例如 AES-GCM。它适合需要在未来取回原文的数据,例如加密保存某些业务机密。
密码不需要被服务端还原,所以密码应哈希,而不是加密保存。
6. 非对称加密
非对称密码学使用一对相关密钥:
- 公钥:可以公开。
- 私钥:必须保密。
不同算法可以用于加密或签名。不能简单理解为“私钥加密、公钥解密”,数字签名和加密是不同的密码学操作。
7. 消息认证码与数字签名
签名类机制主要解决两个问题:
- 完整性:内容是否被修改。
- 来源认证:内容是否由持有正确密钥的一方生成。
JWT 常见的 HS256 实际使用 HMAC:签发方和验证方共享同一个 Secret。
signature = HMAC-SHA256(header.payload, secret)
RS256 使用 RSA 私钥签名、公钥验证:
签发服务:私钥签名
验证服务:公钥验证
签名不会隐藏 header.payload,只用于检测篡改并确认签发者。
8. 四类技术对比
| 技术 | 是否可逆 | 是否需要密钥 | 主要目标 | 示例 |
|---|---|---|---|---|
| 编码 | 是 | 否 | 表示和传输数据 | Base64URL |
| 通用哈希 | 否 | 否 | 数据摘要、完整性 | SHA-256 |
| 密码哈希 | 否 | 否 | 安全保存密码 | Argon2id |
| 加密 | 是 | 是 | 隐藏数据内容 | AES-GCM |
| 签名/MAC | 不用于还原 | 是 | 来源认证、防篡改 | RS256、HS256 |
9. 和本教程的关系
用户密码
-> Argon2id 密码哈希
-> 数据库不保存明文
JWT Header + Payload
-> Base64URL 编码,可被读取
-> 使用 Secret 或私钥生成 Signature
-> verify 检查签名与声明
最重要的结论:
普通 JWT 通常是编码加签名,而不是加密;Argon2 是密码哈希,而不是加密。