1 概述
什么?都2020年了還在Spring Boot的配置文件中寫明文密碼?
雖然是小項目,明文也沒人看.
明文簡單快捷方便啊!!!
你看直接用戶名root密碼123456多么簡單!!!
...
不廢話了,這篇文章主要講了如何使用jasypt-spring-boot這個開源組件來進行配置文件的加密,包括簡單加密以及非對稱加密,同時也介紹了使用jar/war部署時如何輸入加密口令.
2 簡單加密
jasypt簡單加密就是直接把加密口令寫死在文件中.(好吧這樣就差不多大概跟沒加密一樣... )
2.1 依賴
目前最新版本為3.0.2,具體請查看官方github(戳這里).
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
2.2 加密口令
在application.properties中加上:
jasypt.encryptor.password=xxx
xxx為對稱加密的口令.
默認使用PBE算法進行加密,PBE其實並沒有包含真正的加密與解密算法,而是將已有的消息摘要算法(如MD5,SHA等)與對稱加密算法(如AES,DES,RC2等)進行了組合,默認組合的是HCMA消息認證算法,SHA512消息摘要算法以及AES256對稱加密算法.PBE使用口令與隨機生成的鹽去生成對應的對稱加密密鑰,再用密鑰去進行對稱加密.
2.3 輸出密文
這里在配置文件中加一個測試字段password與密鑰test進行測試:
這里為了方便就在run里面測試:
@SpringBootApplication
@EnableEncryptableProperties
public class DemoApplication implements CommandLineRunner {
private static final Logger l = LoggerFactory.getLogger(DemoApplication.class);
@Autowired
private StringEncryptor stringEncryptor;
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
Environment environment = applicationContext.getEnvironment();
l.info(stringEncryptor.encrypt(environment.getProperty("password")));
}
}
注意使用@Autowired進行StringEncryptor的自動裝配時,官方文檔說加上
@Configuration
@EnableEncryptableProperties
由於
@SpringBootApplication
包含了
@Configuration
因此這里只需要后一個.
運行后獲取密文輸出:
2.4 替換配置文件
把上面的密文替換到原配置文件,加上前綴ENC(與后綴):
這樣就加密成功了,直接獲取屬性可以看到明文:
3 自定義加密
當然,上面的簡單加密不能滿足實際使用需求,因此,這里需要進行自定義加密.
3.1 自定義加密前后綴
需要使用一個前后綴區分需要加密與不需加密的字段,默認前綴為
ENC(
后綴為:
)
因此加密時需要加上ENC(與).
自定義前后綴指定兩個屬性就可以了:
密碼字段需要對應修改.
3.2 口令參數化
其實就是在啟動的時候加上命令行參數或者應用環境變量,或者通過系統環境變量讀取口令,詳細使用方式請看第4點部署.
命令行參數:
java -jar xxx.jar --jasypt.encryptor.password=xxx
應用環境變量:
java -Djasypt.encryptor.password=xxx -jar xxx.jar
系統環境變量:
jasypt.encryptor.password=${TEST}
前提是已經設置好對應系統變量.
3.3 自定義加密類
可以實現StringEncryptor接口,重寫里面的encrypt與decrypt方法,再定義一個加密配置類,指定加密類的名字:
@Configuration
@EnableEncryptableProperties
public class MyEncryptorConfiguration {
@Bean("MyEncryptor")
public StringEncryptor getStringEncryptor()
{
return new StringEncryptor() {
@Override
public String encrypt(String s) {
return "111";
}
@Override
public String decrypt(String s) {
return "222";
}
};
}
}
這里是一個很簡單的例子,加密直接返回111,解密直接返回222,具體加解密算法直接替換函數體即可.
注意需要在配置文件中寫上Bean的名字:
jasypt.encryptor.bean=codeSheepEncryptorBean
使用構造函數注入(Autowired也可以):
private final StringEncryptor stringEncryptor;
public DemoApplication(MyEncryptorConfiguration encryptorConfiguration)
{
stringEncryptor = encryptorConfiguration.getStringEncryptor();
}
測試:
@Override
public void run(String... args) throws Exception {
Environment environment = applicationContext.getEnvironment();
l.info(stringEncryptor.encrypt(environment.getProperty("password")));
l.info(stringEncryptor.decrypt(environment.getProperty("password")));
}
4 部署
4.1 jar部署
4.1.1 命令行參數方式
這種方式的話先把配置文件中的jasypt.encryptor.password去掉,然后修改在Spring Boot的運行配置,進行本地測試:
打包時,如果測試的話需要設置Maven的參數,不測試的話直接勾選Skip Tests:
打包后(右側Maven->package)加上參數運行就可以了:
4.1.2 應用環境變量方式
其實和第一種方式差不多,也是把jasypt.encryptor.password去掉,在VM options中設置參數,Spring Boot運行配置如下:
Maven設置(當然也可以跳過測試):
不過遺憾的是筆者測試失敗了:
沒理由啊,那為什么Spring Boot那里就這樣設置就可以....
(有大佬知道為什么會失敗的話可以留言,感激不盡.)
這里就直接跳過測試了.
然后就可以愉快地運行了(筆者的win下需要加兩個單引號):
4.1.3 系統環境變量方式
設置環境變量這個應該不用怎么說了,直接去設置就行,然后修改一下jasypt.encryptor.password,兩個花括號中間是對應的環境變量名:
Spring Boot運行配置:
Maven:
這次Maven測試就沒問題了.
真是奇了怪了.
運行(還是這個舒服,直接-jar):
4.2 war部署
4.2.1 jar-war轉換
原來的是jar打包,換成war時,需要修改pom.xml中的<packaging>為war,同時加上tomcat依賴:
<packaging>war</packaging>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
再添加一個ServletInitializer:
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
{
return builder.sources(DemoApplication.class);
}
}
其中DemoApplication為main函數所在的類.
war轉為jar時進行對應的相反操作就可以了.
4.2.2 命令行參數方式
Maven設置就不說了,像上面一樣,打包之后...
筆者找不到設置Tomcat命令行參數的方式,所以,就跳過這個了...
(歡迎大佬找到的留言補充,感激不盡!!!)
筆者太菜了,害.
4.2.3 應用環境變量方式
win下可以直接修改catalina.bat或者進入tomcat9w.exe(tomcat9,tomcat8是tomcat8w.exe)進行圖形化修改,這里選擇修改catalina.bat的方式,找到setlocal,后面加上
set "JAVA_OPTS=-Djasypt.encryptor.password=test"
然后把war放到webapps下就可以了.
4.2.4 環境變量方式
這種方式最簡單,設置好了環境變量,修改配置文件:
直接war打包部署就行.
5 非對稱加密
Spring Boot2.2.1之后支持非對稱加密,密鑰對的格式可以為PEM/DER.
5.1 加密
這里使用的一位大佬的RSA自定義位數加密工具類(戳這里),無需額外依賴,僅自帶JDK實現(JDK8+).
import java.util.Base64;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* Java RSA 加密工具類
* 參考: https://blog.csdn.net/qy20115549/article/details/83105736
*/
public class Test {
/**
* 密鑰長度 於原文長度對應 以及越長速度越慢
*/
private final static int KEY_SIZE = 2048;
/**
* 用於封裝隨機產生的公鑰與私鑰
*/
private static Map<Integer, String> keyMap = new HashMap<Integer, String>();
/**
* 隨機生成密鑰對
*/
public static void genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGenerator類用於生成公鑰和私鑰對,基於RSA算法生成對象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密鑰對生成器
keyPairGen.initialize(KEY_SIZE, new SecureRandom());
// 生成一個密鑰對,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私鑰
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公鑰
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
// 得到私鑰字符串
String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());
// 將公鑰和私鑰保存到Map
//0表示公鑰
keyMap.put(0, publicKeyString);
//1表示私鑰
keyMap.put(1, privateKeyString);
}
/**
* RSA公鑰加密
*
* @param str 加密字符串
* @param publicKey 公鑰
* @return 密文
* @throws Exception 加密過程中的異常信息
*/
public static String encrypt(String str, String publicKey) throws Exception {
//base64編碼的公鑰
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
/**
* RSA私鑰解密
*
* @param str 加密字符串
* @param privateKey 私鑰
* @return 明文
* @throws Exception 解密過程中的異常信息
*/
public static String decrypt(String str, String privateKey) throws Exception {
//64位解碼加密后的字符串
byte[] inputByte = Base64.getDecoder().decode(str);
//base64編碼的私鑰
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
public static void main(String[] args) throws Exception {
long temp = System.currentTimeMillis();
//生成公鑰和私鑰
genKeyPair();
//加密字符串
System.out.println("公鑰:" + keyMap.get(0));
System.out.println("私鑰:" + keyMap.get(1));
System.out.println("生成密鑰消耗時間:" + (System.currentTimeMillis() - temp) / 1000.0 + "秒");
// String message = "RSA測試ABCD~!@#$";
String message = "test";
System.out.println("原文:" + message);
temp = System.currentTimeMillis();
String messageEn = encrypt(message, keyMap.get(0));
System.out.println("密文:" + messageEn);
System.out.println("加密消耗時間:" + (System.currentTimeMillis() - temp) / 1000.0 + "秒");
temp = System.currentTimeMillis();
String messageDe = decrypt(messageEn, keyMap.get(1));
System.out.println("解密:" + messageDe);
System.out.println("解密消耗時間:" + (System.currentTimeMillis() - temp) / 1000.0 + "秒");
}
}
5.2 修改配置文件
把明文輸入,得到密文與私鑰后,替換原來的配置文件:
密文復制到對應加密字段,加上前后綴,同時私鑰格式選擇der,把私鑰復制過去:
運行測試沒問題就可以了.