Bảo mật cấu hình cơ sở dữ liệu trong Spring Boot bằng mã hóa RSA

Để bảo vệ thông tin nhạy cảm như URL, tên người dùng và mật khẩu cơ sở dữ liệu trong ứng dụng Spring Boot, việc mã hóa các giá trị cấu hình là một thực hành an toàn thiết yếu. Giải pháp dưới đây sử dụng thuật toán RSA để mã hóa tại thời điểm triển khai và giải mã động khi khởi tạo kết nối.

1. Thêm thư viện hỗ trợ

Sử dụng commons-io để ghi/đọc tệp khóa một cách tiện lợi:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

2. Triển khai lớp mã hóa RSA tùy chỉnh

Lớp SecureKeyManager cung cấp ba chức năng chính: sinh cặp khóa, mã hóa văn bản rõ bằng khóa công khai và giải mã văn bản mã hóa bằng khóa riêng tư.

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class SecureKeyManager {
    private static final Logger log = LoggerFactory.getLogger(SecureKeyManager.class);
    private static final String ALGORITHM = "RSA";
    private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";

    /**
     * Tạo và lưu cặp khóa vào hệ thống tệp
     */
    public static void generateAndStoreKeys(String pubPath, String priPath) {
        try {
            KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);
            gen.initialize(2048);
            KeyPair pair = gen.generateKeyPair();

            String encodedPub = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded());
            String encodedPri = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded());

            FileUtils.writeStringToFile(new File(pubPath), encodedPub, StandardCharsets.UTF_8);
            FileUtils.writeStringToFile(new File(priPath), encodedPri, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("Lỗi khi tạo khóa", e);
        }
    }

    /**
     * Giải mã chuỗi đã được mã hóa bằng khóa riêng tư
     */
    public static String decryptWithPrivateKey(String privateKeyPem, String encryptedData) {
        if (encryptedData == null || encryptedData.trim().isEmpty()) return "";
        try {
            byte[] decodedKey = Base64.getDecoder().decode(privateKeyPem);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
            PrivateKey key = KeyFactory.getInstance(ALGORITHM).generatePrivate(keySpec);

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] decodedInput = Base64.getDecoder().decode(encryptedData);
            byte[] decryptedBytes = cipher.doFinal(decodedInput);
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.warn("Giải mã thất bại", e);
            return "";
        }
    }

    /**
     * Mã hóa chuỗi bằng khóa công khai
     */
    public static String encryptWithPublicKey(String publicKeyPem, String plainText) {
        if (plainText == null || plainText.trim().isEmpty()) return "";
        try {
            byte[] decodedKey = Base64.getDecoder().decode(publicKeyPem);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
            PublicKey key = KeyFactory.getInstance(ALGORITHM).generatePublic(keySpec);

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (Exception e) {
            log.warn("Mã hóa thất bại", e);
            return "";
        }
    }
}

3. Sinh khóa và mã hóa thông tin cấu hình

Chạy đoạn mã sau một lần để tạo khóa và mã hóa thông tin kết nối:

// 1. Tạo khóa
SecureKeyManager.generateAndStoreKeys("keys/public.key", "keys/private.key");

// 2. Đọc nội dung khóa và mã hóa
String pubKey = FileUtils.readFileToString(new File("keys/public.key"), StandardCharsets.UTF_8);
String urlEnc = SecureKeyManager.encryptWithPublicKey(pubKey, "jdbc:mysql://localhost:3306/mydb");
String userEnc = SecureKeyManager.encryptWithPublicKey(pubKey, "app_user");
String passEnc = SecureKeyManager.encryptWithPublicKey(pubKey, "s3cr3t_p@ss");

System.out.println("URL mã hóa: " + urlEnc);
System.out.println("User mã hóa: " + userEnc);
System.out.println("Pass mã hóa: " + passEnc);

4. Cập nhật application.yml hoặc application.properties

Thay thế giá trị rõ bằng giá trị đã mã hóa và thêm đường dẫn tới khóa riêng tư:

spring:
  datasource:
    url: "AQAAA...XVQ=="  # giá trị đã mã hóa
    username: "BQAAA...Zg==" 
    password: "CQAAA...Hk=="

encrypt:
  private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC..."
  public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."

5. Cấu hình DataSource với giải mã động

Tạo bean DataSource tự động giải mã các tham số trước khi khởi tạo kết nối:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class DatabaseSecurityConfig {

    @Value("${encrypt.private-key}")
    private String privateKey;

    @Value("${spring.datasource.url}")
    private String encodedUrl;

    @Value("${spring.datasource.username}")
    private String encodedUsername;

    @Value("${spring.datasource.password}")
    private String encodedPassword;

    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
                .url(SecureKeyManager.decryptWithPrivateKey(privateKey, encodedUrl))
                .username(SecureKeyManager.decryptWithPrivateKey(privateKey, encodedUsername))
                .password(SecureKeyManager.decryptWithPrivateKey(privateKey, encodedPassword))
                .build();
    }
}

Thẻ: spring-boot RSA datasource Security Encryption

Đăng vào ngày 10 tháng 7 lúc 23:19