一. 技术需求
由于项目中的配置文件 配置的地方过多,现将配置文件统一放到nacos上集中管理 且密码使用加密的方式放在配置文件中
项目中组件使用的版本环境如下
spring cloud 2021.0.5
spring cloud alibaba 2021.0.5.0
spring boot 2.6.13
二. 技术实现
配置文件的加密使用 加密库 jasypt
三. 简单使用步骤
-
引入maven依赖
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency>
-
添加配置
#加解密使用的密码 可以理解为密钥 jasypt.encryptor.password=12581
-
使用jasypt配置的密码来加密密码 并替换配置原先密码 (提供工具类进行加密)
#原密码 spring.datasource.passsword=1232456 #新的密码 其实 rngDAJwRU09A3oNLkxzNaP3wfyhHt5N4DPAjudNJKDYAWXDeFmdGcFvgZJSh4gqZ 为源密码加密后的值 spring.datasource.password=ENC(rngDAJwRU09A3oNLkxzNaP3wfyhHt5N4DPAjudNJKDYAWXDeFmdGcFvgZJSh4gqZ)
工具类如下
@Slf4j
public class Custom {
public static void main(String[] args) {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
//TODO 替换为配置文件中的密码
config.setPassword("12581");
//默认的加密算法
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
//TODO 123456 替换为所需加密的 原文
String encrypt = encryptor.encrypt("123456");
log.info("encrypt: {}", encrypt);
}
}
到此就完成基本的配置文件加解密
你以为结束了吗,no~~~~
四. 高级使用篇
上面使用的默认的加解密的工具类,但是jasypt.encryptor.password 还是需要配置在配置文件中的 (配置文件、jvm参数等)本质来讲还是容易统一泄露。到这里,可能就有灵感了。不如我们自定义的加密的工具类 复杂的话 也可以搞个非对称加密(这个可自行研究)
目前我们来使用下 自定义加密的工具
(1)首先实现自定义的加密工具 需要实现 StringEncryptor 即可 我们这是用的国密4的对称加密算法 同时使用了 hutool的工具类
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SM4;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.jasypt.encryption.StringEncryptor;
import java.nio.charset.StandardCharsets;
/**
* @author leon
* @date 2023-08-19 10:54:32
*/
@Slf4j
public class Sm4Encryptor implements StringEncryptor {
private static final SM4 SM_4;
static {
byte[] key = "1234567891234560".getBytes();
byte[] iv = "1234567891234560".getBytes();
SM_4 = new SM4(Mode.CBC, Padding.PKCS5Padding, key, iv);
}
@Override
public String encrypt(String message) {
return SM_4.encryptHex(message);
}
@Override
public String decrypt(String encryptedMessage) {
return SM_4.decryptStr(encryptedMessage);
}
public static void main(String[] args) {
String originalValue = "123456";
String encrypted = SM_4.encryptHex(originalValue);
String decrypted = SM_4.decryptStr(encrypted);
log.info("Original Value: {}" , originalValue);
log.info("Encrypted Value: {}" , encrypted);
log.info("Decrypted Value: {}", decrypted);
}
}
(2)注册该bean 这需要使用特殊的方式注册bean 因为我们希望在 Spring Boot 应用程序启动期间提前将自定义的加解密类添加到 Spring 的应用程序上下文中,以便在配置文件加载之前,加解密类已经可用。这样可以确保配置文件中的加密属性能够在加载时使用正确的自定义加解密逻辑进行解密
新建初始化类
import com.stlye.encryptor.Sm4Encryptor;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @date 2023-08-21 13:57:20
* @author leon
*/
public class CustomContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.getBeanFactory().registerSingleton("encryptorBean", new Sm4Encryptor());
}
}
其次在resource 下新建 META_INF文件夹 并新建 spring.factories 文件
org.springframework.context.ApplicationContextInitializer=com.style.order.config.CustomContextInitializer
(3)调整配置
#移除该配置
jasypt.encryptor.password=12581
#新增如下配置 自定义加解密工具的bean名称
jasypt.encryptor.bean=encryptorBean
(4) 使用该工具类生成需要加密的内容 并替换 原配置文件中的内容
#其中cab60ba208f8a7d87c4f631e2763607a为源密码加密后的内容
spring.datasource.password=ENC(cab60ba208f8a7d87c4f631e2763607a)
#另外属性解密的前缀和后缀都是支持自定义 默认如下
jasypt.encryptor.property.prefix=ENC(
jasypt.encryptor.property.suffix=)
到这里就完成配置的文件的加密 你肯定会好奇,解密需要我们自己做么,回答当然不需要,这个框架中已经自动帮我们进行了解密
具体的实现原理可看源码类 EnableEncryptablePropertiesBeanFactoryPostProcessor 和 EncryptablePropertyResolverConfiguration 两大核心类 本质上来讲也是动态代理。
以下是国密4加密算法的参数介绍
密码的加密算法选择了 SM4 国密的对称加密算法
内置参数 模式和补码方式对比如下
//不同的模式和补码方式在加密算法中有不同的作用,它们会影响加密和解密的行为以及安全性。下面我会简要解释一下不同模式和补码方式的区别,并提供一些关于安全性的信息:
//
//**模式(Mode)**:
//加密模式定义了数据块之间是如何加密的,它影响到同一消息分成多个数据块时的加密方式。以下是一些常见的加密模式:
//
//1. **ECB(Electronic Codebook)**:每个数据块都独立加密,相同的输入会得到相同的输出。不适合加密大量数据,安全性较差。
//
//2. **CBC(Cipher Block Chaining)**:前一个数据块的加密结果会与当前数据块一起进行加密。需要一个初始化向量(IV)来增加安全性。
//
//3. **CFB(Cipher Feedback)**:前一个密文块被解密并与当前明文块进行异或操作,然后再进行加密。不需要 IV。
//
//4. **OFB(Output Feedback)**:类似于 CFB,但是前一个密文块只用于生成密钥流,不直接与明文块进行操作。不需要 IV。
//
//5. **CTR(Counter)**:将 IV 与一个计数器组合,生成密钥流,然后与明文块进行异或操作。不需要 IV。
//
//**补码方式(Padding)**:
//补码方式是在加密块大小与数据大小不匹配时,如何填充数据块的方式。以下是一些常见的补码方式:
//
//1. **NoPadding**:不进行任何填充,要求明文的大小必须是加密块大小的倍数。
//
//2. **PKCS5Padding** / **PKCS7Padding**:在数据块的末尾填充字节,字节的值等于需要填充的字节数。
//
//3. **ISO10126Padding**:在数据块的末尾填充随机字节,最后一个字节指示填充的字节数。
//
//4. **ZeroBytePadding**:在数据块的末尾填充零字节。
//
//**安全性评估**:
//安全性评估涉及多个因素,包括加密算法的强度、密钥的管理、数据传输的安全性等。在选择加密模式和补码方式时,要根据具体的应用场景和安全需求进行权衡。一般而言,以下是一些建议:
//
//- **模式选择**:CBC 模式相对较为常见和安全,适用于大多数场景。CTR 也是一种常见的选择,适合并行加解密。
//
//- **补码方式选择**:PKCS7Padding / PKCS5Padding 是常见的选择,它们会根据需要填充的字节数进行填充,相对比较安全。
//
//总之,选择加密模式和补码方式时,应该综合考虑性能、安全性和适用性。在设计和实现加密系统时,最好参考专业的加密标准和最佳实践,以确保数据的安全性。
其中安全性较高 mode参数可选择值的是 CBC / GCM
在国密4(SM4)加密算法中,GCM(Galois/Counter Mode)和CBC(Cipher Block Chaining)都是加密模式,用于定义不同的加密操作方式。它们有一些区别,包括加密过程、性能、安全性等方面。
-
GCM模式(Galois/Counter Mode):
- GCM是一种高级的分组加密模式,除了提供加密和解密功能外,还具有认证和完整性校验的能力。
- GCM模式在加密过程中会使用一个称为"Nonce"的值,用于确保每个加密操作的唯一性,避免重复使用Nonce导致的安全问题。
- GCM模式可以同时进行加密和认证,因此在一些场景中可以减少通信的复杂性和性能开销。
- 由于GCM模式的性能较高且提供了认证能力,通常在需要较高性能和安全性的场景中被使用。
-
CBC模式(Cipher Block Chaining):
- CBC是一种较为传统的分组加密模式,每个分组的密文会依赖于前一个分组的明文,因此具有一定的关联性。
- CBC模式需要在加密前进行填充操作,以适应固定长度的分组大小。
- 由于每个分组的加密都依赖于前一个分组的密文,所以在并行处理时可能存在性能上的限制。
- CBC模式通常需要额外的认证步骤来保证数据完整性和安全性。
性能方面,GCM模式通常在处理大量数据时具有更好的性能,因为它可以同时进行加密和认证,而且不需要像CBC那样需要明确的填充步骤。然而,由于GCM模式引入了额外的认证计算,对于较小的数据块,GCM的性能可能会略逊于CBC。
选择合适的加密模式取决于具体的应用场景和需求。如果你需要高性能和认证能力,可以考虑使用GCM模式。如果你更关注传统的分组加密模式,并且不需要认证能力,可以选择CBC模式。
GCM 由于使用一个称为"Nonce"的值,每次加密都需要保证唯一 ,每次需要新建实例 ,避免重复使用Nonce导致的安全问题 这就导致了性能来讲可能稍逊于 CBC 且GCM模式 不需要补码