第 32 章:数据加密
学习目标
- 掌握对称加密、非对称加密、哈希
- 学会密钥管理与传输加密
- 实现字段级加密和脱敏
- 避免密钥泄漏、加密失效的坑
一、加密基础
text
对称加密: AES、DES(同一密钥)
非对称加密: RSA、ECC(公钥 + 私钥)
哈希: SHA-256、BCrypt(不可逆)java
// 简单分类
// 1. 数据加密(可逆):AES、RSA
// 2. 密码哈希(不可逆):BCrypt、Argon2
// 3. 消息签名:HMAC、RSA 签名二、对称加密 AES
java
public class AesUtil {
// 128/192/256 bit 密钥
private static final String ALGORITHM = "AES/GCM/NoPadding";
public static String encrypt(String plaintext, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 拼接 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 ciphertext, SecretKey key) throws Exception {
byte[] data = Base64.getDecoder().decode(ciphertext);
byte[] iv = Arrays.copyOfRange(data, 0, 12);
byte[] cipherBytes = Arrays.copyOfRange(data, 12, data.length);
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
return new String(cipher.doFinal(cipherBytes), StandardCharsets.UTF_8);
}
}java
// 密钥生成
public class KeyUtil {
public static SecretKey generateAesKey() throws Exception {
KeyGenerator gen = KeyGenerator.getInstance("AES");
gen.init(256); // AES-256
return gen.generateKey();
}
}⚠️ 坑 1:AES 用 ECB 模式(默认)其实只保密文块顺序,首块相同的明文块密文也相同。用 GCM 或 CBC。
三、非对称加密 RSA
java
public class RsaUtil {
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(2048);
return gen.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);
return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes()));
}
public static String decrypt(String ciphertext, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)));
}
}java
// 签名
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 signature, PublicKey publicKey) throws Exception {
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey);
verifier.update(data.getBytes());
return verifier.verify(Base64.getDecoder().decode(signature));
}text
RSA 用途
├── 加密: 客户端拿公钥加密,服务端用私钥解密
├── 签名: 服务端用私钥签名,客户端用公钥验签
└── 密钥交换: 配合 AES,Hybrid Encryption四、Hybrid 加密
java
// 实战:大量数据用 AES,密钥用 RSA
public class HybridEncryption {
public static String encrypt(String data, PublicKey publicKey) throws Exception {
// 1. 随机生成 AES 密钥
SecretKey aesKey = KeyUtil.generateAesKey();
// 2. 用 AES 加密数据
String encrypted = AesUtil.encrypt(data, aesKey);
// 3. 用 RSA 加密 AES 密钥
String encryptedKey = RsaUtil.encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), publicKey);
// 4. 返回 加密密钥 + 加密数据
return encryptedKey + ":" + encrypted;
}
}五、哈希与摘要
java
// SHA-256 单向哈希
public class HashUtil {
public static String sha256(String input) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(hash);
}
// 加盐哈希
public static String saltedHash(String input, String salt) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(salt.getBytes());
byte[] hash = digest.digest(input.getBytes());
return HexFormat.of().formatHex(hash);
}
}java
// 密码专用:BCrypt、Argon2
public class PasswordEncoder {
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
public static String hash(String password) {
return encoder.encode(password);
}
public static boolean matches(String password, String hash) {
return encoder.matches(password, hash);
}
}⚠️ 坑 2:SHA-256 不带盐,相同密码哈希相同,彩虹表攻击秒破。密码必用 BCrypt / Argon2,不要用 SHA-256。
六、字段级加密
java
// 1. 加密字段入库
@Entity
@Table(name = "user")
public class User {
@Id
private Long id;
@Column
private String name;
@Column
@Convert(converter = EncryptedStringConverter.class)
private String idCard; // 身份证号加密存储
}
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
private static final String KEY = "your-aes-key";
@Override
public String convertToDatabaseColumn(String attribute) {
return aesEncrypt(attribute);
}
@Override
public String convertToEntityAttribute(String dbData) {
return aesDecrypt(dbData);
}
}java
// 2. 脱敏(返回前端时)
public class DataMask {
public static String maskIdCard(String idCard) {
if (idCard == null || idCard.length() < 8) return idCard;
return idCard.substring(0, 4) + "**********" + idCard.substring(14);
}
public static String maskPhone(String phone) {
if (phone == null || phone.length() < 11) return phone;
return phone.substring(0, 3) + "****" + phone.substring(7);
}
}java
// Jackson 注解脱敏
@JsonSerialize(using = IdCardSerializer.class)
private String idCard;
public class IdCardSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(DataMask.maskIdCard(value));
}
}七、密钥管理
java
// 1. 密钥不放代码,放配置中心
@Value("${encryption.aes.key}")
private String aesKey;
// 2. 用 KMS(密钥管理服务)
public class KmsService {
public SecretKey getKey(String keyId) {
// 调 AWS KMS / 阿里云 KMS / HashiCorp Vault
return kmsClient.getSecret(keyId);
}
}yaml
# 3. K8s Secret
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
aes-key: <base64-encoded-key>yaml
# 4. Spring Cloud Config + 加密
spring:
cloud:
config:
server:
encrypt:
enabled: true⚠️ 坑 3:密钥和加密数据放同一数据库,数据库被攻破 = 全部解密。密钥必须独立存储(KMS、Vault)。
八、传输加密
yaml
# HTTPS 服务端
server:
ssl:
key-store: classpath:keystore.p12
key-store-password: changeit
key-store-type: PKCS12
key-alias: tomcatjava
// 客户端 TLS 配置
public class TlsConfig {
@Bean
public RestTemplate restTemplate() throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial(new ClassPathResource("truststore.p12").getURL(), "changeit".toCharArray())
.build();
HttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);
return new RestTemplate(factory);
}
}九、签名防篡改
java
// 接口签名:时间戳 + 业务参数 + 密钥 HmacSHA256
public class SignatureUtil {
public static String sign(Map<String, String> params, String secret) throws Exception {
// 1. 参数排序
String sorted = params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
// 2. 加时间戳
String timestamp = String.valueOf(System.currentTimeMillis());
String content = sorted + "×tamp=" + timestamp;
// 3. HMAC
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
byte[] hash = mac.doFinal(content.getBytes());
return HexFormat.of().formatHex(hash);
}
public static boolean verify(Map<String, String> params, String signature, String secret, long maxAge) throws Exception {
String timestamp = params.get("timestamp");
if (System.currentTimeMillis() - Long.parseLong(timestamp) > maxAge) {
return false; // 超时
}
return sign(params, secret).equals(signature);
}
}十、合规与去标识化
java
// 1. 加密字段(PII 数据)
@Convert(converter = EncryptedStringConverter.class)
private String phone;
private String email;
// 2. 假名化(不可逆映射)
public class Pseudonymizer {
private static final Map<String, String> mapping = new HashMap<>();
public static String pseudonymize(String value) {
return mapping.computeIfAbsent(value, k -> "P" + UUID.randomUUID().toString().substring(0, 8));
}
}
// 3. K-匿名化(数据脱敏)
public class KAnonymity {
public static String generalizeAge(int age) {
if (age < 20) return "<20";
if (age < 30) return "20-29";
if (age < 40) return "30-39";
return "40+";
}
}text
合规要求
├── GDPR: 加密 + 假名化
├── 个人信息保护法: 加密 + 最小化
├── 等保: 三级加密 + 备份
└── 行业标准: PCI DSS(支付卡)本章小结
| 类型 | 算法 | 用途 |
|---|---|---|
| 对称 | AES-GCM | 数据加密 |
| 非对称 | RSA-OAEP | 密钥交换、签名 |
| 哈希 | SHA-256 | 完整性校验 |
| 密码 | BCrypt | 密码存储 |
| 关键点 | 建议 |
|---|---|
| AES 模式 | GCM(认证加密) |
| RSA 填充 | OAEP-SHA256 |
| 密钥 | KMS 管理 |
| 字段 | 加密 + 脱敏 |
动手练习
- AES-GCM:实现 AES-GCM 加解密,处理 IV
- RSA 签名:实现签名 + 验签,验证传输完整
- 字段加密:把身份证号加密存储,查询时脱敏
- 密钥管理:用 K8s Secret 注入 AES 密钥
下一章:第 33 章:应用安全 →