[L1] 密码学基础概念辨析
一句话结论
哈希不可逆、加密可逆、编码无密钥,三类概念混淆即漏洞。
体系讲解
密码学在 Web 安全中涉及五个核心概念,初学者常将它们混淆使用,导致安全漏洞。
一、哈希 vs 加密
| 维度 | 哈希(Hash) | 加密(Encryption) |
|---|---|---|
| 可逆性 | 不可逆(单向函数) | 可逆(持有密钥可解密) |
| 密钥 | 无 | 有(对称或非对称) |
| 典型算法 | SHA-256、bcrypt、argon2 | AES、RSA |
| 典型用途 | 存储密码、数据完整性校验 | 传输/存储需还原的敏感数据 |
| PHP API | password_hash() / hash() | openssl_encrypt() |
二、对称 vs 非对称加密
| 维度 | 对称加密 | 非对称加密 |
|---|---|---|
| 密钥数量 | 1 个(收发方共享) | 1 对(公钥+私钥) |
| 速度 | 快(适合大数据) | 慢(适合小数据/密钥协商) |
| 典型算法 | AES-256-CBC/GCM | RSA、ECDSA |
| 典型场景 | 数据库字段加密、文件加密 | HTTPS 握手、数字签名 |
| 密钥分发难点 | 如何安全传递共享密钥 | 公钥可公开,私钥自持 |
HTTPS 两者结合:非对称加密协商对称密钥,后续数据传输用对称加密。
三、HMAC(消息认证码)
HMAC = Hash + 共享密钥,公式为 HMAC(key, message) = Hash(key ⊕ opad || Hash(key ⊕ ipad || message))。
普通哈希无密钥,任何人都能伪造消息并重新计算哈希值;HMAC 引入共享密钥,只有持有密钥的双方才能生成/验证合法 MAC,适用于:
- API 请求签名(防篡改、防重放)
- Webhook 消息完整性验证(如 GitHub/Stripe 回调)
四、数字签名(签名 ≠ 加密)
| 操作 | 用谁的密钥 | 谁能验证/解密 | 保证什么 |
|---|---|---|---|
| 加密 | 接收方公钥加密 | 接收方私钥解密 | 机密性 |
| 签名 | 发送方私钥签名 | 任何持有公钥者验证 | 完整性 + 不可否认性 |
密钥方向完全相反,混淆会导致功能失效。
五、编码(Base64 / URL 编码)≠ 加密
编码只是改变数据的表示形式,无需密钥,任何人都可以逆向还原:
base64_encode('secret')→c2VjcmV0→base64_decode('c2VjcmV0')→secret- URL 编码同理,仅解决字符集兼容问题,不提供任何保密性
将 Base64 编码的内容放在 HTTP 头或日志中并不等于"加密传输"。
考察意图
考察候选人是否能准确区分安全领域五个基础概念,防止将编码当加密、用 MD5 存密码等高频安全误用,反映其代码审查和安全意识的基本素养。
追问链
Q1:为什么不能用 MD5 或 SHA1 存储用户密码? MD5/SHA1 是通用哈希,运算速度极快,GPU 可秒级暴力破解或查彩虹表;应改用专为密码设计的慢哈希算法(bcrypt / argon2),PHP 中直接用 password_hash() + password_verify()。
Q2:HMAC 与直接在消息末尾附加密钥再哈希(hash('sha256', $key.$msg))有何区别? 后者存在长度扩展攻击(Length Extension Attack)漏洞:攻击者无需知道密钥,仅凭已知哈希值就能伪造附加任意内容后的合法哈希。HMAC 通过双层嵌套结构规避了此漏洞,是经过密码学证明安全的构造。
Q3:HTTPS 握手中,非对称加密和对称加密分别在哪个阶段使用? 握手阶段:客户端用服务端公钥加密预主密钥(或用 ECDHE 做密钥协商),完成对称密钥的安全交换;数据传输阶段:双方用协商得到的对称密钥(AES)加解密实际数据,兼顾安全性与性能。
Q4:API 请求签名中,HMAC 如何防重放攻击? 仅靠 HMAC 无法防重放(相同请求签名相同);需额外加入时间戳 + nonce:服务端校验时间戳在允许窗口内(如 ±5 分钟),并将 nonce 存入 Redis 短期缓存,防止同一请求被重复提交。
易错点
Base64 当加密用:Base64 无密钥,任何人执行
base64_decode()即可还原原文。将 Base64 字符串放入 HTTP Header 或日志中暴露 token/密码,是常见安全漏洞。用 MD5/SHA1 直接存密码:这两种算法被设计为"越快越好",现代 GPU 破解速度以亿次/秒计;应使用
password_hash($pwd, PASSWORD_BCRYPT)或PASSWORD_ARGON2ID,其内置的 cost 因子会故意拖慢计算。签名和加密的密钥方向混淆:加密是用接收方公钥加密(私钥解密),签名是用发送方私钥签名(公钥验证),方向相反。混淆后若用私钥"加密"则所有人都能用公钥"解密",完全失去机密性。
代码示例
<?php
// 1. 哈希(不可逆)—— 存储密码
$hash = password_hash('my-password', PASSWORD_BCRYPT);
var_dump(password_verify('my-password', $hash)); // bool(true)
// 2. 对称加密(可逆)—— 存储需还原的敏感字段
$key = random_bytes(32);
$iv = random_bytes(16);
$enc = openssl_encrypt('card-number', 'aes-256-cbc', $key, 0, $iv);
$dec = openssl_decrypt($enc, 'aes-256-cbc', $key, 0, $iv); // 还原原文
// 3. HMAC —— API 请求签名
$sig = hash_hmac('sha256', 'timestamp=1716451200&nonce=abc', 'shared-secret');
// 4. Base64 只是编码,非加密
echo base64_decode(base64_encode('secret')); // 输出: secret