Spring Boot 配置文件密碼加密兩種方案


Spring Boot 配置文件密碼加密兩種方案

jasypt 加解密

jasypt 是一個簡單易用的加解密Java庫,可以快速集成到 Spring 項目中。可以快速集成到 Spring Boot 項目中,並提供了自動配置,使用非常簡單。
jasypt 庫已上傳到 Maven 中央倉庫, 在 GitHub 上有更詳細的使用說明
jasypt 的實現原理是實現了 ApplicationContextInitializer 接口,重寫了獲取環境變量的方法,在容器初始化時對配置文件中的屬性進行判斷,若包含前后綴(ENC())表示是加密屬性值,則進行解密並返回。

你的配置文件是不是還在使用下面這種落后的配置暴露一些密碼:

jdbc.url=jdbc:mysql://127.0.0.1:3305/afei
jdbc.username=afei
jdbc.password=123456

如果是,那么繼續往下看。筆者今天介紹史上最優雅加密接入方式:jasypt

使用方式

  • 用法一

先看用法有多簡單,以springboot為例:

  1. Application.java上增加注解@EnableEncryptableProperties(jasypt-spring-boot-starter包不需要該配置);

  2. 增加配置文件jasypt.encryptor.password = Afei@2018,這是加密的秘鑰;

  3. 所有明文密碼替換為ENC(加密字符串),例如ENC(XW2daxuaTftQ+F2iYPQu0g==);

  4. 引入一個MAVEN依賴;

maven坐標如下:

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot</artifactId>
    <version>2.0.0</version>
</dependency>

簡答的4步就搞定啦,是不是超簡單?完全不需要修改任何業務代碼。    其中第三步的加密字符串的生成方式為:
java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="123456" password=Afei@2018 algorithm=PBEWithMD5AndDES

其中:

  • input的值就是原密碼。

  • password的值就是參數jasypt.encryptor.password指定的值,即秘鑰。


  • 用法二

其實還有另一種更簡單的姿勢:

  1. 增加配置文件jasypt.encryptor.password = Afei@2018,這是加密的秘鑰;

  2. 所有明文密碼替換為ENC(加密字符串),例如ENC(XW2daxuaTftQ+F2iYPQu0g==);

  3. 引入一個MAVEN依賴;

maven坐標如下:

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

相比第一種用法,maven坐標有所變化。但是不需要顯示增加注解@EnableEncryptableProperties;

github地址

github:https://github.com/ulisesbocchio/jasypt-spring-boot
它github首頁有詳細的用法說明,以及一些自定義特性,例如使用自定義的前綴和后綴取代ENC():

jasypt.encryptor.property.prefix=ENC@[
jasypt.encryptor.property.suffix=]

原理解密

既然是springboot方式集成,那么首先看jasypt-spring-boot的spring.factories的申明:

org.springframework.context.ApplicationListener=\
com.ulisesbocchio.jasyptspringboot.configuration.EnableEncryptablePropertiesBeanFactoryPostProcessor

這個類的部分核心源碼如下:

public class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, ApplicationListener<ApplicationEvent>, Ordered {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 得到加密字符串的處理類(已經加密的密碼通過它來解密)
        EncryptablePropertyResolver propertyResolver = beanFactory.getBean(RESOLVER_BEAN_NAME, EncryptablePropertyResolver.class);
        // springboot下的Environment里包含了所有我們定義的屬性, 也就包含了application.properties中所有的屬性
        MutablePropertySources propSources = environment.getPropertySources();
        // 核心,PropertySource的getProperty(String)方法委托給EncryptablePropertySourceWrapper
        convertPropertySources(interceptionMode, propertyResolver, propSources);
    }

    @Override
    public int getOrder() {
        // 讓這個jasypt定義的BeanFactoryPostProcessor的初始化順序最低,即最后初始化
        return Ordered.LOWEST_PRECEDENCE;
    }
}

PropertySource的getProperty(String)方法委托給EncryptablePropertySourceWrapper,那么當獲取屬性時,實際上就是調用EncryptablePropertySourceWrapper的getProperty()方法,在這個方法里我們就能對value進行解密了。

EncryptablePropertySourceWrapper實現了接口EncryptablePropertyResolver,該定義如下:

// An interface to resolve property values that may be encrypted.
public interface EncryptablePropertyResolver {

    String resolvePropertyValue(String value);
}

接口描述:
Returns the unencrypted version of the value provided free on any prefixes/suffixes or any other metadata surrounding the encrypted value. Or the actual same String if no encryption was detected.

  • 如果通過prefixes/suffixes包裹的屬性,那么返回解密后的值;

  • 如果沒有被包裹,那么返回原生的值;

實現類的實現如下:

@Override
public String resolvePropertyValue(String value) {
    String actualValue = value;
    // 如果value是加密的value,則進行解密。
    if (detector.isEncrypted(value)) {
        try {
            // 解密算法核心實現
            actualValue = encryptor.decrypt(detector.unwrapEncryptedValue(value.trim()));
        } catch (EncryptionOperationNotPossibleException e) {
            // 如果解密失敗,那么拋出異常。
            throw new DecryptionException("Decryption of Properties failed,  make sure encryption/decryption passwords match", e);
        }
    }
    // 沒有加密的value,返回原生value即可
    return actualValue;
}

判斷是否是加密的邏輯很簡單:(trimmedValue.startsWith(prefix) && trimmedValue.endsWith(suffix)),即只要value是以prefixe/suffixe包括,就認為是加密的value。

總結

通過對源碼的分析可知jasypt的原理很簡單,就是講原本spring中PropertySource的getProperty(String)方法委托給我們自定義的實現。然后再自定義實現中,判斷value是否是已經加密的value,如果是,則進行解密。如果不是,則返回原value。注意該方式由於會在bean初始化前做一些操作, 和dubbo混用是容易導致對dubbo的初始化進行預操作, 導致dubbo加載失敗

 

druid 非對稱加密

數據庫連接池 Druid 自身支持對數據庫密碼的加密解密, 是通過 ConfigFilter 實現的,在 GitHub 有官方的指導說明

  1. 添加依賴,

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
    </dependency>
  2. 配置數據源,先用明文密碼連接數據庫

    1
    2
    spring.datasource.username=root
    spring.datasource.password=123456
  3. 編寫測試代碼生成非對稱加密的公鑰和私鑰

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ApplicationTests {

    @Test
    public void druidEncrypt() throws Exception {
    //密碼明文
    String password = "123456";
    System.out.println("明文密碼: " + password);
    String[] keyPair = ConfigTools.genKeyPair(512);
    //私鑰
    String privateKey = keyPair[0];
    //公鑰
    String publicKey = keyPair[1];

    //用私鑰加密后的密文
    password = ConfigTools.encrypt(privateKey, password);

    System.out.println("privateKey:" + privateKey);
    System.out.println("publicKey:" + publicKey);

    System.out.println("password:" + password);

    String decryptPassword = ConfigTools.decrypt(publicKey, password);
    System.out.println("解密后:" + decryptPassword);
    }
    }

    #------------結果------------------
    明文密碼: 123456
    privateKey:MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAlgDJ+BjPrmzXfnZ3DYddy7LyVqvyWkbDkVuw+hhsKPZNJRpuCjAGj9omHoj4EJ5ZMsW8emKapCPZaKKUtw1DhQIDAQABAkAgpdtPnFbXZ+kfJTmUQDox86i7JIGDFJPMN2C1jks8PsoKRuMwbSSXd3owdGyEQ28bJa3EOEdkGex+2IqsfZwBAiEAx7aclTD+MVsx9dkOcp5oWpCDpQCK0gbnyIeS5arUcyECIQDAR5Czh8ejceRRcG7yH13+FcC2GIgtLxYmi691hrBn5QIhAJuRCcPFGByGNxKUc4ahEhSJwaIEHB6iNmakBK9WNItBAiEAtXBSmTadKhxEyJyB9LOorCS2rp5Dke+GxWS2cv5f5AkCIQCwhGIq7dmtg12cK4S63zD9/SIbLMTW89ph4rgQFEsoMg==

    publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJYAyfgYz65s1352dw2HXcuy8lar8lpGw5FbsPoYbCj2TSUabgowBo/aJh6I+BCeWTLFvHpimqQj2WiilLcNQ4UCAwEAAQ==

    password:CFJ5PUOf0GLY56E27pCPI12eHFqtFzVk/XcBN49qr1e/ya/X1eN4FtGLnaEe/7VPefF40UKPgSqFMbnfPLKAiA==

    生成公鑰和私鑰,還可使用命令生成:java -cp druid-1.0.16.jar com.alibaba.druid.filter.config.ConfigTools you_password

  4. 配置文件增加解密支持,並替換明文密碼

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #---------密碼加密------------------------
    spring.datasource.username=panda
    spring.datasource.password=CFJ5PUOf0GLY56E27pCPI12eHFqtFzVk/XcBN49qr1e/ya/X1eN4FtGLnaEe/7VPefF40UKPgSqFMbnfPLKAiA==
    #---------開啟ConfigFilter支持-----------
    spring.datasource.druid.filter.config.enabled=true
    #---------設置公鑰------------------------
    spring.datasource.publicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAINRom1IY639dDMD0FFw7zMsxRVABYGJnKxSpO84dyJgXaIkoTZkE1JaWE2/gtgli28vgM72UHf2EGhxbLZwzhsCAwEAAQ==
    #---------設置連接屬性---------------------
    spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${spring.datasource.publicKey}
  5. 重啟應用, 查看數據源初始化時的連接是否成功。
    再謹慎點是把測試生成密文的java文件也刪除。

完整配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#=============jdbc dataSource=========================
spring.datasource.name=druidDataSource
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/sakila?characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true

#賬號密碼明文顯示
#spring.datasource.username=panda
#spring.datasource.password=123456

#方案一:jasypt加解密
#spring.datasource.username=ENC(ocj4Go8I46th0NOUs2BdGg==)
#spring.datasource.password=ENC(QA8zJh3woJEjyJjaKCpsiQ==)
#jasypt加密
#jasypt.encryptor.password=vh^onsYFUx^DMCKK

#方案二:druid自帶非對稱加密
spring.datasource.username=root
spring.datasource.password=ai9lB7h4oR9AHrQzU8H38umcelX9dBmx4aSycDOgJWa/2sv5U0GzbyI9sx54sL3nJ0kGayGrTHl3N/Bp1sSJ4w==

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.initial-size=5
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-wait=10
spring.datasource.druid.validationQuery=SELECT 1
spring.datasource.druid.filter.config.enabled=true
#spring.datasource.druid.filters=stat,wall,log4j2,config
spring.datasource.publicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAINRom1IY639dDMD0FFw7zMsxRVABYGJnKxSpO84dyJgXaIkoTZkE1JaWE2/gtgli28vgM72UHf2EGhxbLZwzhsCAwEAAQ==
spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${spring.datasource.publicKey}

注意:最好將密鑰或私鑰作為環境變量參數在執行應用的啟動命令時傳入,而不是放在配置文件中。

示例源碼 -> GitHubhttp://www.gxitsky.com/2018/09/19/springboot-app-32-password-encryptor/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM