Nacos源码学习5-Nacos配置中心配置加密原理

在微服务架构中使用配置中心(如Nacos、Apollo、Spring Cloud Config等)时,敏感配置项(如数据库密码、API密钥等)需要进行加密存储

Nacos配置加密涉及类图
绿色为可扩展点
在这里插入图片描述
在 NacosConfigDataLoader 从配置中心上拉取配置时,会调用 ConfigFilterChainManager 进行配置过滤
在这里插入图片描述
NacosConfigService 执行了拉取配置的实际动作,通过 ClientWorker 发送RPC调用,获取到了响应
ConfigResponse response,response中包含了配置数据的全文
在这里插入图片描述
encryptedDataKey 是用于解密秘钥的秘钥
content 是配置中心的原文
在这里插入图片描述
ConfigFilterChainManager 使用了JDK 的SPI机制加载了所有 IConfigFilter,并且执行每一个过滤器
在加解密的过程了就使用了 ConfigEncryptionFilter
ConfigEncryptionFilter 会对请求数据和响应数据都进行加解密
在这里插入图片描述
EncryptionHandler 解密会执行以下步骤
1、判断当前dataId是否需要解密,通过dataId(即配置文件名)是否以 cipher- 开头
2、根据dataId获取对应的解密算法,即以 - 分隔,dataId中第二个元素,例如配置文件名为cipher-aes-nacos-service.properties,其中 aes 即为解密算法
3、根据响应信息中的秘钥获取真实的秘钥
4、根据真实秘钥解密响应信息内容
在这里插入图片描述
Nacos配置中心加解密时序图

在这里插入图片描述

自定义扩展
如果需要自定义Nacos配置文件的加解密有以下两种方法
1、自定义扩展 EncryptionPluginService 接口,实现加解密方法,该方式存在缺点是,配置文件必须按照 cipher-${ALGORITHM}-xxxx.properties 的方式命名,如果只是想对特定的键值对加解密不太合适
2、自定义扩展 IConfigFilter 接口,这样就不用限制配置文件名称,用户可以自定义自己的配置文件加解密
3、拒绝自己造轮子,使用已有的开源框架 Jasypt集成(推荐)

使用Jasypt集成(推荐)
步骤1:添加依赖

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>

步骤2:配置加密密钥

jasypt:
  encryptor:
    password: your-encryption-password # 生产环境应从安全渠道获取
    algorithm: PBEWithHMACSHA512AndAES_256
    iv-generator-classname: org.jasypt.iv.RandomIvGenerator

步骤3:加密配置值

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setPassword("your-encryption-password");
        encryptor.setAlgorithm("PBEWithHMACSHA512AndAES_256");
        
        String encrypted = encryptor.encrypt("your-sensitive-value");
        System.out.println("ENC(" + encrypted + ")");
    }
}

步骤4:在配置中使用加密值

datasource:
  password: ENC(Ad9q8asd7f8a7sdf87a8sdf7) # 以ENC()包裹加密值

如果看到这里你就是要用Nacos原生的加解密方式
1、将配置文件加密之后上传到Nacos配置中心,并注意命名规范,cipher-${ALGORITHM}-xxxx.properties 的命名方式
在这里插入图片描述
2、自定义实现接口 EncryptionPluginService ,并实现加解密方法

package com.takemehand.nacos.demos.config.encryption;

import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService;
import com.takemehand.nacos.demos.util.AESUtils;

import java.security.NoSuchAlgorithmException;

public class AESEncryptionPluginService implements EncryptionPluginService {

    private static final String ALGORITHM = "aes";

    private static final String AES_PKCS5P = "AES/ECB/PKCS5Padding";

    private static String key = "Ahg4MXRvwGzcHCpxDYeNbOzpbMPd+WzN9uZK0wV+lpM=";
    private static String salt = "GT+Bhdag6/NyQlHNI+zqHg==";

    @Override
    public String encrypt(String secretKey, String content) {
        try {
            return AESUtils.encrypt(content, key, salt);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String decrypt(String secretKey, String content) {
        try {
            return AESUtils.decrypt(content, key, salt);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String generateSecretKey() {
        try {
            return AESUtils.generateKey();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String algorithmName() {
        return ALGORITHM;
    }

    @Override
    public String encryptSecretKey(String s) {
        return key;
    }

    @Override
    public String decryptSecretKey(String s) {
        return key;
    }

}

AES 加解密工具类

package com.takemehand.nacos.demos.util;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class AESUtils {
    
    private static final String ALGORITHM = "aes";
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding"; // 使用CBC模式,需要IV


    public static void main(String[] args) throws Exception {
        // 生成密钥和IV
        String key = AESUtils.generateKey();
        String iv = AESUtils.generateIV();

        System.out.println("AES Key: " + key);
        System.out.println("IV: " + iv);

        String originalText = "auth.name=李四\n" +
                "auth.age=18\n" +
                "auth.password=123456";
        System.out.println("原始文本: " + originalText);

        // 加密
        String encryptedText = AESUtils.encrypt(originalText, key, iv);
        System.out.println("加密后: " + encryptedText);

        // 解密
        String decryptedText = AESUtils.decrypt(encryptedText, key, iv);
        System.out.println("解密后: " + decryptedText);

    }

    /**
     * 生成AES密钥
     */
    public static String generateKey() throws NoSuchAlgorithmException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        keyGenerator.init(256); // 可以选择128, 192或256位
        SecretKey secretKey = keyGenerator.generateKey();
        return Base64.getEncoder().encodeToString(secretKey.getEncoded());
    }

    /**
     * 加密
     */
    public static String encrypt(String content, String key, String iv) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(key);
        byte[] ivBytes = Base64.getDecoder().decode(iv);

        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);

        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

        byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encrypted);
    }

    /**
     * 解密
     */
    public static String decrypt(String encryptedContent, String key, String iv) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(key);
        byte[] ivBytes = Base64.getDecoder().decode(iv);

        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);

        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

        byte[] decoded = Base64.getDecoder().decode(encryptedContent);
        byte[] decrypted = cipher.doFinal(decoded);
        return new String(decrypted, StandardCharsets.UTF_8);
    }

    /**
     * 生成随机IV (16字节)
     */
    public static String generateIV() {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        return Base64.getEncoder().encodeToString(iv);
    }
}

3、使用JDK 的SPI机制,注入该实现类,使Nacos可以发现,在resource文件夹下新建META-INF/services 文件夹,并创建文件com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService,注意该文件不需要后缀名,文件内容为刚刚创建的实现类的全类名,如果有多个,则换行就行

com.takemehand.nacos.demos.config.encryption.AESEncryptionPluginService
com.takemehand.nacos.demos.config.encryption.AESEncryptionPluginService2

到此自定义扩展就已经实现了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值