Skip to content
第 32 章 架构 ⏱ 11 分钟阅读

第 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: tomcat
java
// 客户端 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 + "&timestamp=" + 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 管理
字段加密 + 脱敏

动手练习 ​

  1. AES-GCM:实现 AES-GCM 加解密,处理 IV
  2. RSA 签名:实现签名 + 验签,验证传输完整
  3. 字段加密:把身份证号加密存储,查询时脱敏
  4. 密钥管理:用 K8s Secret 注入 AES 密钥

下一章:第 33 章:应用安全 →

本站基于 VitePress 构建 · 由 StackHub 团队维护