SpringBoot使用jasypt加密密碼等重要信息

筆者日常:這幾天筆者換公司了,中途休息了幾天,找工作又花了幾天,博客這陣子有點落下了。


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

第五步正常使用即可。

 

^_^ 如有不當之處,歡迎指正

^_^ 參考鏈接
              
https://github.com/ulisesbocchio/jasypt-spring-boot

^_^ 測試代碼託管鏈接
              
https://github.com/JustryDeng/Com.../Abc_Jasypt_Demo

^_^ 本文已經被收錄進《程序員成長筆記(五)》,筆者JustryDeng

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章