加密基础:对称加密、非对称加密、哈希与签名
安全相关知识不能只背“对称加密快,非对称加密安全”。这句话太浅,真正做项目时会遇到这些问题:
- 接口传输要不要加密。
- 数据库存手机号、身份证号、病案号要不要加密。
- JWT 为什么叫签名,不叫加密。
- HTTPS 为什么既用非对称加密,又用对称加密。
- 密码为什么不能“解密”,而是用哈希校验。
- 第三方接口为什么要“加密 + 签名 + 时间戳 + nonce”。
本章按零基础也能看懂的方式,把对称加密、非对称加密、哈希摘要、数字签名、HTTPS 混合加密、商业场景和 Java Demo 串起来。
先分清四个概念
| 概念 | 解决什么问题 | 是否能还原原文 | 常见算法 | 典型场景 |
|---|---|---|---|---|
| 对称加密 | 保护数据内容不被看见 | 能,用同一把密钥解密 | AES、SM4 | 字段加密、文件加密、HTTPS 数据传输 |
| 非对称加密 | 解决密钥分发、身份与签名 | 能,用另一把密钥解密 | RSA、ECC、SM2 | HTTPS 握手、加密 AES 密钥、验签 |
| 哈希摘要 | 证明内容有没有变 | 不能 | SHA-256、SHA-3、SM3 | 文件完整性、密码存储、数据指纹 |
| 数字签名 | 证明是谁发的、内容没被改 | 不能,签名不是加密 | RSA-PSS、ECDSA、SM2 | 开放接口签名、JWT 签名、软件包验签 |
最重要的边界:
加密解决“别人看不见内容”。
哈希解决“内容变没变”。
签名解决“是不是你发的,并且内容有没有被改”。
编码解决“怎么表示数据”,不是安全手段。Base64 只是编码,不是加密。任何人拿到 Base64 字符串都能还原。
flowchart TD
A["安全目标"] --> B{"要保护内容不被看见?"}
B -- "是" --> C["加密<br/>AES / RSA-OAEP"]
B -- "否" --> D{"要判断内容有没有被改?"}
D -- "是" --> E["哈希摘要<br/>SHA-256"]
D -- "否" --> F{"要证明发送方身份?"}
F -- "是" --> G["数字签名<br/>私钥签名 / 公钥验签"]
F -- "否" --> H["可能只是编码或普通传输"]对称加密是什么
对称加密是指加密和解密使用同一把密钥。
密文 = 加密算法(明文, 密钥)
明文 = 解密算法(密文, 同一把密钥)可以把它理解成:两个人共用同一把钥匙。A 用这把钥匙锁箱子,B 也用同一把钥匙打开箱子。
flowchart TD
A["明文:患者身份证号"] --> B["AES 加密"]
C["同一把密钥 key"] --> B
B --> D["密文:不可直接看懂"]
D --> E["AES 解密"]
C --> E
E --> F["恢复明文"]对称加密的优点
- 速度快。
- 适合大数据量。
- 算法成熟。
- 适合文件、字段、网络数据流加密。
例如 HTTPS 真正传输大量业务数据时,主要靠对称加密。
对称加密的核心问题:密钥怎么给对方
如果 A 和 B 还没有安全通道,A 直接把密钥发给 B,攻击者可能截获密钥。
flowchart TD
A["客户端生成 AES 密钥"] --> B["网络传输密钥"]
B --> C["服务端收到密钥"]
D["攻击者监听网络"] -. "截获密钥" .-> B
D --> E["后续密文都可能被解开"]所以对称加密本身不解决“安全分发密钥”的问题。这就是非对称加密登场的原因。
为什么推荐 AES-GCM
AES 是常见对称加密算法。使用时不要只写 AES,因为不同工作模式安全性差异很大。
推荐学习和项目里优先理解:
AES/GCM/NoPaddingGCM 的价值是:它不仅能加密内容,还能校验密文有没有被篡改。
| 模式 | 是否推荐 | 原因 |
|---|---|---|
| AES/ECB | 不推荐 | 相同明文块会得到相同密文块,会泄露模式 |
| AES/CBC | 可用但要谨慎 | 需要随机 IV,还需要额外完整性校验 |
| AES/GCM | 推荐 | 同时提供机密性和完整性校验 |
ECB 的问题可以这样理解:如果一张图片中有大量重复块,ECB 加密后仍可能看出轮廓。业务数据虽然不是图片,但相同明文生成相同密文,会泄露重复模式。
AES-GCM Java Demo
下面 Demo 演示:
- 生成 AES 密钥。
- 使用随机 IV 加密。
- 把 IV 和密文一起保存。
- 解密时取出 IV。
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class AesGcmDemo {
private static final int IV_LENGTH = 12;
private static final int TAG_LENGTH_BIT = 128;
private static final SecureRandom RANDOM = new SecureRandom();
public static void main(String[] args) throws Exception {
SecretKey key = generateKey();
String plainText = "患者身份证号: 110101199001011234";
String encrypted = encrypt(plainText, key.getEncoded());
String decrypted = decrypt(encrypted, key.getEncoded());
System.out.println(encrypted);
System.out.println(decrypted);
}
public static SecretKey generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
return keyGenerator.generateKey();
}
public static String encrypt(String plainText, byte[] keyBytes) throws Exception {
byte[] iv = new byte[IV_LENGTH];
RANDOM.nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
ByteBuffer buffer = ByteBuffer.allocate(iv.length + cipherText.length);
buffer.put(iv);
buffer.put(cipherText);
return Base64.getEncoder().encodeToString(buffer.array());
}
public static String decrypt(String encryptedText, byte[] keyBytes) throws Exception {
byte[] allBytes = Base64.getDecoder().decode(encryptedText);
ByteBuffer buffer = ByteBuffer.wrap(allBytes);
byte[] iv = new byte[IV_LENGTH];
buffer.get(iv);
byte[] cipherText = new byte[buffer.remaining()];
buffer.get(cipherText);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
byte[] plainBytes = cipher.doFinal(cipherText);
return new String(plainBytes, StandardCharsets.UTF_8);
}
}关键点:
| 点 | 为什么 |
|---|---|
使用 SecureRandom | 加密随机数不能用普通 Random |
| GCM 推荐 12 字节 IV | 常见实现性能和安全性更合适 |
| 同一密钥下 IV 不能重复 | GCM 重复 IV 会造成严重安全风险 |
| 保存 IV 不等于泄露密钥 | IV 可以和密文一起保存,密钥必须保密 |
| 密钥不要硬编码 | 要放 KMS、配置加密系统、环境变量或密钥管理平台 |
对称加密适合什么场景
| 场景 | 是否适合 | 说明 |
|---|---|---|
| 数据库敏感字段加密 | 适合 | 手机号、身份证号、病案号等可用 AES 加密 |
| 文件内容加密 | 适合 | 文件较大,对称加密性能好 |
| HTTPS 数据传输 | 适合 | TLS 握手后用对称密钥传输数据 |
| 密码存储 | 不适合 | 密码应该用慢哈希,不能可逆加密 |
| 接口身份认证 | 不单独适合 | 还需要签名、时间戳、nonce、防重放 |
对称加密用错会怎样
| 错误做法 | 后果 | 正确做法 |
|---|---|---|
| 使用 ECB 模式 | 相同明文暴露相同密文模式 | 使用 GCM |
| IV 固定或重复 | 可能泄露明文关系甚至破坏安全性 | 每次加密生成随机 IV |
| 密钥写在代码里 | 代码泄露等于密钥泄露 | 用密钥管理系统 |
| 多环境共用密钥 | 测试环境泄露影响生产 | 按环境隔离密钥 |
| 以为加密后无需权限 | 拿到密钥仍可解密 | 加密、权限、审计一起做 |
| 密文不能轮换密钥 | 密钥泄露后难恢复 | 设计 keyVersion |
非对称加密是什么
非对称加密使用一对密钥:
- 公钥:可以公开。
- 私钥:必须自己保存,不能泄露。
常见用法有两类:
| 用法 | 谁操作 | 用什么密钥 | 目的 |
|---|---|---|---|
| 公钥加密,私钥解密 | 发送方加密,接收方解密 | 公钥加密,私钥解密 | 只有私钥持有者能看到内容 |
| 私钥签名,公钥验签 | 发送方签名,接收方验签 | 私钥签名,公钥验签 | 证明消息确实来自私钥持有者 |
flowchart TD
A["服务端生成密钥对"] --> B["公钥发给客户端"]
A --> C["私钥服务端自己保存"]
D["客户端用公钥加密数据"] --> E["密文"]
E --> F["服务端用私钥解密"]非对称加密解决了“可以把公钥公开给别人”的问题。别人拿到公钥只能加密或验签,不能推出私钥。
非对称加密的优点
- 公钥可以公开,降低密钥分发难度。
- 可以做数字签名,证明身份。
- 适合密钥交换、接口验签、证书体系。
非对称加密的缺点
- 速度比对称加密慢。
- 不适合直接加密大文件、大 JSON。
- 加密数据长度有限。
- 私钥管理要求很高。
所以真实系统不会用 RSA 直接加密整个大报文,而是采用“混合加密”。
RSA-OAEP Java Demo
下面 Demo 演示公钥加密、私钥解密。注意使用 OAEP 填充,不要用老旧的 RSA/ECB/PKCS1Padding 作为新系统首选。
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
public class RsaEncryptDemo {
public static void main(String[] args) throws Exception {
KeyPair keyPair = generateKeyPair();
String plainText = "AES会话密钥或小段敏感数据";
String encrypted = encrypt(plainText, keyPair.getPublic());
String decrypted = decrypt(encrypted, keyPair.getPrivate());
System.out.println(encrypted);
System.out.println(decrypted);
}
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
return generator.generateKeyPair();
}
public static String encrypt(String plainText, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(cipherText);
}
public static String decrypt(String encryptedText, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] plainBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
return new String(plainBytes, StandardCharsets.UTF_8);
}
}这里的 ECB 是 Java 对 RSA transformation 的历史命名,不等同于 AES 的 ECB 分组模式。实际关注点是 RSA 的填充方式,推荐使用 OAEP。
数字签名是什么
签名不是加密。签名不隐藏内容,它解决的是:
- 消息是不是私钥持有者发的。
- 消息传输中有没有被篡改。
- 发送方事后难以否认。
签名流程:
flowchart TD
A["原始报文"] --> B["计算摘要 SHA-256"]
B --> C["用私钥对摘要签名"]
C --> D["签名值"]
A --> E["报文 + 签名一起发送"]
D --> E
E --> F["接收方用公钥验签"]
F --> G{"验签是否通过"}
G -- "通过" --> H["确认来源可信且内容未改"]
G -- "失败" --> I["拒绝请求"]RSA 签名验签 Java Demo
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;
public class RsaSignatureDemo {
public static void main(String[] args) throws Exception {
KeyPair keyPair = generateKeyPair();
String body = "{\"batchNo\":\"B202607010001\",\"hospitalCode\":\"H001\"}";
String sign = sign(body, keyPair.getPrivate());
boolean ok = verify(body, sign, keyPair.getPublic());
System.out.println(sign);
System.out.println(ok);
}
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
return generator.generateKeyPair();
}
public static String sign(String body, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(body.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature.sign());
}
public static boolean verify(String body, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(body.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.getDecoder().decode(sign));
}
}如果报文被改了,验签就会失败。如果公钥不是对应私钥生成的,验签也会失败。
对称加密和非对称加密对比
| 对比项 | 对称加密 | 非对称加密 |
|---|---|---|
| 密钥数量 | 一把共享密钥 | 一对公私钥 |
| 性能 | 快 | 慢 |
| 加密大数据 | 适合 | 不适合 |
| 密钥分发 | 困难 | 公钥可公开,分发更容易 |
| 典型算法 | AES、SM4 | RSA、ECC、SM2 |
| 典型用途 | 数据内容加密 | 密钥交换、签名、证书 |
| 最大风险 | 共享密钥泄露 | 私钥泄露 |
不要说“非对称加密一定比对称加密安全”。它们解决的问题不同:
对称加密适合大量数据保密。
非对称加密适合密钥分发和身份可信。为什么 HTTPS 两种都用
HTTPS/TLS 使用的是混合思路:
- 用非对称加密和证书体系解决“连接的服务端是不是真的”以及“如何安全协商密钥”。
- 握手完成后,用对称加密传输业务数据。
flowchart TD
A["客户端访问 https://api.example.com"] --> B["服务端返回证书和公钥信息"]
B --> C["客户端校验证书链和域名"]
C --> D{"证书是否可信"}
D -- "否" --> E["浏览器或客户端报错"]
D -- "是" --> F["协商会话密钥"]
F --> G["后续 HTTP 数据用对称密钥加密传输"]为什么不全用非对称加密传数据?因为太慢,而且不适合大数据量。
为什么不只用对称加密?因为客户端一开始不知道安全密钥怎么传给服务端。
HTTPS 就是把两者组合起来:非对称负责建立信任和协商密钥,对称负责高效传输数据。
混合加密:商业接口常见方案
第三方接口常见报文安全方案:
- 客户端生成随机 AES 密钥。
- 用 AES 加密业务报文。
- 用服务端 RSA 公钥加密 AES 密钥。
- 用客户端私钥对报文摘要签名。
- 服务端用私钥解出 AES 密钥。
- 服务端解密报文。
- 服务端用客户端公钥验签。
- 校验时间戳和 nonce,防止重放。
flowchart TD
A["客户端生成 AES key"] --> B["AES 加密业务报文"]
A --> C["服务端 RSA 公钥加密 AES key"]
B --> D["计算报文摘要"]
D --> E["客户端私钥签名"]
B --> F["发送 encryptedData"]
C --> F
E --> F
F --> G["服务端 RSA 私钥解出 AES key"]
G --> H["AES 解密业务报文"]
H --> I["服务端用客户端公钥验签"]
I --> J["校验 timestamp + nonce"]
J --> K["处理业务"]这种方案解决三个问题:
| 安全目标 | 靠什么实现 |
|---|---|
| 内容保密 | AES 加密业务报文 |
| AES 密钥安全传输 | RSA 公钥加密 AES key |
| 防篡改和身份确认 | 私钥签名、公钥验签 |
| 防重放 | timestamp + nonce + Redis 去重 |
混合加密请求结构示例
{
"appId": "hospital-a",
"timestamp": 1782892800000,
"nonce": "8f1c6d2a0c7b4a4d",
"encryptedKey": "RSA加密后的AES密钥",
"encryptedData": "AES-GCM加密后的业务报文",
"sign": "客户端私钥签名"
}服务端处理顺序建议:
flowchart TD
A["收到请求"] --> B["校验 appId 是否存在"]
B --> C["校验 timestamp 是否在允许窗口"]
C --> D["校验 nonce 是否已使用"]
D --> E["用服务端私钥解 encryptedKey"]
E --> F["用 AES key 解 encryptedData"]
F --> G["按约定字段重建待签名字符串"]
G --> H["用客户端公钥验签"]
H --> I{"验签是否通过"}
I -- "否" --> J["拒绝请求并告警"]
I -- "是" --> K["进入业务处理"]
K --> L["记录 nonce 防重放"]注意:签名字段要有稳定排序规则。JSON 原始字符串如果格式化、字段顺序、空格不同,签名可能不一致。项目里常用“按字段名排序后拼接”或“对规范化 JSON 签名”。
哈希摘要不是加密
哈希是单向的,不能解密。
digest = SHA-256(原文)同样输入得到同样摘要;输入改一点,摘要会完全变化。
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
public class Sha256Demo {
public static void main(String[] args) throws Exception {
System.out.println(sha256("hello"));
System.out.println(sha256("hello!"));
}
public static String sha256(String text) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] bytes = digest.digest(text.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(bytes);
}
}适合场景:
- 文件完整性校验。
- 请求参数摘要。
- 数据指纹。
- 密码哈希的基础组成。
不适合场景:
- 需要还原明文的数据。
- 直接
SHA-256(password)存密码。
密码存储需要慢哈希和盐,例如 BCrypt、Argon2、PBKDF2。Spring Security 中常用 BCryptPasswordEncoder。密码不能用 AES 存,因为 AES 是可逆的,密钥泄露后所有密码都能被还原。
HMAC:共享密钥签名
HMAC 可以理解成“带密钥的哈希”。双方共享同一个 secret,用它计算签名。
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class HmacDemo {
public static void main(String[] args) throws Exception {
String secret = "shared-secret";
String body = "batchNo=B202607010001×tamp=1782892800000";
System.out.println(hmacSha256(body, secret));
}
public static String hmacSha256(String data, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] sign = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign);
}
}HMAC 常用于内部接口签名。它比 RSA 签名简单、速度快,但双方都要保存同一个 secret。任何一方泄露 secret,攻击者就能伪造签名。
JWT 是加密还是签名
普通 JWT 通常是签名,不是加密。
JWT 内容由三段组成:
header.payload.signatureheader 和 payload 通常只是 Base64Url 编码,不是加密。任何人拿到 Token 都能解码看到 payload。因此 JWT 里不要放身份证号、密码、手机号、病历内容等敏感信息。
JWT 签名的作用是:服务端收到 Token 后,可以验证它有没有被篡改。
flowchart TD
A["JWT header + payload"] --> B["服务端密钥或私钥签名"]
B --> C["生成 signature"]
C --> D["客户端携带 JWT"]
D --> E["服务端重新验签"]
E --> F{"签名是否一致"}
F -- "一致" --> G["Token 未被篡改"]
F -- "不一致" --> H["拒绝请求"]如果想让 JWT 内容也不可见,需要使用 JWE 或者不要把敏感数据放进 Token。
医疗数据采集平台怎么用
医疗数据涉及患者标识、就诊记录、检查检验结果、数据资产权限,安全设计不能只靠“用了 HTTPS”。
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 医院接口到平台传输 | HTTPS + 请求签名 + timestamp + nonce | 防窃听、防篡改、防重放 |
| 极敏感字段落库 | AES-GCM 字段加密 + keyVersion | 数据库泄露时降低明文暴露风险 |
| 用户密码 | BCrypt / Argon2 等慢哈希 | 密码不应可逆 |
| JWT 登录 | 签名 + 短有效期 + 黑名单 | 防篡改,支持撤销 |
| 文件上传 | 文件摘要 + 病毒扫描 + 权限控制 | 防篡改和越权下载 |
| 配置密钥 | 配置加密或 KMS | 防止密钥散落在配置文件 |
| 开放 API | 非对称签名或 HMAC | 证明调用方身份 |
接口签名字段示例
待签名字符串:
appId=hospital-a
&encryptedData=...
&nonce=8f1c6d2a0c7b4a4d
×tamp=1782892800000服务端必须校验:
appId是否有效。timestamp是否过期,例如只允许 5 分钟内。nonce是否已使用,使用 Redis 记录短期 nonce。- 签名是否通过。
- 解密后的业务数据是否符合 schema。
- 当前 appId 是否有权限提交这个医院的数据。
只做加密不做签名,攻击者可能无法看懂内容,但仍可能替换密文或重放旧请求。只做签名不做加密,能防篡改但不能防止内容被看见。所以要按场景组合。
防重放 Demo
public class ReplayProtector {
private final StringRedisTemplate redisTemplate;
public ReplayProtector(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void check(String appId, long timestamp, String nonce) {
long now = System.currentTimeMillis();
if (Math.abs(now - timestamp) > 5 * 60 * 1000) {
throw new SecurityException("请求已过期");
}
String key = "api:nonce:" + appId + ":" + nonce;
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", Duration.ofMinutes(5));
if (!Boolean.TRUE.equals(success)) {
throw new SecurityException("重复请求");
}
}
}这个 Demo 的重点不是 Redis API,而是防重放思想:同一个 appId + nonce 在有效时间窗口内只能使用一次。
密钥管理原则
| 原则 | 解释 |
|---|---|
| 不硬编码 | 不把密钥写在 Java 代码、前端代码、Git 仓库 |
| 分环境隔离 | dev、test、prod 不共用密钥 |
| 最小权限 | 服务只能拿自己需要的密钥 |
| 定期轮换 | 支持 keyVersion,旧密钥逐步淘汰 |
| 审计访问 | 谁在什么时候读取了密钥要可追踪 |
| 泄露预案 | 密钥泄露后能禁用、换新、重加密 |
字段加密落库建议保存 keyVersion:
create table t_patient_secure_info (
id bigint primary key,
patient_id bigint not null,
id_card_cipher varchar(512) not null,
phone_cipher varchar(512),
key_version varchar(32) not null,
created_at datetime not null,
updated_at datetime not null
);这样密钥轮换时,可以知道这条数据用哪一版密钥加密。
常见误区
| 误区 | 为什么错 | 正确理解 |
|---|---|---|
| Base64 是加密 | Base64 可以直接解码 | 它只是编码 |
| MD5 加盐就适合存密码 | MD5 太快,容易被撞库 | 用 BCrypt/Argon2/PBKDF2 |
| RSA 比 AES 更安全,所以都用 RSA | RSA 慢且加密长度有限 | RSA 用于密钥交换/签名,AES 加密数据 |
| HTTPS 之后不用签名 | HTTPS 保护传输链路,不一定解决业务级防重放和调用方身份 | 开放 API 仍常用签名 |
| JWT 内容别人看不到 | JWT payload 通常只是 Base64Url | 不放敏感信息 |
| 加密后就不用权限控制 | 有密钥仍能解密 | 加密、鉴权、审计都要有 |
| 前端保存私钥 | 前端环境无法保密 | 私钥必须在可信服务端或安全硬件中 |
面试回答模板
对称加密和非对称加密解决的问题不同。对称加密使用同一把密钥加解密,性能高,适合加密大量数据,比如字段、文件和 HTTPS 传输数据,常见算法是 AES。它的问题是密钥分发困难。非对称加密使用公钥和私钥,公钥可以公开,私钥必须保密,适合密钥交换和数字签名,比如 RSA、ECC。非对称加密性能较低,所以生产中常用混合加密:用非对称加密协商或保护对称密钥,再用对称加密传输业务数据。哈希不是加密,不能解密;签名也不是加密,它用于证明消息来源和防篡改。项目话术:
在医疗数据采集平台中,医院接口到平台的链路会走 HTTPS;对开放接口还会加 appId、timestamp、nonce 和签名,防止篡改和重放。业务报文如果涉及敏感字段,可以用 AES-GCM 做字段或报文加密,AES 密钥通过密钥管理系统管理,必要时用 RSA 保护会话密钥。用户密码不会用可逆加密,而是用 BCrypt 这类慢哈希存储。本章小结
对称加密、非对称加密、哈希和签名不是互相替代关系,而是各司其职:
- 数据内容不想被看见,用加密。
- 大量数据加密,用对称加密。
- 密钥分发、签名验签,用非对称加密。
- 内容完整性,用哈希摘要。
- 证明身份和防篡改,用数字签名。
- 防重放,要加时间戳、nonce 和服务端去重。
真正安全的系统不是“用了某个算法”,而是算法选择、密钥管理、权限控制、审计、轮换和补偿都设计完整。
