jasypt 加解密
参考:
- jasypt 的 GitHub 官方网址
- jasypt加密
- Jasypt 开源加密库使用教程
- SpringBoot配置文件中密码属性加密
概述
- Jasypt 全称 Java Simplified Encryption ,是 Sourceforge.net 上的一个开源项目。
- Jasypt 可用于加密任务与应用程序,例如加密密码、敏感信息和数据通信,还包括高安全性、基于标准的加密技术、可同时单向和双向加密的加密密码、文本、数字和二进制文件。
- Jasypt 还符合 RSA 标准的基于密码的加密,并提供了无配置加密工具以及新的、高可配置标准的加密工具、加密属性文件(encryptable properties files)、Spring work 集成、加密 Hibernate 数据源配置、新的命令行工具、UR L加密的 Apache wicket 集成以及升级文档。
- Jasypt 也可以与 Acegi Security 整合,即 Spring Security。Jasypt 亦拥有加密应用配置的集成功能,而且提供一个开放的 API 从而任何一个 Java Cryptography Extension 都可以使用 Jasypt
出于安全考虑,Spring boot 配置文件中的敏感信息通常需要对它进行加密/脱敏处理,尽量不使用明文。
开源安全框架 Jasypt Spring Boot 为 Spring Boot 应用程序中的属性源提供加密支持,专门用于处理 Spring boot 属性加密,在配置文件中使用特定格式直接配置密文,然后应用启动的时候,Jasypt 会自动将密码解密成明文供程序使用。
- Jasypt 加密属性配置格式:secret.property=ENC(nrmZtkF7T0kjG/VodDvBw93Ct8EgjCA+),ENC() 就是它的标识,程序启动的时候,会自动解密其中的内容,如果解密失败,则会报错。
- 所以获取这些属性值和平时没有区别,直接使用如 @Value("${secret.property}") 获取即可,取值并不需要特殊处理。
- Jasypt 同一个密钥(secretKey)对同一个内容执行加密,每次生成的密文都是不一样的,但是根据根据这些密文解密成原内容都是可以的.
jasypt 集成方式(含依赖)
在项目中集成 jasypt-spring-boot 有三种方式:
方式1:
如果是 Spring Boot 应用程序,使用了注解 @SpringBootApplication 或者 @EnableAutoConfiguration,那么只需添加 jasypt-spring-boot-starter 依赖,此时整个 Spring 环境就会支持可加密属性配置(这意味着任何系统属性、环境属性、命令行参数,yaml、properties 和任何其他自定义属性源可以包含加密属性)
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>
方式2:
如果没有使用 @SpringBootApplication 或者 @EnableAutoConfiguration,则将 jasypt-spring-boot 添加到类路径
1)依赖
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot</artifactId> <version>3.0.3</version> </dependency>
2)将 @EnableEncryptableProperties 添加到配置类中,以便在整个 Spring 环境中启用可加密属性:
@Configuration @EnableEncryptableProperties public class MyApplication { ... }
方式3:
如果不使用 @SpringBootApplication 或者 @EnableAutoConfiguration 自动配置注解,并且不想在整个 Spring 环境中启用可加密的属性,则可以使用本方式
1)将依赖项添加到项目中
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot</artifactId> <version>3.0.3</version> </dependency>
2)配置文件中添加任意数量的 @EncryptablePropertySource 注解(就像使用 Spring 的 @PropertySource 注解一样)
@Configuration @EncryptablePropertySource(name = "EncryptedProperties", value = "classpath:encrypted.properties") public class MyApplication { ... }
2)或者还可以使用 @EncryptablePropertySources 注解来对 @EncryptablePropertySource 类型的注解进行分组
@Configuration @EncryptablePropertySources({@EncryptablePropertySource("classpath:encrypted.properties"), @EncryptablePropertySource("classpath:encrypted2.properties")}) public class MyApplication { ... }
注:从 1.8 版起,@EncryptablePropertySource 支持 YAML 文件
yml 配置及参数说明
yml 配置
# jasypt 配置加密
jasypt:
encryptor:
# 自定义加密盐值(密钥)
password: jasypt
# 加密算法设置
algorithm: PBEWithMD5AndDES
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
salt-generator-classname: org.jasypt.salt.RandomSaltGenerator
参数说明
Key | Required | Default Value | 说明 |
---|---|---|---|
jasypt.encryptor.password | True | - | 自定义加密盐值(密钥) |
jasypt.encryptor.algorithm | False | PBEWITHHMACSHA512ANDAES_256 | 加密算法, 必须由 JCE 提供程序支持 |
jasypt.encryptor.key-obtention-iterations | False | 1000 | 获取加密密钥的哈希迭代次数 |
jasypt.encryptor.pool-size | False | 1 | 要创建的加密程序池的大小 |
jasypt.encryptor.provider-name | False | SunJCE | 请求加密算法的安全提供程序的名称 |
jasypt.encryptor.provider-class-name | False | null | |
jasypt.encryptor.iv-generator-classname | False | org.jasypt.iv.RandomIvGenerator | IV 发生器 |
jasypt.encryptor.salt-generator-classname | False | org.jasypt.salt.RandomSaltGenerator | Sal 发生器 |
jasypt.encryptor.string-output-type | False | base64 | 字符串输出的编码形式 可用的编码类型有: base64、hexadecimal(16进制) |
jasypt.encryptor.proxy-property-sources | False | false | |
jasypt.encryptor.skip-property-sources | False | empty list | |
jasypt.encryptor.property.prefix | False | ENC( | 设置密文前缀 |
jasypt.encryptor.property.suffix | False | ) | 设置密文后缀 |
注:
- 唯一需要的属性是 jasypt.encryptor.password ,其余的可以使用默认值。
虽然所有这些属性都可以在属性文件中声明,但为了安全 password 属性官方不推荐存储在属性文件中,而应作为系统属性、命令行参数或环境变量传递。
- 官网默认加解密算法为 "PBEWITHHMACSHA512ANDAES_256",它是 sha512 加 AES 高级加密,需要 Java JDK 1.9 及以上支持,或者添加 JCE 无限强度权限策略文件,否则运行会报错:”加密引发异常,一个可能的原因是您正在使用强加密算法,并且您没有在这个Java虚拟机中安装Java加密扩展(JCE)无限强权限策略文件“。
- 默认使用 com.ulisesbocchio.jasyptspringboot.encryptor.DefaultLazyEncryptor 进行加解密
标准 StringEncryptor 的所有属性,都可以在全局配置文件中进行配置。也可以在后台添加 StringEncryptor bean,此时默认的加密程序将被忽略。
import org.jasypt.encryption.StringEncryptor; import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JasyptConfig { /** * 自定义 StringEncryptor,覆盖默认的 StringEncryptor * bean 名称是必需的,从 1.5 版开始按名称检测自定义字符串加密程序,默认 bean 名称为:jasyptStringEncryptor */ @Bean("jasyptStringEncryptor") public StringEncryptor jasyptStringEncryptor() { PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); SimpleStringPBEConfig config = new SimpleStringPBEConfig(); config.setPassword("jasypt"); config.setAlgorithm("PBEWithMD5AndDES"); config.setKeyObtentionIterations("1000"); config.setPoolSize("1"); config.setProviderName("SunJCE"); config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator"); config.setStringOutputType("base64"); encryptor.setConfig(config); return encryptor; } }
注意:
- bean 名称是必需的,因为 jasypt spring boot 从 1.5 版开始按名称检测自定义字符串加密程序,默认 bean 名称为:jasyptStringEncryptor
但也可以通过定义属性来覆盖,例如 jasypt.encryptor.bean=encryptorBean,然后使用该名称定义自定义加密程序:
@Bean("encryptorBean") public StringEncryptor stringEncryptor() { ... }
加解密工具类
要获取密文,就是将需要加密的数据进行加密,方法多样,可以通过命令行操作 jar 包获取,也可以直接使用代码进行加密。
推荐使用代码加密,下面提供一个工具类进行加密,注意事项:
- Jasypt 默认使用 StringEncryptor 解密属性,所以加密时默认也得使用 StringEncryptor 加密,否则启动时解密失败报错
StringEncryptor 接口有很多的实现类,常用 PooledPBEStringEncryptor
- 加密与解密对 StringEncryptor 设置的属性必须要一致,比如加密时使用什么算法,那么解密时也得一样,否则启动时解密失败报错
- 常用的加密算法为 "PBEWithMD5AndDES",官网默认的是 "PBEWITHHMACSHA512ANDAES_256",前者是 md5 加 des 标准加密,后者是 sha512 加 AES 高级加密,但需要 Java JDK 1.9 及以上支持,或者添加 JCE 无限强度权限策略文件。
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
public class JasyptUtils {
/**
* 加密
* @param password 加密盐值
* @param text 需要加密的字符串
* @return 加密后的字符串
*/
public static String encrypt(String password, String text) {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setConfig(cryptor(password));
return encryptor.encrypt(text);
}
/**
* 解密
* @param password 加密盐值
* @param text 需要解密的字符串
* @return 解密后的字符串
*/
public static String decrypt(String password, String text) {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setConfig(cryptor(password));
return encryptor.decrypt(text);
}
/**
* 配置(对应yml中的配置)
* @param password 加密盐值
* @return SimpleStringPBEConfig
*/
public static SimpleStringPBEConfig cryptor(String password) {
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
//设置盐值
config.setPassword(password);
//设置算法配置信息
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setProviderName("SunJCE");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
config.setPoolSize("1");
return config;
}
public static void main(String[] args) {
// 加密
String encryptStr = encrypt("jasypt", "root");
// 解密
String decryptStr = decrypt("jasypt", encryptStr);
System.out.println("加密后:" + encryptStr);
System.out.println("解密后:" + decryptStr);
}
}
基本使用
全局配置文件中配置如下,必须设置 jasypt.encryptor.password 属性,algorithm 算法需要与加密时使用的算法一致。
想要对哪个属性进行加密,则使用 ENC() 包裹起来,然后里面放置密文即可,应用启动时会自动被解密。
密文在 yml 配置文件中的使用格式:ENC(密文)
示例:
username: ENC(vyxdS47pJdWBF38TFdmjKmMm4zEO0FQP)
password: ENC(vyxdS47pJdWBF38TFdmjKmMm4zEO0FQP)
拓展:
- SpringBoot 项目(有正常使用
@SpringBootApplication
或者@EnableAutoConfiguration
注解)中可以在代码中使用@Value
注解来直接获取解密后的配置值 Jasypt 默认使用 StringEncryptor 解密属性,所以它默认就已经放置在了 Spring 容器中,可以直接获取使用,比如除了对配置文件中的属性加解密后,还可以做其它任何加解密操作,比如提供一个 Controller 接口用于在线加解密。
因为浏览器地址栏对于特殊字符比较敏感,所以不使用默认的 base64、而改为使用 16 进制字符串
jasypt: encryptor: password: wangmaox # 加密的密钥,自定义即可,必填项 algorithm: PBEWithMD5AndDES # 指定解密算法 string-output-type: hexadecimal # 设置加密内容输出的编码形式,可用的编码类型有 base64、hexadecimal(16进制)
想要使用 StringEncryptor 的地方直接获取使用即可
@Resource private StringEncryptor stringEncryptor; /** * http://localhost:8080/jasypt/encryptor?message=12日下午17点执行任务&isEncrypt=true * http://localhost:8080/jasypt/encryptor?message=702EAA3755766C567F62E83273681A90DC684B6AFADD5CD84691778DAF4A1466E13CE0720E8BABC06081A5D6DBD90EA1&isEncrypt=false * 在线使用 {@link StringEncryptor} 加解密消息。 * * @param message 加/解密的内容 * @param isEncrypt true 表示加密、false 表示解密 */ @GetMapping("jasypt/encryptor") public ObjectNode encrypt(@RequestParam String message, @RequestParam boolean isEncrypt) { JsonNodeFactory nodeFactory = JsonNodeFactory.instance; String encrypt = isEncrypt ? stringEncryptor.encrypt(message) : stringEncryptor.decrypt(message); ObjectNode objectNode = nodeFactory.objectNode(); objectNode.put("code", 200); objectNode.put("data", encrypt); return objectNode; }
密钥(盐值)存储说明
- 加解密过程本身都是通过盐值进行处理的,所以正常情况下盐值和加密串是分开存储的。
盐值应该放在系统属性、命令行或是环境变量来使用,而不是放在配置文件。
密钥传递方式:
# 方式1:启动参数 java -jar jasypt-spring-boot-demo.jar --jasypt.encryptor.password=password # 方式2:系统属性 java -Djasypt.encryptor.password=password -jar jasypt-spring-boot-demo.jar # 方式3:环境变量 jasypt: encryptor: password: ${JASYPT_ENCRYPTOR_PASSWORD:password} # 也可以先设置环境变量 export JASYPT_ENCRYPTOR_PASSWORD=password java -jar jasypt-spring-boot-demo.jar
去掉配置文件中设置的盐值后:
- 在 idea 的启动配置项 “VM options” 添加:Djasypt.encryptor.password=盐值。
- 打包后启动方式:java -jar -Djasypt.encryptor.password=盐值 xxx.jar
使用 jar 包方式-加密
@echo off
set/p input=待加密的明文字符串:
set/p password=加密密钥(盐值):
echo 加密中......
java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input=%input% password=%password% algorithm=PBEWithMD5AndDES
pause
使用 jar 包方式-解密
java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringDecryptionCLI password=123456 algorithm=PBEWithMD5AndDES ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator input=BwNPdUi+syCTKFj/nlbI5fAtGUKuhN8r
发现器、解析器
EncryptablePropertyDetector:发现器
该接口提供了两个方法:
- isEncrypted 方法:判断是否是 jasypt 约定规则加密的属性
- unwrapEncryptedValue 方法:密文预处理。可以自定义返回去除掉前缀和后缀的真正加密的值
该接口默认的实现是 DefaultPropertyDetector
EncryptablePropertyResolver:分解器
该接口中只提供了一个方法:
- resolvePropertyValue 方法:遍历配置文件属性,判断是否是加密属性,然后进行解密返回明文。
在默认实现 DefaultPropertyResolver 中,依赖 EncryptablePropertyDetector 以及 StringEncryptor,真正解密的方法是写在StringEncryptor
该接口默认的实现是 DefaultPropertyResolver
自定义分解器(一般不用自定义,此处仅作示例)
public class JasyptEncryptablePropertyResolver implements EncryptablePropertyResolver {
//自定义解密方法
@Override
public String resolvePropertyValue(String s) {
if (null != s && s.startsWith(MyEncryptablePropertyDetector.ENCODED_PASSWORD_HINT)) {
return PasswordUtil.decode(s.substring(MyEncryptablePropertyDetector.ENCODED_PASSWORD_HINT.length()));
}
return s;
}
}
自定义加密算法
如果不想要使用 jasypt 工具中的加密算法,或者内部要求使用某种特定的加密算法,jasypt-spring-boot 组件提供了自定义加解密的实现方式。
可以通过自己实现 EncryptablePropertyDetector、EncryptablePropertyResolver 接口,并且交给 Spring 管理,设置 bean 名称为 encryptablePropertyDetector 和 encryptablePropertyResolver 来覆盖框架提供的默认实现,完成加密算法和前缀后缀的自定义。
在 yml 文件指定自定义的加解密实现类注入 Spring 容器的 bean 名称
#数据库配置文件加密
jasypt:
encryptor:
## 实现jasypt加密解密的类
bean: customJasyptStringEncryptor
自定义加解密实现类
import com.test.ssmtest.encryption.utils.AESUtils;
import com.test.ssmtest.encryption.utils.Sm4Utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
/**
* 自定义 jasypt 加解密实现类
*/
@Slf4j
public class JasyptCustomStringEncryptor implements StringEncryptor {
@Autowired
private AESUtils aesUtils;
@Autowired
private Sm4Utils sm4Utils;
@Value("${jasypt.encryption.encryptedMethod:aes}")
private String encryptedMethod;
@Override
public String encrypt(String s) {
return s;
}
@Override
public String decrypt(String s) {
log.info("get encrypted text:" + s);
String encryptedText = null;
if (StringUtils.isNotBlank(s)){
try {
String encodedPrefix = s.substring(0, s.indexOf("(")).toUpperCase();
if (JasyptEncryptableDetector.ENCODED_HINT_ENC.equalsIgnoreCase(encodedPrefix)){
encodedPrefix = encryptedMethod;
}
String ciphertext = s.substring(s.indexOf("(") + 1, s.lastIndexOf(")"));
if (AESUtils.AES.equalsIgnoreCase(encodedPrefix)){
encryptedText = aesUtils.decryptAESAndDecode(ciphertext);
} else if (Sm4Utils.SM4.equalsIgnoreCase(encodedPrefix)){
encryptedText = sm4Utils.decryptSM4AndDecode(ciphertext);
}
log.info("generate decrypted text:"+ encryptedText);
if (StringUtils.isNotBlank(encryptedText)){
log.info("decrypt text success!");
} else {
log.error("decrypt text failed!");
}
} catch (Exception e) {
log.error("decrypt text error!", e);
}
}
return encryptedText;
}
}
自定义发现器
为了支持自定义的加密属性前缀,需要自己实现 EncryptablePropertyDetector 接口
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyDetector;
import java.util.Arrays;
/**
* 自定义 jasypt 发现器实现类
*/
public class JasyptEncryptableDetector implements EncryptablePropertyDetector {
private static final String[] ENCODED_HINTS = {"ENC", "AES", "SM4"};
public static final String ENCODED_HINT_ENC = "ENC";
// 判断是否是 jasypt 约定规则加密的属性
@Override
public boolean isEncrypted(String s) {
if (null != s && s.contains("(") && s.contains(")")) {
s = s.trim();
return Arrays.asList(ENCODED_HINTS).contains(s.substring(0, s.indexOf("(")).toUpperCase());
}
return false;
}
// 密文预处理。可以自定义返回去除掉前缀和后缀的真正加密的值。
// 因解密方法需要加密提示判断加密的算法,此处不去除前缀和后缀
@Override
public String unwrapEncryptedValue(String s) {
return s.trim();
}
}
注册 bean
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
// 配置添加该注解,开启属性自动解密功能。若用jasypt-spring-boot-starter依赖包,可以不用配置该注解
// @EnableEncryptableProperties
public class JasyptConfig {
@Bean
@ConditionalOnProperty(name = "jasypt.encryptor.bean", havingValue = "jasyptCustomStringEncryptor")
public JasyptCustomStringEncryptor jasyptCustomStringEncryptor(){
return new JasyptCustomStringEncryptor();
}
@Bean("encryptablePropertyDetector")
@ConditionalOnProperty(name = "jasypt.encryptor.bean", havingValue = "jasyptCustomStringEncryptor")
public JasyptEncryptableDetector jasyptEncryptableDetector(){
return new JasyptEncryptableDetector();
}