筆者日常:這幾天筆者換公司了,中途休息了幾天,找工作又花了幾天,博客這陣子有點落下了。
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
第五步:正常使用即可。