Skip to content

网络安全基础 ​

S2-016选修进阶约 45 分钟JDK 21计算机基础 · 计算机网络

1. 本节定位 ​

《DNS、IP、端口与 ARP》解决了"找到对方";本节解决**"在不可信网络里保护数据"**:

text
明文 HTTP:密码原封不动出现在报文里 ✗✗
加密之后:  中间人只能看到乱码 ✓

★ 本节的四个实测都指向同一个结论:

别自己发明加密方案,用标准库 + 标准做法 ✓✓

2. 学习目标 ​

  • 能说出明文传输的实际风险,并知道什么必须走 HTTPS ★★★;
  • 知道哈希的三个性质,以及"哈希 ≠ 加密" ★★★;
  • 知道密码存储必须"加盐 + 慢哈希",并能说出原因 ★★★;
  • 知道对称与非对称加密的分工,以及"混合加密"为什么必要 ★★★;
  • 能区分"加密"与"签名"的方向 ★★★。

3. 前置知识检查 ​

  • 知道 HTTPS 做了哪三件事吗?(《HTTP 与 HTTPS》)✓
  • 会用 MessageDigest / Cipher 吗?✓

4. 为什么需要这个知识 ​

先看本节的实测 ✗✗:

text
客户端发送(http://):
    POST /login
    Body: {"username":"alice","password":"P@ssw0rd123"}

服务端(或任何链路上的人)看到的明文:
    {"username":"alice","password":"P@ssw0rd123"}

★ 密码原封不动地出现在报文里 ✗✗ —— 同一个 WiFi 下的任何人、路径上的任何一跳,都能直接读到 ✓

★ 再看一个更隐蔽的问题 ✗:

text
如果数据库被拖库,而密码是"裸 SHA-256 存储":
    用户 A 与用户 B 用同一个密码 → 哈希值一模一样 ✗
    攻击者拿彩虹表一查 → 直接得到原文 ✗✗

★ 本节的实测把这个对比量化出来 ✓:

text
① 裸 SHA-256:两个用户密码相同 → 哈希完全相同 ✗✗
② 加盐:每人随机 16 字节 → 哈希完全不同 ✓
③ PBKDF2(迭代 210000 次):141 ms    ← 故意慢 ✗
   单次 SHA-256:< 1 ms                ← 快得可以被暴力破解 ✗

5. 核心概念 ​

5.1 明文传输:什么都拦不住 ★★★ ​

★ 明文 HTTP 下暴露的东西 ✓:

暴露项攻击者能做什么
密码 ✗✗直接登录 ✓
会话 Cookie(JSESSIONID) ✗✗直接冒充登录状态 ——不需要密码 ✓✓
个人数据 ✗隐私泄露 ✓
请求内容 ✗篡改(中间人改金额、改收款账号) ✗✗

★★ 第二条最容易被低估 ✓✓:

会话 Cookie 泄露 = 账号被完全接管 ✗

而且攻击者不需要知道密码,甚至不需要你再次登录 ✓

★ 三条硬性要求 ✓✓:

要求说明
所有带凭据的请求必须 HTTPS ★★★登录、支付、任何带 Cookie 的请求 ✓
不要用 GET 传敏感参数 ✗✗URL 会被记进浏览器历史、访问日志、代理日志 ✓
敏感字段不要打日志 ★★日志往往比数据库更容易被看到 ⚠️

5.2 哈希:单向、定长、雪崩 ★★★ ​

本节的实测 ✓:

text
输入: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 节)✓

5.3 密码存储:加盐 + 慢哈希 ★★★ ​

★ 三个递进的做法 ✓✓:

做法效果评价
明文存密码 ✗✗✗拖库即全泄露绝不允许 ✗
裸哈希(SHA-256) ✗✗彩虹表 / 相同密码同哈希不够 ✗
加盐 + 慢哈希 ★★★每个用户盐不同 + 计算成本高正确做法 ✓✓

★ "加盐"解决什么 ✓✓:

text
没有盐:密码 "123456" → 哈希永远是这个值 → 彩虹表一查就出来 ✗
有盐:  hash("123456" + "随机盐A") → 每个用户的哈希都不同 ✓
        攻击者要为每个盐单独算一遍 ✗✗

★ "慢"解决什么 ✓✓:

text
单次 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 量级 ✓
绝不自己设计哈希方案 ✗✗用标准库 ✓

★ 最后一条最重要 ✓✓:密码学里,"自己设计"几乎等于"有漏洞" ✗

5.4 对称加密:AES-GCM ★★★ ​

本节的实测 ✓:

text
明文:  转账 100 元给 Bob
密钥:  (256 位,运行时随机生成)
IV:    (12 字节随机数)
密文:  (Base64 编码的乱码)

解密:  转账 100 元给 Bob  (与原文一致:true)✓
篡改密文一个字节后再解密:解密失败:AEADBadTagException ✓✓

★ 最后一行是关键 ✓✓ ——GCM 模式同时提供"加密"与"完整性校验" ✓:

模式能防窃听能防篡改
CBC ⚠️✓不能(要额外加 HMAC)✗
GCM ★★★✓能(自带认证标签)✓✓
ECB ✗✗弱完全不能(相同明文块产生相同密文块,能看出图样)✗✗

★ 所以选模式的口诀 ✓✓:"用 GCM,别用 CBC,绝不用 ECB" ✓

★ 但对称加密有个死结 ✗✗:

text
要通信,双方得有同一把密钥
→ 那"怎么把密钥安全地给对方"?
→ 如果能把密钥安全地送过去,为什么不能直接把数据送过去? ✗

★ 这就是"混合加密"存在的原因 ✓✓:

text
用非对称加密(RSA/ECDHE)**协商**出一把临时对称密钥
→ 之后所有数据用对称加密(AES)传 ✓✓

★ HTTPS 就是这么做的 ✓(《HTTP 与 HTTPS》 的 TLS 握手就包含这一步)✓

5.5 非对称加密与签名 ★★★ ​

本节的实测 ✓:

text
生成 2048 位密钥对耗时:(约百毫秒量级)

公钥加密 → 私钥解密:成功 ✓
私钥签名 → 公钥验签(原文):通过 ✓
私钥签名 → 公钥验签(被篡改):拒绝 ✓✓

★★ 两种用法的方向相反(最容易记混) ✓✓:

目的用哪把锁保护什么
加密 ★★公钥加密、私钥解密机密性(谁都能发,只有我能看)✓
签名 ★★★私钥签名、公钥验签来源与完整性(只有我能发,谁都能验)✓✓

★ 记忆法 ✓✓:

"加密是"锁上门,谁都进不来";签名是"盖上我的章,谁都描不出" ✓

加密用对方的公钥(只有对方能开);签名用我的私钥(只有我能盖) ✓

★ 签名的四个现实用途 ✓:

用途说明
HTTPS 证书 ★★★CA 用私钥给"你的公钥"签名 ——这就是"身份验证"的来源 ✓✓
JWT ★★服务器用私钥签 token,资源服务器用公钥验 ✓
代码签名 ★确认软件没被篡改 ✓
接口签名 ★★防篡改 + 防重放(配合时间戳与随机数)✓

★ 两个性能现实 ✓✓:

现实后果
RSA 比 AES 慢几个数量级 ★★只用它协商密钥或签名"摘要",数据用 AES ✓
RSA 单次能加密的数据长度有限 ★★2048 位约 245 字节 ——不能用来加密长内容 ✓

5.6 一张"该用什么"的对照表 ★★★ ​

需求用什么
传输加密 ★★★HTTPS(TLS) ——不要自己实现 ✓
存密码 ★★★bcrypt / Argon2 / PBKDF2 + 随机盐 ✓
校验文件完整性 ★★SHA-256 + 数字签名 ✓
对称加密数据 ★★AES-GCM ✓
加密长内容 ★★混合加密(RSA 协商 + AES 传输) ✓
防篡改的 API 请求 ★★HMAC 签名 + 时间戳 + nonce ✓
自己设计加密算法 ✗✗✗永远不要 ✓

6. 递进示例 ​

6.1 最小示例:把密码变成"慢哈希" ​

java
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(都要存,否则用户下次登录无法验证)

6.2 业务示例:AES-GCM 加解密 ​

java
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 必须随密文一起传(它不需要保密,但不能重复使用)

6.3 反例:自己"发明"加密 ​

java
// ✗ 这些都不是加密
String encoded = Base64.getEncoder().encodeToString(data.getBytes());   // 只是编码
String hashed = md5(data);                                              // 不可逆
String xored = xorWithKey(data, "secret");                              // 可被轻易还原

★ 一条判据 ✓:"不知道密钥的人能不能还原?" —— "不能"才叫加密 ✓

7. 完整可运行代码与实测 ​

代码:labs/cs-basics/src/main/java/cn/kb/learn/cs/net/SecurityBasicsLesson.java

运行:

powershell
cd labs
.\mvnw.cmd -pl cs-basics compile
java "-Dstdout.encoding=UTF-8" -cp cs-basics/target/classes cn.kb.learn.cs.net.SecurityBasicsLesson

实测输出(节选):

text
===== 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(自带防篡改)篡改被拒 ✓
④加密用公钥、签名用私钥两种方向别记混 ✓

8. Debug 观察点 ​

  1. 把 PBKDF2 的迭代次数从 210000 改到 1000:观察耗时骤降(安全性也骤降)★★;
  2. 把盐改成固定的(所有用户同一个):观察相同密码得到相同哈希 ✗✗;
  3. 把 AES 模式从 GCM 改成 ECB:加密一张有规律的图,观察密文仍能看出轮廓 ✗✗;
  4. 把 Signature 的验签数据改一个字符:观察验签失败 ✓;
  5. 用 MessageDigest 算一下自己名字的哈希,再改一个字符重算,观察完全不同 ✓。

9. 常见错误 ​

#错误说明
1明文 HTTP 传密码 / Cookie ✗✗✗中间人直接可见(5.1)✗
2把 Base64 当加密 ✗✗它只是编码,谁都能解 ✓
3用 MD5/SHA-1 做安全用途 ✗✗已被证明能构造碰撞 ✓
4裸哈希存密码(不加盐) ✗✗彩虹表 + 相同密码同哈希 ✓
5用快哈希存密码 ✗✗暴力破解成本太低 ✓
6AES 用 ECB 模式 ✗✗能看出明文图样 ✓
7自己设计加密算法 ✗✗✗几乎必然有漏洞 ✓
8密钥硬编码在代码里并提交到仓库 ✗✗✗最常见的泄露方式 ⚠️

10. 动手实验 ​

目标:亲手量出"加盐"与"慢哈希"的必要性。

步骤:

  1. 跑 SecurityBasicsLesson,确认明文密码可见与 PBKDF2 的耗时 ✓;
  2. 写一个"彩虹表"实验:对 1000 个常见密码算 SHA-256 存进 Map,然后用它反查一个密码 ★★;
  3. 加上随机盐后重做:验证彩虹表失效 ✓;
  4. 把 AES 模式换成 ECB,加密一张 100×100 的纯色图,观察密文仍保留色块边界 ✗✗;
  5. 用 openssl 或 Java 生成一对 RSA 密钥,签名一段文字并验签 ✓。

验收方式:能说出"为什么慢哈希能防暴力破解"(提示:把 141 ms 乘上密码空间)✓

扩展任务:实现一个"接口签名":

  • 对 method + path + body + timestamp + nonce 算 HMAC-SHA256 ✓;
  • 服务端校验签名 + 时间戳在 5 分钟内 + nonce 未用过 ✓;
  • 说明:这三样分别防什么(提示:篡改、重放、重复提交)★★

11. 练习题 ​

  1. 判断:Base64 编码可以保护数据不被看到。(对 / 错)
  2. 填空:哈希的三个性质是 、、______;密码存储要用 ______ + ______。
  3. 简答:为什么"慢哈希"反而更安全?
  4. (代码阅读)
java
String password = "123456";
String stored = md5(password);
if (md5(input).equals(stored)) { loginSuccess(); }

这段代码有哪些问题? 5. (编程)实现一个安全的密码存储与验证:随机盐 + PBKDF2 + 盐与哈希一起保存,并写出验证方法。

参考答案(要点)
  1. 错。Base64 只是编码,任何人 Base64 解码就能看到原文 ✗✗★
  2. 单向、定长、雪崩;随机盐、慢哈希 ✓
  3. 因为它把"每次尝试的成本"抬高了几千倍 ✓ ——用户登录多花 141 ms 无感,攻击者破解 10 亿密码要几千年 ✓✓
  4. ① 用 MD5(已被攻破) ✗;② 没有盐(相同密码同哈希,彩虹表可查) ✗✗; ③ 用普通哈希(太快,可暴力破解) ✗;④ 比较字符串用了 equals 而非恒定时间比较(理论上存在时序攻击) ⚠️
  5. 要点:SecureRandom 生成盐 → PBKDF2WithHmacSHA256 → 存储 迭代次数$盐$哈希 ✓ 验证时用同样的盐与参数重算再比较 ✓

12. 掌握标准 ​

  • [ ] 知道明文 HTTP 会暴露什么,尤其"Cookie 泄露 = 账号被接管" ★★★;
  • [ ] 能说出哈希的三个性质,并知道"哈希 ≠ 加密" ★★★;
  • [ ] 知道密码存储必须"随机盐 + 慢哈希",并能解释原因 ★★★;
  • [ ] 知道 AES 要用 GCM,绝不用 ECB ★★★;
  • [ ] 能说清"加密用公钥、签名用私钥" ★★★;
  • [ ] 知道"混合加密"为什么必要 ★★★;
  • [ ] 知道密钥不能硬编码进仓库 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

S2 计算机基础全部完成(16 节)。接下来是 S4 企业开发进阶(Redis、消息队列、Nginx、微服务、容器与 CI/CD)。

后续知识

暂无

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引