在微服务架构中使用配置中心(如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
到此自定义扩展就已经实现了