密码学实战:对称加密、非对称加密、签名验签与证书体系
2026/6/27大约 9 分钟
密码学实战:对称加密、非对称加密、签名验签与证书体系
面试被问"RSA 和 AES 区别"答不清?HTTPS 握手过程讲不明白?签名和加密搞混了?从底层原理到 Java 代码,一篇讲透密码学核心。
一、密码学三大分支
对称加密(Symmetric) → 加密解密用同一把密钥
AES、DES、ChaCha20
非对称加密(Asymmetric) → 公钥加密,私钥解密
RSA、ECC、DSA
哈希算法(Hash) → 不可逆,验证完整性
SHA-256、MD5、SHA-3
衍生:数字签名 = 非对称加密 + 哈希
衍生:证书 = 公钥 + 身份 + 签名
衍生:密钥交换 = 非对称加密协商对称密钥二、对称加密
2.1 AES 原理
AES(Advanced Encryption Standard):
分组加密:每次加密 16 字节(128 bit)数据
密钥长度:128 / 192 / 256 bit
加密轮数:10 / 12 / 14 轮
加密流程:
明文 (16字节) → 初始轮密钥加 → 9轮(字节替换+行移位+列混淆+轮密钥加) → 最终轮 → 密文
字节替换(SubBytes):S 盒替换,非线性变换
行移位(ShiftRows):行循环移位,扩散
列混淆(MixColumns):矩阵乘法,扩散
轮密钥加(AddRoundKey):与轮密钥异或2.2 加密模式
ECB(Electronic Codebook):
每块独立加密 → 相同明文块产生相同密文块 → 不安全 ❌
CBC(Cipher Block Chaining):
每块与前一块密文异或再加密 → 需要IV → 串行 ❌(慢)
CTR(Counter):
加密计数器与明文异或 → 可并行 → 推荐 ✅
GCM(Galois/Counter Mode):
CTR + 认证标签 → 加密+完整性 → 最推荐 ✅✅2.3 Java 实现
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
public class AESUtil {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final int KEY_SIZE = 256; // AES-256
private static final int IV_SIZE = 12; // GCM 推荐 12 字节 IV
private static final int TAG_SIZE = 128; // 认证标签 128 bit
// 生成密钥
public static SecretKey generateKey() throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM);
kg.init(KEY_SIZE, new SecureRandom());
return kg.generateKey();
}
// 加密
public static String encrypt(String plaintext, SecretKey key) throws Exception {
byte[] iv = new byte[IV_SIZE];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
GCMParameterSpec spec = new GCMParameterSpec(TAG_SIZE, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
// IV + 密文拼接返回
byte[] result = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(result);
}
// 解密
public static String decrypt(String encrypted, SecretKey key) throws Exception {
byte[] data = Base64.getDecoder().decode(encrypted);
byte[] iv = new byte[IV_SIZE];
byte[] ciphertext = new byte[data.length - IV_SIZE];
System.arraycopy(data, 0, iv, 0, IV_SIZE);
System.arraycopy(data, IV_SIZE, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
GCMParameterSpec spec = new GCMParameterSpec(TAG_SIZE, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
return new String(cipher.doFinal(ciphertext));
}
public static void main(String[] args) throws Exception {
SecretKey key = generateKey();
String encrypted = encrypt("融资担保代偿金额: 500万", key);
System.out.println("加密: " + encrypted);
String decrypted = decrypt(encrypted, key);
System.out.println("解密: " + decrypted);
}
}三、非对称加密
3.1 RSA 原理
RSA 基于大数分解难题:
选两个大素数 p, q
n = p × q (公开)
φ(n) = (p-1)(q-1)
选 e(公钥指数),gcd(e, φ(n)) = 1
d = e⁻¹ mod φ(n) (私钥)
加密:c = m^e mod n
解密:m = c^d mod n
密钥长度:2048 bit(最低安全要求),推荐 4096 bit
2048 bit → 安全到 2030 年
4096 bit → 安全到 2040+ 年3.2 Java 实现
import javax.crypto.Cipher;
import java.security.*;
import java.util.Base64;
public class RSAUtil {
private static final String ALGORITHM = "RSA";
private static final int KEY_SIZE = 2048;
// 生成密钥对
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM);
kpg.initialize(KEY_SIZE, new SecureRandom());
return kpg.generateKeyPair();
}
// 公钥加密
public static String encryptWithPublicKey(String plaintext, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(plaintext.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
}
// 私钥解密
public static String decryptWithPrivateKey(String ciphertext, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
return new String(decrypted);
}
public static void main(String[] args) throws Exception {
KeyPair keyPair = generateKeyPair();
// 公钥加密
String encrypted = encryptWithPrivateKey("对称密钥: AES-256", keyPair.getPublic());
// 私钥解密
String decrypted = decryptWithPrivateKey(encrypted, keyPair.getPrivate());
}
}3.3 对称 vs 非对称
| 维度 | 对称加密 | 非对称加密 |
|---|---|---|
| 密钥 | 一把密钥 | 公钥+私钥 |
| 速度 | 极快(AES ~1GB/s) | 慢(RSA ~100KB/s) |
| 安全性 | 密钥分发难 | 公钥可公开 |
| 用途 | 数据加密 | 密钥交换、数字签名 |
| 实际方案 | 混合加密:非对称交换对称密钥,对称加密数据 |
四、数字签名
4.1 签名原理
签名:
1. 对原文计算哈希:hash = SHA256(data)
2. 用私钥加密哈希:signature = RSA_Encrypt(hash, privateKey)
3. 发送:data + signature
验签:
1. 对收到的 data 计算哈希:hash1 = SHA256(data)
2. 用公钥解密签名:hash2 = RSA_Decrypt(signature, publicKey)
3. 比较 hash1 == hash2 → 验证通过
签名 ≠ 加密:
加密:公钥加密,私钥解密(保密性)
签名:私钥签名,公钥验签(不可否认性)4.2 Java 实现
import java.security.*;
import java.util.Base64;
public class SignatureUtil {
// 签名
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes());
return Base64.getEncoder().encodeToString(signature.sign());
}
// 验签
public static boolean verify(String data, String signStr, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(data.getBytes());
return signature.verify(Base64.getDecoder().decode(signStr));
}
public static void main(String[] args) throws Exception {
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
String data = "转账金额: 500万元";
// 签名
String sign = sign(data, keyPair.getPrivate());
System.out.println("签名: " + sign);
// 验签
boolean valid = verify(data, sign, keyPair.getPublic());
System.out.println("验签: " + valid); // true
// 篡改检测
boolean tampered = verify("转账金额: 5000元", sign, keyPair.getPublic());
System.out.println("篡改验签: " + tampered); // false
}
}五、HTTPS/TLS 握手
5.1 TLS 1.2 握手流程
Client Server
│ │
│ ──── 1. ClientHello ──────────────────► │ 支持的TLS版本、加密套件、随机数
│ │
│ ◄─── 2. ServerHello ────────────────── │ 选定TLS版本、加密套件、随机数
│ ◄─── 3. Certificate ───────────────── │ 服务器证书(含公钥)
│ ◄─── 4. ServerHelloDone ───────────── │
│ │
│ ──── 5. ClientKeyExchange ────────────► │ 用服务器公钥加密的 Pre-Master Secret
│ ──── 6. ChangeCipherSpec ─────────────► │ 切换到加密通信
│ ──── 7. Finished ─────────────────────► │ 加密的握手摘要
│ │
│ ◄─── 8. ChangeCipherSpec ───────────── │
│ ◄─── 9. Finished ───────────────────── │
│ │
│ ◄═══ 加密通信开始 ═════════════════════► │ 使用协商的对称密钥
三个随机数 → Master Secret → 对称密钥
Client Random + Server Random + Pre-Master Secret → Master Secret5.2 TLS 1.3 优化
TLS 1.2:2 RTT(两次往返)
TLS 1.3:1 RTT(一次往返),支持 0-RTT 恢复
TLS 1.3 改进:
1. 去掉 RSA 密钥交换,只用 ECDHE(前向安全)
2. 精简加密套件(只保留 AEAD)
3. 握手消息加密(更安全)
4. 0-RTT 恢复(首次连接 1-RTT,重连 0-RTT)六、数字证书
6.1 证书链
根证书(Root CA)→ 中间证书(Intermediate CA)→ 网站证书(Leaf Cert)
验证过程:
1. 网站证书由中间 CA 签发
2. 中间 CA 证书由根 CA 签发
3. 根 CA 证书预装在操作系统/浏览器中(信任锚)
4. 逐级验证签名
证书内容:
- 版本号
- 序列号
- 签名算法
- 颁发者(Issuer)
- 有效期
- 主体(Subject)
- 公钥
- CA 签名6.2 Java 证书操作
import java.io.FileInputStream;
import java.security.cert.*;
public class CertificateUtil {
// 读取证书
public static X509Certificate loadCertificate(String path) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (FileInputStream fis = new FileInputStream(path)) {
return (X509Certificate) cf.generateCertificate(fis);
}
}
// 验证证书链
public static boolean validateChain(X509Certificate leaf,
X509Certificate intermediate,
X509Certificate root) throws Exception {
// 验证 leaf 由 intermediate 签发
leaf.verify(intermediate.getPublicKey());
// 验证 intermediate 由 root 签发
intermediate.verify(root.getPublicKey());
// 验证有效期
leaf.checkValidity();
intermediate.checkValidity();
return true;
}
// 检查证书吊销(CRL/OCSP)
public static void checkRevocation(X509Certificate cert) throws Exception {
// 实际中用 OCSP 或 CRL 检查
// ocsp.example.com 查询证书是否被吊销
}
}七、密钥交换
7.1 Diffie-Hellman
DH 密钥交换:
Alice 和 Bob 公开协商出一个共享密钥,即使通信被窃听也无法破解
公开参数:大素数 p,生成元 g
Alice:
私钥 a(随机)
公钥 A = g^a mod p
发送 A 给 Bob
Bob:
私钥 b(随机)
公钥 B = g^b mod p
发送 B 给 Alice
共享密钥:
Alice 计算:S = B^a mod p = (g^b)^a mod p = g^(ab) mod p
Bob 计算: S = A^b mod p = (g^a)^b mod p = g^(ab) mod p
窃听者只知道 p, g, A, B,无法计算 a 或 b(离散对数难题)7.2 ECDHE(椭圆曲线 DH)
ECDHE(Elliptic Curve Diffie-Hellman Ephemeral):
用椭圆曲线代替大素数
密钥更短(256 bit ECC ≈ 3072 bit RSA)
计算更快
前向安全(Ephemeral = 每次用新密钥对)
TLS 1.3 只用 ECDHE,不用 RSA 密钥交换八、Java KeyStore
import java.security.*;
public class KeyStoreUtil {
// 创建 KeyStore
public static KeyStore createKeyStore(String password) throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, password.toCharArray());
return ks;
}
// 存储密钥对
public static void storeKeyPair(KeyStore ks, KeyPair keyPair, String alias,
String password, X509Certificate cert) throws Exception {
ks.setKeyEntry(alias, keyPair.getPrivate(), password.toCharArray(),
new java.security.cert.Certificate[]{cert});
}
// 加载 KeyStore
public static KeyStore loadKeyStore(String path, String password) throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(path)) {
ks.load(fis, password.toCharArray());
}
return ks;
}
// Spring Boot 配置 HTTPS
// application.yml:
// server:
// ssl:
// key-store: classpath:keystore.p12
// key-store-password: changeit
// key-store-type: PKCS12
// key-alias: tomcat
}九、密码学在项目中的应用
9.1 API 签名验证
// 对称签名:HMAC-SHA256
public class ApiSignature {
// 客户端签名
public static String sign(String secretKey, String method, String path,
Map<String, String> params, long timestamp, String nonce) {
// 1. 参数排序
String sortedParams = params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
// 2. 拼接签名字符串
String signStr = method + "\n" + path + "\n" + sortedParams + "\n" + timestamp + "\n" + nonce;
// 3. HMAC-SHA256
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"));
byte[] hmac = mac.doFinal(signStr.getBytes());
return HexFormat.of().formatHex(hmac);
}
// 服务端验签
public static boolean verify(String secretKey, String method, String path,
Map<String, String> params, long timestamp,
String nonce, String signature) throws Exception {
// 1. 检查时间戳(防重放,5分钟有效)
if (Math.abs(System.currentTimeMillis() / 1000 - timestamp) > 300) {
return false;
}
// 2. 检查 nonce(防重放,Redis 记录已用 nonce)
// if (redis.exists("nonce:" + nonce)) return false;
// redis.setex("nonce:" + nonce, 300, "1");
// 3. 计算签名比对
String expected = sign(secretKey, method, path, params, timestamp, nonce);
return MessageDigest.isEqual(expected.getBytes(), signature.getBytes());
}
}9.2 敏感数据加密存储
// 数据库字段加密:AES-GCM + 密钥管理
public class FieldEncryption {
private final SecretKey dataKey; // 数据加密密钥
private final SecretKey kek; // 密钥加密密钥(KEK)
// 加密
public String encryptField(String plaintext) throws Exception {
return AESUtil.encrypt(plaintext, dataKey);
}
// 解密
public String decryptField(String ciphertext) throws Exception {
return AESUtil.decrypt(ciphertext, dataKey);
}
// 密钥轮换
public void rotateKey() throws Exception {
// 1. 生成新数据密钥
SecretKey newKey = AESUtil.generateKey();
// 2. 用 KEK 加密新密钥存储
// 3. 重新加密所有数据(分批进行)
// 4. 切换到新密钥
}
}十、面试要点
Q:对称加密和非对称加密的区别?实际怎么用?
对称加密一把密钥,快但不安全分发;非对称两把密钥,安全但慢。实际用混合加密:非对称加密交换对称密钥,对称密钥加密数据(HTTPS 就是这么做的)。
Q:数字签名和加密有什么区别?
加密是"保密":公钥加密,只有私钥能解密。签名是"认证":私钥签名,公钥能验签。签名不提供保密性,只提供完整性和不可否认性。
Q:HTTPS 握手过程?
- ClientHello(支持的TLS版本、加密套件、随机数)
- ServerHello + 证书 + ServerHelloDone
- ClientKeyExchange(用服务器公钥加密 Pre-Master Secret)
- 双方用三个随机数生成对称密钥
- 切换到加密通信
TLS 1.3 简化为 1-RTT,去掉 RSA 密钥交换,只用 ECDHE。
十一、总结
密码学核心公式:
保密性 = 对称加密(AES-GCM)
完整性 = 哈希(SHA-256)
真实性 = 数字签名(RSA/ECDSA)
密钥分发 = 非对称加密(RSA/ECDH)
信任传递 = 证书链(CA → 中间CA → 网站证书)记住:AES-GCM 加密数据,RSA/ECC 交换密钥和签名,SHA-256 验完整性,HTTPS = 混合加密 + 证书。