信息安全 商业场景训练营
这页把信息安全放到真实商业系统里学习。目标不是背 AES、RSA、SHA、JWT、TLS 这些名词,而是能解释一个开放接口如何防窃听、防篡改、防重放,敏感字段如何加密和脱敏,密码为什么不能可逆加密,密钥泄露后怎么止血和轮换。
训练目标
学完这页,你要能做到:
- 区分编码、哈希、对称加密、非对称加密、数字签名、HMAC。
- 解释 HTTPS/TLS 为什么使用“非对称加密协商密钥 + 对称加密传输数据”。
- 设计开放接口的 appId、timestamp、nonce、bodyHash、signature 防篡改和防重放方案。
- 设计身份证、手机号、银行卡等敏感字段的加密、脱敏、查询和审计方案。
- 解释密码为什么用 BCrypt/Argon2/PBKDF2 慢哈希,而不是 AES、RSA、MD5。
- 解释 JWT 是签名 Token,不是默认加密,payload 不能放敏感信息。
- 设计密钥版本、轮换、禁用、泄露应急和审计。
- 排查签名失败、时间戳失败、nonce 重复、证书过期、密钥版本不一致。
商业安全总图
以“医院系统调用医疗数据采集平台开放接口”为例:
mermaid
flowchart TD
A["医院系统"] --> B["HTTPS 传输防窃听"]
B --> C["appId 识别调用方"]
C --> D["timestamp + nonce 防重放"]
D --> E["bodyHash 防正文被改"]
E --> F["HMAC/RSA 验签防篡改"]
F --> G["业务权限校验"]
G --> H["敏感字段加密落库"]
H --> I["脱敏展示和审计日志"]注意:HTTPS 保护传输链路,接口签名保护业务报文完整性和调用方身份,两者不是互相替代关系。很多开放平台同时使用 HTTPS 和业务签名。
基础概念边界
| 概念 | 是否可逆 | 解决什么 | 例子 |
|---|---|---|---|
| 编码 | 可逆 | 数据表示转换 | Base64、URL Encode |
| 哈希 | 不可逆 | 摘要、完整性校验 | SHA-256、SM3 |
| HMAC | 不可逆 | 带密钥的完整性校验 | HMAC-SHA256 |
| 对称加密 | 可逆 | 用同一密钥加解密数据 | AES、SM4 |
| 非对称加密 | 可逆 | 公钥加密、私钥解密 | RSA、SM2 |
| 数字签名 | 不可逆校验 | 私钥签名、公钥验签 | RSA-SHA256 |
| TLS | 协议 | 保护传输链路 | HTTPS |
常见误区:
- Base64 不是加密,任何人都能解码。
- MD5/SHA 不是加密,是哈希。
- 密码不应该用 AES 加密保存,因为 AES 可逆。
- JWT 普通 payload 只是 Base64Url 编码,不是加密。
- RSA 不适合加密大报文,通常用于签名或密钥协商。
HTTPS/TLS 为什么这样设计
如果所有数据都用 RSA 加密,性能差且有长度限制;如果只用 AES,又无法安全把 AES 密钥交给对方。TLS 使用混合思路:
mermaid
flowchart TD
A["客户端发起握手"] --> B["服务端返回证书"]
B --> C["客户端验证证书链"]
C --> D["协商会话密钥"]
D --> E["后续数据使用对称加密传输"]为什么安全:
- 证书证明服务端身份,防止访问假站点。
- 非对称能力用于安全协商密钥。
- 对称加密用于大量业务数据传输,性能更好。
- 完整性校验防止密文被篡改。
证书过期、域名不匹配、信任链不完整都会导致 HTTPS 失败。
Demo 一:开放接口签名
请求头设计
text
X-App-Id: hospital-a
X-Timestamp: 1720000000000
X-Nonce: 6f0b9d6d8f5a4d5a
X-Body-Hash: base64(sha256(body))
X-Signature: base64(hmacSha256(canonicalString, secret))签名原文
text
HTTP_METHOD
PATH
QUERY_STRING
TIMESTAMP
NONCE
BODY_HASH示例:
text
POST
/api/open/collect/push
hospitalId=1001
1720000000000
6f0b9d6d8f5a4d5a
5xY...bodyHash为什么要规范化签名原文?
- 客户端和服务端必须对同一串内容签名。
- 参数顺序、大小写、空格、编码不一致都会导致验签失败。
- 规范化可以减少“我签了但你验不过”的问题。
Java HMAC Demo
java
public final class SignUtils {
public static String hmacSha256Base64(String text, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(
secret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(key);
byte[] result = mac.doFinal(text.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(result);
} catch (GeneralSecurityException ex) {
throw new IllegalStateException("签名失败", ex);
}
}
public static String sha256Base64(byte[] body) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return Base64.getEncoder().encodeToString(digest.digest(body));
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("摘要失败", ex);
}
}
}服务端验签流程
mermaid
flowchart TD
A["收到请求"] --> B["检查 appId 是否存在"]
B --> C["检查 timestamp 是否在窗口内"]
C --> D["检查 nonce 是否已使用"]
D --> E["计算 bodyHash"]
E --> F["重建 canonicalString"]
F --> G["用 secret 计算签名"]
G --> H{"是否一致"}
H -- "否" --> I["拒绝请求"]
H -- "是" --> J["保存 nonce 并进入业务"]nonce 要存 Redis:
java
String nonceKey = "openapi:nonce:" + appId + ":" + nonce;
Boolean first = redisTemplate.opsForValue()
.setIfAbsent(nonceKey, "1", Duration.ofMinutes(5));
if (!Boolean.TRUE.equals(first)) {
throw new SecurityException("重复请求");
}如果没有 timestamp 和 nonce,攻击者抓到一次合法请求后可以反复重放。
Demo 二:敏感字段加密和脱敏
字段加密设计
sql
create table patient_info (
id bigint primary key,
patient_no varchar(64) not null,
id_card_cipher text not null,
id_card_hash varchar(64) not null,
phone_cipher text not null,
phone_hash varchar(64) not null,
key_version int not null,
created_at datetime not null,
updated_at datetime not null,
index idx_id_card_hash (id_card_hash),
index idx_phone_hash (phone_hash)
);为什么同时存 cipher 和 hash?
cipher用于需要授权查看明文时解密。hash用于等值查询,例如按身份证查人。- 加密后的密文通常不能直接做普通索引等值查询。
- hash 要加盐或使用安全方案,避免被彩虹表反推。
Java AES-GCM Demo
java
public class FieldCryptoService {
private final KeyProvider keyProvider;
public EncryptedValue encrypt(String plainText) {
int version = keyProvider.currentVersion();
SecretKey key = keyProvider.getAesKey(version);
byte[] iv = SecureRandom.getInstanceStrong().generateSeed(12);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return new EncryptedValue(
version,
Base64.getEncoder().encodeToString(iv),
Base64.getEncoder().encodeToString(encrypted)
);
}
}GCM 提供机密性和完整性校验。不要自己发明加密模式,不要固定 IV,不要把密钥写死在代码里。
脱敏展示
java
public String maskIdCard(String idCard) {
if (idCard == null || idCard.length() < 8) {
return "****";
}
return idCard.substring(0, 4) + "**********" + idCard.substring(idCard.length() - 4);
}脱敏不是加密。脱敏是展示层减少泄露,加密是存储层保护数据。
密码为什么不能可逆加密
密码校验不需要知道原文,只需要判断用户输入是否匹配。因此应该使用慢哈希:
java
PasswordEncoder encoder = new BCryptPasswordEncoder();
String hash = encoder.encode(rawPassword);
boolean matches = encoder.matches(inputPassword, hash);为什么不用 MD5:
- 太快,容易被暴力破解。
- 常见密码有彩虹表。
- 没有合适工作因子。
为什么不用 AES:
- AES 可逆,密钥泄露后所有密码明文暴露。
- 密码系统应该避免任何人能解密出原密码。
JWT 安全边界
JWT 结构:
text
header.payload.signaturepayload 是 Base64Url 编码,不是加密。不要放:
- 身份证号。
- 手机号。
- 密码。
- 详细权限列表。
- 医疗敏感数据。
适合放:
- userId。
- tenantId。
- tokenVersion。
- issuer。
- expireAt。
JWT 泄露后要能撤销,可以用短 access token、refresh token、Redis 黑名单或 tokenVersion。
密钥管理
mermaid
flowchart TD
A["生成密钥"] --> B["存入 KMS 或密钥系统"]
B --> C["应用按权限读取"]
C --> D["使用 keyVersion 加密"]
D --> E["定期轮换新版本"]
E --> F["旧数据逐步重加密"]
F --> G["禁用旧密钥"]密钥原则:
- 不进 Git。
- 不打印日志。
- 不写死在镜像。
- 最小权限访问。
- 分环境隔离。
- 定期轮换。
- 泄露后能禁用和重加密。
生产排查流程
签名失败
mermaid
flowchart TD
A["签名失败"] --> B["appId 是否正确"]
B --> C["secret 是否同版本"]
C --> D["HTTP method/path 是否一致"]
D --> E["query 参数排序是否一致"]
E --> F["bodyHash 是否一致"]
F --> G["编码和换行是否一致"]重放校验失败
mermaid
flowchart TD
A["重放校验失败"] --> B["timestamp 是否超窗口"]
B --> C["服务器时间是否同步"]
C --> D["nonce 是否重复"]
D --> E["Redis nonce key 是否冲突"]HTTPS 失败
mermaid
flowchart TD
A["HTTPS 失败"] --> B["证书是否过期"]
B --> C["域名是否匹配"]
C --> D["证书链是否完整"]
D --> E["客户端是否信任 CA"]
E --> F["TLS 版本和加密套件是否支持"]面试标准回答
对称加密和非对称加密区别
text
对称加密加密和解密使用同一把密钥,速度快,适合大量数据加密,例如 AES、SM4;非对称加密使用公钥和私钥,公钥加密私钥解密,或私钥签名公钥验签,速度较慢,适合身份认证、签名或密钥协商,例如 RSA、SM2。HTTPS 会用非对称能力协商会话密钥,再用对称加密传输数据。接口签名怎么防篡改和防重放
text
开放接口通常用 appId 识别调用方,用 timestamp 限制请求时间窗口,用 nonce 保证同一请求不能重复使用,用 bodyHash 保证正文没被改,再把 method、path、query、timestamp、nonce、bodyHash 组成规范字符串,用 HMAC 或 RSA 签名。服务端重建同样字符串验签,验签通过后把 nonce 写入 Redis,防止抓包重放。密码为什么不能加密保存
text
密码校验不需要还原明文,只需要判断输入是否匹配。可逆加密意味着密钥泄露后所有密码都会变成明文,所以密码应该用 BCrypt、Argon2、PBKDF2 这类带盐慢哈希保存,而不是 AES、RSA 或 MD5。