笔者日常:这几天笔者换公司了,中途休息了几天,找工作又花了几天,博客这阵子有点落下了。
jasypt简单介绍:
jasypt是国外开发者(@author Daniel Fernández)写的一个对PropertySource资源进行加密保护的依赖工具。我们可以使用其来对一些敏感信息(如:配置文件中的各种账号密码)进行加密保护。
jasypt原理及加解密密钥位置方式说明:
原理说明:
jasypt通过实现了BeanFactoryPostProcessor, ApplicationListener<ApplicationEvent>, Ordered接口的EnableEncryptablePropertiesBeanFactoryPostProcessor类对PropertySources进行识别及解密:当识别到某属性值是以jasypt.encryptor.property.prefix指定的前缀(默认值是ENC()开头,并且以jasypt.encryptor.property.suffix指定的后缀(默认值是))结尾的话,那么就会将对应的密文进行解密还原。解密分两种,对对称加密的解密(加密解密时使用相同的jasypt.encryptor.password即可),对非对称加密的解密(加密时使用公钥PublicKey加密,解密时使用私钥PrivateKey解密)。
注:我们在配置文件中填写账号密码时,需要直接填写密文(如何根据明文生成密文,见下文的示例)。
对称加密时,加解密密钥的设置位置及方式:
对于对称加密在解密时,需要用到的password,可以在以下位置中进行设置。
-
System(系统)
-
properties file(属性文件)。如,在配置文件中配置:
-
command line arguments(命令行参数)。如,在启动jar包时通过-Djasypt.encryptor.password来指定:
-
environment variable(环境变量)。
注:此方式和方式二几乎一样,不过方式二是直接将加解密密钥写在了配置文件中,而此方式则是从环境变量中读
取加解密密钥。
注:linux的环境变量在/etc/profile文件中进行设置;windows右击我的电脑……环境变量中进行设置。 -
......
非对称加密时,加解密密钥的设置位置及方式:
非对称加密在解密时,需要(在配置文件中)指定私钥,可以直接指定私钥字符串,也可以是.crt、.pem、.der等包含私钥的文件。
-
直接字符串形式的私钥:
-
或指定文件证书形式的私钥:
使用示例:
准备工作:在pom.xml中,引入jasypt依赖:
<!-- https://mvnrepository.com/artifact/com.github.ulisesbocchio/jasypt-spring-boot-starter -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
jasypt(对称加密)简单使用示例:
第一步:设置对称加密的加解密密钥等信息。
# 指定前后缀,被前后缀包围的属性值密文,将会被jasypt识别,并进行解密
jasypt.encryptor.property.prefix=ENC@[
jasypt.encryptor.property.suffix=]
### 对称加密 加解密密钥
jasypt.encryptor.password=hello~JustryDeng!
注:设置前后缀不是必须的,但是对于对称加密来说,jasypt.encryptor.password是必须的,
而jasypt.encryptor.password又可以不在配置文件中设置,可以在启动jar包时进行设置。
第二步:使用对称加密,将重要的明文加密转换为密文。
/**
* 对称加密, 根据密钥,【明文生成密文】示例
*
* @date 2019/7/13 5:04
*/
@Test
public void symmetricTest() {
// 基础加密器
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
// 设置对称加密的 加解密 密钥
textEncryptor.setPassword("hello~JustryDeng!");
String info = "dengshuai";
log.info("加密前" + info);
// 加密
String result = textEncryptor.encrypt(info);
log.info("加密后" + result);
// 解密
String originInfo = textEncryptor.decrypt(result);
log.info("解密后" + originInfo);
}
注:这里的密钥,一定要和第一步时指定的密钥一模一样。
本人运行方法后,控制台输出:
第三步:以加密后得到的密文替换配置文件中原来的明文,并在密文前后加上第一步指定的前后缀。
原:
现:
spring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.username=root
### 使用jasypt加密数据库连接密码示例
# 原密码是dengshuai,假设加密后为xxx,那么此处应填写
# jasypt.encryptor.property.prefix + xxx + jasypt.encryptor.property.suffix
# 本人指定的jasypt.encryptor.property.prefix为 ENC@[,jasypt.encryptor.property.suffix为]
# 对称加密示例
spring.datasource.password=ENC@[MgUhHHMeK3ou6/5Uf5fEt4WE+USpGxPz]
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
第四步:正常使用即可。
jasypt(非对称加密)简单使用示例:
第一步:生成一对非对称加密的公钥/私钥。
注:可使用keytool、openssl等工具生成.crt、.der等含公私钥的文件,也可以直接使用Java类
KeyPairGenerator来生成,如:
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
/**
* RSA公钥/私钥 工具类
*
* 注:此工具类摘录自某个网友,具体的我忘记了,这是之前去网上找的,当时忘记把链接记下来了,深感抱歉。
* @author JustryDeng
* @date 2019/7/12 13:19
*/
@SuppressWarnings("unused")
public class KeypairUtil {
private static final String KEY_ALGORITHM = "RSA";
private static final String PUBLIC_KEY = "RSAPublicKey";
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* 获取公钥
*
* @param keyMap 公钥/私钥信息map
* @return 公钥字符串
*/
public static String getPublicKey(Map<String, Object> keyMap) {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return encryptBase64(key.getEncoded());
}
/**
* 获取私钥
*
* @param keyMap 公钥/私钥信息map
* @return 私钥字符串
*/
public static String getPrivateKey(Map<String, Object> keyMap) {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return encryptBase64(key.getEncoded());
}
/**
* 将byte[]型的密钥转换为String类
*
* @param key 公钥/私钥字节数组
* @return 公钥/私钥字符串
*/
private static String encryptBase64(byte[] key) {
return new BASE64Encoder().encodeBuffer(key);
}
/**
* 将String类型的密钥转换为byte[]
*
* @param key 公钥/私钥字符串
* @return 公钥/私钥字节数组
*/
public static byte[] decryptBase64(String key) throws Exception {
return new BASE64Decoder().decodeBuffer(key);
}
/**
* RSA是目前最有影响力的公钥加密算法,该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,
* 但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数
* 组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。
* <p>
* 解密者拥有私钥,并且将由私钥计算生成的公钥发布给加密者。加密都使用公钥进行加密,并将密文发送到
* 解密者,解密者用私钥解密将密文解码为明文。
* <p>
* 以甲要把信息发给乙为例,首先确定角色:甲为加密者,乙为解密者。首先由乙随机确定一个KEY,称之为
* 密匙,将这个KEY始终保存在机器B中而不发出来;然后,由这个 KEY计算出另一个KEY,称之为公匙。这
* 个公钥的特性是几乎不可能通过它自身计算出生成它的私钥。接下来通过网络把这个公钥传给甲,甲收到公
* 钥后,利用公钥对信息加密,并把密文通过网络发送到乙,最后乙利用已知的私钥,就对密文进行解码了。
* 以上就是RSA算法的工作流程。
* <p>
* 算法实现过程为:
* 1. 随意选择两个大的质数p和q,p不等于q,计算N=pq。
* 2. 根据欧拉函数,不大于N且与N互质的整数個数為(p-1)(q-1)。
* 3. 选择一个整数e与(p-1)(q-1)互质,并且e小于(p-1)(q-1)。
* 4. 用以下这个公式计算d:d× e ≡ 1 (mod (p-1)(q-1))。
* 5. 将p和q的记录销毁。
* <p>
* 以上内容中,(N,e)是公钥,(N,d)是私钥。
* <p>
* RSA算法的应用。
* 1. RSA的公钥和私钥是由KeyPairGenerator生成的,获取KeyPairGenerator的实例后还需要设置其密钥位数。
* 设置密钥位数越高,加密过程越安全,一般使用1024位。 *
* KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
* keyPairGen.initialize(1024);
* 2.公钥和私钥可以通过KeyPairGenerator执行generateKeyPair()后生成密钥对KeyPair,
* 通过KeyPair.getPublic()和KeyPair.getPrivate()来获取。
* 动态生成密钥对,这是当前最耗时的操作,一般要2s以上。
* KeyPair keyPair = keyPairGen.generateKeyPair();
* 公钥
* PublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
* 私钥
* PrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
* <p>
* byte[] publicKeyData = publicKey.getEncoded();
* byte[] privateKeyData = publicKey.getEncoded();
* 公钥和私钥都有它们自己独特的比特编码,可以通过getEncoded()方法获取,返回类型为byte[]。
* 通过byte[]可以再度将公钥或私钥还原出来。
*/
public static Map<String, Object> initKey() throws Exception {
// 获取密钥对生成器
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
// 设置密钥位数
keyPairGen.initialize(1024);
// 生成密钥对
KeyPair keyPair = keyPairGen.generateKeyPair();
// 公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<>(4);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
// public static void main(String[] args) throws Exception {
// Map<String, Object> map = initKey();
// System.out.println(getPublicKey(map) + ".");
// System.out.println(getPrivateKey(map) + ".");
// }
}
第二步:设置非对称加密的用来对密文进行解密的私钥信息。
# 指定前后缀,被前后缀包围的属性值密文,将会被jasypt识别,并进行解密
jasypt.encryptor.property.prefix=ENC@[
jasypt.encryptor.property.suffix=]
### 非对称加密
# 非对称加密 私钥类型
jasypt.encryptor.privateKeyFormat=PEM
# 非对称加密 私钥
jasypt.encryptor.privateKeyString=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIxd/gJdD/Ftlo1KQZ87w2HNvFERY4CELDfkn82bPS6LIVXn+JdM3sZkDherWRwBacigO5Qe33d2+A1Gkw/3yTAl/i5IgwAG54aXGmOhzmTCFNXGUEqyFxiWIyaoUNQZBfhw0mr6v+sB3VIpgT8WPbRdjFU/jM8JPDn45KZFrVsVAgMBAAECgYAewiX8LJpmxCXediwlEXqB/wxKE25jZhMueEnQSzk/7rryUS+3L+ANRzWTWDfhnCmrDfmgPpenXQmEFzf4osqSDh1TA+wlBXm2iMjVxz01uI0+CES+4I7AjmsVza4rmO8EUyadjoiBFHHDD3+VjWM+o7Pg33L6Hr5dPjEcXU5GrQJBAO8baqlc+1Qvbjb7CHx0U4tx+I204ehFYpQT5vqk89atxqLjMn8QgTA5cFZwQ5V7+uBR+6ZnTlDh3DDrVxZYCOcCQQCWSLoUkN5ljgsaoUMmlQA6vKFVlA/5Oaul574EDpLtiPZHva/u0pdP2fmnPQebe0sX5ThmeD3Aq0v/p/oX/NCjAkEAs+wah9UK3h9OvRqLGTNjhmO9l8xLzb8gXbLYNTUYsytSdFGoNssRm1steC3D/WEst82ZIm9MFDrQuRLuFkcqcwJAXZRx0qaW5bP6dB2gu+CiYPDeoXRuMenYWZmhd9M/aIwVl3ylldgqgn2f+KSHHSk8DGgeo6gSA+xmiY6mq9MwcwJAE03+ZGbTDJoqmSzOlg/P4ScIZH6dDyeycQB2aHKNblRFHEQEzw6+/bEZh5TxSbV4oBWUQGXRCMlkhRAA/A/oHw==
第三步:使用公钥,将重要的明文加密转换为密文。
/**
* 非对称加密, 利用公钥,将【明文生成密文】示例
*
* @date 2019/7/13 5:04
*/
@Test
public void asymmetricTest() {
SimpleAsymmetricConfig config = new SimpleAsymmetricConfig();
// 设置密钥类型
config.setKeyFormat(AsymmetricCryptography.KeyFormat.PEM);
// 设置用来加密的公钥(注:生成的公钥/私钥可能会有换行,保不保留换行都一样)
config.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMXf4CXQ/xbZaNSkGfO8NhzbxREWOAhCw35J/Nmz0uiyFV5/iXTN7GZA4Xq1kcAWnIoDuUHt93dvgNRpMP98kwJf4uSIMABueGlxpjoc5kwhTVxlBKshcYliMmqFDUGQX4cNJq+r/rAd1SKYE/Fj20XYxVP4zPCTw5+OSmRa1bFQIDAQAB");
StringEncryptor encryptor = new SimpleAsymmetricStringEncryptor(config);
String info = "dengshuai";
log.info("加密前" + info);
// 加密
String result = encryptor.encrypt(info);
log.info("加密后" + result);
// 解密
config.setPrivateKey("MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIxd/gJdD/Ftlo1KQZ87w2HNvFERY4CELDfkn82bPS6LIVXn+JdM3sZkDherWRwBacigO5Qe33d2+A1Gkw/3yTAl/i5IgwAG54aXGmOhzmTCFNXGUEqyFxiWIyaoUNQZBfhw0mr6v+sB3VIpgT8WPbRdjFU/jM8JPDn45KZFrVsVAgMBAAECgYAewiX8LJpmxCXediwlEXqB/wxKE25jZhMueEnQSzk/7rryUS+3L+ANRzWTWDfhnCmrDfmgPpenXQmEFzf4osqSDh1TA+wlBXm2iMjVxz01uI0+CES+4I7AjmsVza4rmO8EUyadjoiBFHHDD3+VjWM+o7Pg33L6Hr5dPjEcXU5GrQJBAO8baqlc+1Qvbjb7CHx0U4tx+I204ehFYpQT5vqk89atxqLjMn8QgTA5cFZwQ5V7+uBR+6ZnTlDh3DDrVxZYCOcCQQCWSLoUkN5ljgsaoUMmlQA6vKFVlA/5Oaul574EDpLtiPZHva/u0pdP2fmnPQebe0sX5ThmeD3Aq0v/p/oX/NCjAkEAs+wah9UK3h9OvRqLGTNjhmO9l8xLzb8gXbLYNTUYsytSdFGoNssRm1steC3D/WEst82ZIm9MFDrQuRLuFkcqcwJAXZRx0qaW5bP6dB2gu+CiYPDeoXRuMenYWZmhd9M/aIwVl3ylldgqgn2f+KSHHSk8DGgeo6gSA+xmiY6mq9MwcwJAE03+ZGbTDJoqmSzOlg/P4ScIZH6dDyeycQB2aHKNblRFHEQEzw6+/bEZh5TxSbV4oBWUQGXRCMlkhRAA/A/oHw==");
String originInfo = encryptor.decrypt(result);
log.info("解密后" + originInfo);
}
提示:本人这里为了看是否加密解密后得到的明文与加密前的明文一致,所以代码中把解密也写出来了。
本人运行方法后,控制台输出(此图过长,本人只截取了部分):
第四步:以加密后得到的密文替换配置文件中原来的明文,并在密文前后加上第一步指定的前后缀。
原:
现:
spring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.username=root
### 使用jasypt加密数据库连接密码示例
# 原密码是dengshuai,假设加密后为xxx,那么此处应填写
# jasypt.encryptor.property.prefix + xxx + jasypt.encryptor.property.suffix
# 本人指定的jasypt.encryptor.property.prefix为 ENC@[,jasypt.encryptor.property.suffix为]
# 非对称加密示例
spring.datasource.password=ENC@[Uji3dGx89KrSaQJmQaKO/SBgA+oUEBlvfkIzkxxbyFdGv9ddPrm7F277BJYZzi5R8UGd0hGy4Pcjva1C+B1H9GFQwm5cbpVCiAp41tcvBq+CHtgd+nuD2nTkqL5HaouGL1vCkGDMsRIozyrNK4re9TMX/6ZkF8B18hlm1uCBcAE=]
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
第五步:正常使用即可。