主题
界面
主题
《DNS、IP、端口与 ARP》解决了"找到对方";本节解决**"在不可信网络里保护数据"**:
明文 HTTP:密码原封不动出现在报文里 ✗✗
加密之后: 中间人只能看到乱码 ✓★ 本节的四个实测都指向同一个结论:
别自己发明加密方案,用标准库 + 标准做法 ✓✓
MessageDigest / Cipher 吗?✓先看本节的实测 ✗✗:
客户端发送(http://):
POST /login
Body: {"username":"alice","password":"P@ssw0rd123"}
服务端(或任何链路上的人)看到的明文:
{"username":"alice","password":"P@ssw0rd123"}★ 密码原封不动地出现在报文里 ✗✗ —— 同一个 WiFi 下的任何人、路径上的任何一跳,都能直接读到 ✓
★ 再看一个更隐蔽的问题 ✗:
如果数据库被拖库,而密码是"裸 SHA-256 存储":
用户 A 与用户 B 用同一个密码 → 哈希值一模一样 ✗
攻击者拿彩虹表一查 → 直接得到原文 ✗✗★ 本节的实测把这个对比量化出来 ✓:
① 裸 SHA-256:两个用户密码相同 → 哈希完全相同 ✗✗
② 加盐:每人随机 16 字节 → 哈希完全不同 ✓
③ PBKDF2(迭代 210000 次):141 ms ← 故意慢 ✗
单次 SHA-256:< 1 ms ← 快得可以被暴力破解 ✗★ 明文 HTTP 下暴露的东西 ✓:
| 暴露项 | 攻击者能做什么 |
|---|---|
| 密码 ✗✗ | 直接登录 ✓ |
会话 Cookie(JSESSIONID) ✗✗ | 直接冒充登录状态 ——不需要密码 ✓✓ |
| 个人数据 ✗ | 隐私泄露 ✓ |
| 请求内容 ✗ | 篡改(中间人改金额、改收款账号) ✗✗ |
★★ 第二条最容易被低估 ✓✓:
会话 Cookie 泄露 = 账号被完全接管 ✗
而且攻击者不需要知道密码,甚至不需要你再次登录 ✓
★ 三条硬性要求 ✓✓:
| 要求 | 说明 |
|---|---|
| 所有带凭据的请求必须 HTTPS ★★★ | 登录、支付、任何带 Cookie 的请求 ✓ |
| 不要用 GET 传敏感参数 ✗✗ | URL 会被记进浏览器历史、访问日志、代理日志 ✓ |
| 敏感字段不要打日志 ★★ | 日志往往比数据库更容易被看到 ⚠️ |
本节的实测 ✓:
输入:hello world
MD5 (16 字节 / 128 位):5eb63bbbe01eeed093cb22bb8f5acdc3
SHA-256 (32 字节 / 256 位):b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
把最后的 d 改成 e(只改 1 个字符):
4a44dc15364204a80fe80e9039455cc1 607db0c1f2d…(完全不同)
★ 256 位里有 140 位变了(约 55%)—— 这就是雪崩效应(理想值约 50%)✓★ 哈希的三个性质 ✓✓:
| 性质 | 意义 |
|---|---|
| 单向 ★★★ | 从哈希推不回原文 ——所以适合存密码 ✓ |
| 定长 ★★ | 任意长度输入 → 固定长度输出 ✓ |
| 雪崩 ★★★ | 输入改一点,输出面目全非 ——所以能防篡改检测 ✓✓ |
★ 两条重要区分 ✓✓:
| 说法 | 对错 |
|---|---|
| "哈希就是加密" ✗✗ | 加密可逆(有密钥能还原),哈希不可逆 ✓ |
| "MD5/SHA-1 可以用于安全用途" ✗✗ | 它们已被证明能构造碰撞,不要用于签名与密码 ✓ |
★ 哈希的两种正确用途 ✓:
| 用途 | 说明 |
|---|---|
| 完整性校验 ★★ | 下载文件后比对哈希值(但要确认哈希值本身没被改,所以还要签名)✓ |
| 密码存储 ★★★ | 但要"加盐 + 慢哈希"(见 5.3 节)✓ |
★ 三个递进的做法 ✓✓:
| 做法 | 效果 | 评价 |
|---|---|---|
| 明文存密码 ✗✗✗ | 拖库即全泄露 | 绝不允许 ✗ |
| 裸哈希(SHA-256) ✗✗ | 彩虹表 / 相同密码同哈希 | 不够 ✗ |
| 加盐 + 慢哈希 ★★★ | 每个用户盐不同 + 计算成本高 | 正确做法 ✓✓ |
★ "加盐"解决什么 ✓✓:
没有盐:密码 "123456" → 哈希永远是这个值 → 彩虹表一查就出来 ✗
有盐: hash("123456" + "随机盐A") → 每个用户的哈希都不同 ✓
攻击者要为每个盐单独算一遍 ✗✗★ "慢"解决什么 ✓✓:
单次 SHA-256:< 1 ms → 攻击者每秒能试上百万个密码 ✗✗
PBKDF2 21 万次:141 ms → 攻击者每秒只能试 7 个 ✓✓
看起来"慢"是缺点,实际上是**故意付出的成本**:
· 用户登录时多花 141 ms —— 无感 ✓
· 攻击者暴力破解 10 亿个密码 —— 需要 4500 年 ✗✗★ 正确做法(可以直接抄) ✓✓:
| 要点 | 说明 |
|---|---|
| 每个用户独立随机盐 ★★★ | 至少 16 字节,用 SecureRandom ✓ |
| 用专门的慢哈希 ★★★ | bcrypt / scrypt / Argon2 / PBKDF2 ✓ |
| 盐与参数跟哈希一起存 ★★ | 格式如 $pbkdf2-sha256$210000$<salt>$<hash> ✓ |
| 迭代次数随硬件升级 ★ | 每年评估一次,让单次耗时保持在 100~300 ms 量级 ✓ |
| 绝不自己设计哈希方案 ✗✗ | 用标准库 ✓ |
★ 最后一条最重要 ✓✓:密码学里,"自己设计"几乎等于"有漏洞" ✗
本节的实测 ✓:
明文: 转账 100 元给 Bob
密钥: (256 位,运行时随机生成)
IV: (12 字节随机数)
密文: (Base64 编码的乱码)
解密: 转账 100 元给 Bob (与原文一致:true)✓
篡改密文一个字节后再解密:解密失败:AEADBadTagException ✓✓★ 最后一行是关键 ✓✓ ——GCM 模式同时提供"加密"与"完整性校验" ✓:
| 模式 | 能防窃听 | 能防篡改 |
|---|---|---|
| CBC ⚠️ | ✓ | 不能(要额外加 HMAC)✗ |
| GCM ★★★ | ✓ | 能(自带认证标签)✓✓ |
| ECB ✗✗ | 弱 | 完全不能(相同明文块产生相同密文块,能看出图样)✗✗ |
★ 所以选模式的口诀 ✓✓:"用 GCM,别用 CBC,绝不用 ECB" ✓
★ 但对称加密有个死结 ✗✗:
要通信,双方得有同一把密钥
→ 那"怎么把密钥安全地给对方"?
→ 如果能把密钥安全地送过去,为什么不能直接把数据送过去? ✗★ 这就是"混合加密"存在的原因 ✓✓:
用非对称加密(RSA/ECDHE)**协商**出一把临时对称密钥
→ 之后所有数据用对称加密(AES)传 ✓✓★ HTTPS 就是这么做的 ✓(《HTTP 与 HTTPS》 的 TLS 握手就包含这一步)✓
本节的实测 ✓:
生成 2048 位密钥对耗时:(约百毫秒量级)
公钥加密 → 私钥解密:成功 ✓
私钥签名 → 公钥验签(原文):通过 ✓
私钥签名 → 公钥验签(被篡改):拒绝 ✓✓★★ 两种用法的方向相反(最容易记混) ✓✓:
| 目的 | 用哪把锁 | 保护什么 |
|---|---|---|
| 加密 ★★ | 公钥加密、私钥解密 | 机密性(谁都能发,只有我能看)✓ |
| 签名 ★★★ | 私钥签名、公钥验签 | 来源与完整性(只有我能发,谁都能验)✓✓ |
★ 记忆法 ✓✓:
"加密是"锁上门,谁都进不来";签名是"盖上我的章,谁都描不出" ✓
加密用对方的公钥(只有对方能开);签名用我的私钥(只有我能盖) ✓
★ 签名的四个现实用途 ✓:
| 用途 | 说明 |
|---|---|
| HTTPS 证书 ★★★ | CA 用私钥给"你的公钥"签名 ——这就是"身份验证"的来源 ✓✓ |
| JWT ★★ | 服务器用私钥签 token,资源服务器用公钥验 ✓ |
| 代码签名 ★ | 确认软件没被篡改 ✓ |
| 接口签名 ★★ | 防篡改 + 防重放(配合时间戳与随机数)✓ |
★ 两个性能现实 ✓✓:
| 现实 | 后果 |
|---|---|
| RSA 比 AES 慢几个数量级 ★★ | 只用它协商密钥或签名"摘要",数据用 AES ✓ |
| RSA 单次能加密的数据长度有限 ★★ | 2048 位约 245 字节 ——不能用来加密长内容 ✓ |
| 需求 | 用什么 |
|---|---|
| 传输加密 ★★★ | HTTPS(TLS) ——不要自己实现 ✓ |
| 存密码 ★★★ | bcrypt / Argon2 / PBKDF2 + 随机盐 ✓ |
| 校验文件完整性 ★★ | SHA-256 + 数字签名 ✓ |
| 对称加密数据 ★★ | AES-GCM ✓ |
| 加密长内容 ★★ | 混合加密(RSA 协商 + AES 传输) ✓ |
| 防篡改的 API 请求 ★★ | HMAC 签名 + 时间戳 + nonce ✓ |
| 自己设计加密算法 ✗✗✗ | 永远不要 ✓ |
byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 210_000, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();
// 存储:salt + iteration + hash(都要存,否则用户下次登录无法验证)byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// ★ iv 必须随密文一起传(它不需要保密,但不能重复使用)// ✗ 这些都不是加密
String encoded = Base64.getEncoder().encodeToString(data.getBytes()); // 只是编码
String hashed = md5(data); // 不可逆
String xored = xorWithKey(data, "secret"); // 可被轻易还原★ 一条判据 ✓:"不知道密钥的人能不能还原?" —— "不能"才叫加密 ✓
代码:labs/cs-basics/src/main/java/cn/kb/learn/cs/net/SecurityBasicsLesson.java
运行:
cd labs
.\mvnw.cmd -pl cs-basics compile
java "-Dstdout.encoding=UTF-8" -cp cs-basics/target/classes cn.kb.learn.cs.net.SecurityBasicsLesson实测输出(节选):
===== 1. 明文 HTTP 有多透明 =====
服务端(或任何链路上的人)看到的明文:
{"username":"alice","password":"P@ssw0rd123"}
===== 3. 密码存储:裸哈希 vs 加盐 + 慢哈希 =====
① 裸 SHA-256:用户 A 与用户 B 的哈希一模一样 ✗✗
② 加盐:同样的密码,哈希完全不同 ✓✓
③ PBKDF2 耗时:141 ms 单次 SHA-256 耗时:< 1 ms
===== 4. 对称加密:AES-GCM =====
篡改密文一个字节后再解密:解密失败:AEADBadTagException ✓✓
===== 5. 非对称加密与数字签名(RSA)=====
公钥加密 → 私钥解密:成功 ✓
私钥签名 → 公钥验签(原文):通过 ✓
私钥签名 → 公钥验签(被篡改):拒绝 ✓✓★ 四条可落地的结论 ✓:
| # | 结论 | 依据 |
|---|---|---|
| ① | 所有凭据必须走 HTTPS;敏感信息不进 URL 与日志 | 明文密码直接可见 ✗ |
| ② | 密码存储用"随机盐 + 慢哈希" | 141 ms vs 1 ms ✓ |
| ③ | 对称加密选 AES-GCM(自带防篡改) | 篡改被拒 ✓ |
| ④ | 加密用公钥、签名用私钥 | 两种方向别记混 ✓ |
Signature 的验签数据改一个字符:观察验签失败 ✓;MessageDigest 算一下自己名字的哈希,再改一个字符重算,观察完全不同 ✓。| # | 错误 | 说明 |
|---|---|---|
| 1 | 明文 HTTP 传密码 / Cookie ✗✗✗ | 中间人直接可见(5.1)✗ |
| 2 | 把 Base64 当加密 ✗✗ | 它只是编码,谁都能解 ✓ |
| 3 | 用 MD5/SHA-1 做安全用途 ✗✗ | 已被证明能构造碰撞 ✓ |
| 4 | 裸哈希存密码(不加盐) ✗✗ | 彩虹表 + 相同密码同哈希 ✓ |
| 5 | 用快哈希存密码 ✗✗ | 暴力破解成本太低 ✓ |
| 6 | AES 用 ECB 模式 ✗✗ | 能看出明文图样 ✓ |
| 7 | 自己设计加密算法 ✗✗✗ | 几乎必然有漏洞 ✓ |
| 8 | 密钥硬编码在代码里并提交到仓库 ✗✗✗ | 最常见的泄露方式 ⚠️ |
目标:亲手量出"加盐"与"慢哈希"的必要性。
步骤:
SecurityBasicsLesson,确认明文密码可见与 PBKDF2 的耗时 ✓;openssl 或 Java 生成一对 RSA 密钥,签名一段文字并验签 ✓。验收方式:能说出"为什么慢哈希能防暴力破解"(提示:把 141 ms 乘上密码空间)✓
扩展任务:实现一个"接口签名":
method + path + body + timestamp + nonce 算 HMAC-SHA256 ✓;String password = "123456";
String stored = md5(password);
if (md5(input).equals(stored)) { loginSuccess(); }这段代码有哪些问题? 5. (编程)实现一个安全的密码存储与验证:随机盐 + PBKDF2 + 盐与哈希一起保存,并写出验证方法。
equals 而非恒定时间比较(理论上存在时序攻击) ⚠️SecureRandom 生成盐 → PBKDF2WithHmacSHA256 → 存储 迭代次数$盐$哈希 ✓ 验证时用同样的盐与参数重算再比较 ✓S2 计算机基础全部完成(16 节)。接下来是 S4 企业开发进阶(Redis、消息队列、Nginx、微服务、容器与 CI/CD)。
暂无