1. 程式人生 > >properties檔案加密處理

properties檔案加密處理

前言

  開發中會把一些屬性配置放在properties,以方便進行管理,但是如果相關資料未進行加密,便可能導致一些私密資料暴露,比如資料庫的使用者名稱和密碼.本文章主要講解兩種方式對資料庫配置檔案進行加密.
  一種是使用druid自帶的配置,該方式使用簡便,但是通用性不強,只適用於資料庫檔案加密.另一種是使用java編碼解決.該方式開發較複雜,但是通用性強.
  藍莓商城專案cms.web使用的是druid方式進行加密處理,os.web使用的是Java編碼方式進行加密處理.更多的詳情請自行檢視.

常規處理-不加密

資料庫properties配置檔案

jdbc.driver
=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/lanmei?characterEncoding=utf-8 jdbc.username=root jdbc.password=xxxxxxxxx jdbc.pool.init=5 jdbc.pool.minIdle=5 jdbc.pool.maxActive=10 jdbc.maxWait=1000 jdbc.timeBetweenEvictionRunsMillis=500 jdbc.minEvictableIdleTimeMillis=40000 jdbc.testSql=show tables;

xml引入properties配置檔案

<!-- 引入資料庫配置檔案 -->
<context:property-placeholder location="classpath:mysqljdbc.properties" />

xml配置檔案裡面使用”${jdbc.driver}”方式呼叫

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">
        <!-- 資料來源驅動類可不寫,Druid預設會自動根據URL識別DriverClass -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" /> <!-- 基本屬性 url、user、password --> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="${jdbc.pool.init}" /> <property name="minIdle" value="${jdbc.pool.minIdle}" /> <property name="maxActive" value="${jdbc.pool.maxActive}" /> <!-- 配置獲取連線等待超時的時間 --> <property name="maxWait" value="${jdbc.maxWait}" /> <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" /> <!-- 配置一個連線在池中最小生存的時間,單位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${jdbc.testSql}" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <!-- 開啟PSCache,並且指定每個連線上PSCache的大小(Oracle使用) --> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> <!-- 配置監控統計攔截的filters --> <property name="filters" value="stat" /> </bean>

這種方式不對配置進行加密,很容易造成安全問題.

使用druid的配置進行加密處理

如果專案使用druid做所謂資料庫連線池,那麼可以使用其ConfigFilter進行配置.這裡只講解當配置檔案從本地中讀取時的加密處理,更多方式請參考druid官方文件
我使用的druid版本為1.0.29,放置在.m2/repository/com/alibaba/druid/1.0.29/
目錄下.
進入該目錄

cd .m2/repository/com/alibaba/druid/1.0.29/

再執行目錄下的com.alibaba.druid.filter.config.ConfigTools類,引數為你的資料庫密碼.

java -cp druid-1.0.29.jar com.alibaba.druid.filter.config.ConfigTools 563739007

druid使用的是不可逆加密方式,因此會有私鑰和公鑰.
privateKey:私鑰;
publicKey:公鑰;
password:加密後的密碼

privateKey:MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAw3NrAA8X/2MGuvf8HGn2VlOjGW7qkIh3NFv9jtYsyHtsqtReVuUAfxPZM4sPNu72qcxEcVavekAz7Gb58n3PlwIDAQABAkB6LQDa9ZRzsWw4neG7xUUWa4vNzzbTiGqzkTlr+1fdLapgm/1Pb1il8Z/Plxnl7wdl0HHq02TbyZEa17fgu96JAiEA5SHvQcarJdBH7ResAKcMa/2WNmYSTCvy1BMwICNcfkMCIQDaXm5h4un8cafC4iG5Q4+Y+KL2gkN72bz+VmzbF/NWHQIhAKZ0opWMOCU+TCJHgiLvOCzzij52pHBFtSCv19RhG/51AiEAs5Fbq9rxFspPbg6ONM690skDGTrdS4ctxuhC85eqXnECIF9WCpnQp+A3M5EkYj4yG+uCeCvIiFLkvZW+DlSJa6SX
publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMNzawAPF/9jBrr3/Bxp9lZToxlu6pCIdzRb/Y7WLMh7bKrUXlblAH8T2TOLDzbu9qnMRHFWr3pAM+xm+fJ9z5cCAwEAAQ==
password:g6n3goSvd0ggKOlZ+KEVXsfc84/KWjvOd6t1VLUr/cOFW3k4E3NN1PtzR5e3ImRgZWktN0NLDIZIXGZjQ+IiLg==

這裡寫圖片描述

在資料庫配置檔案中新增jdbc.publicKey屬性,並將上述的publicKey和password填入.由於每次生成的資料金鑰不一樣,因此上面生成的金鑰和圖中的還有下面的不一樣,不必拘泥於這個.

jdbc.publicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIe5K9tyOgml4FWfhmhLtvGIJohv9QUk0EJDrAcqxfjEgZJOvaeFEU0eN+WhPGM6KOt8SabL45Jo823D0NCwGtUCAwEAAQ==
jdbc.password=hcqgyt5Q3IanwC8BIz6XZM10/El+vwcPs0CAcaZFzTvw7mFptiANbLv2WoIHFtMc76BCadXBh4VooOFCdidMFg==

修改資料庫xml配置檔案.
只要在上面的配置檔案基礎上修改這兩個地方:
1.filters新增值config,stat是和監控統計攔截相關,兩個屬性值之間使用逗號進行隔開
2.配置屬性connectionProperties,如下圖,這裡就需要jdbc.publicKey了

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">
        <!-- 配置監控統計攔截的filters -->
        <property name="filters" value="config,stat" />
         <property name="connectionProperties" value="config.decrypt=true;config.decrypt.key=${jdbc.publicKey}" />
    </bean> 

至此,就已經配置完成.實現資料庫密碼加密功能.

由於該方式只適用於使用druid作為資料庫連線池的場景,不能適用於其他型別的配置檔案,因此不具有通用性.

使用Java編碼進行加密處理.

之前寫過一篇文章建立SqlSessionFactory時對資料庫配置中的加密使用者名稱和密碼進行解密操作示例,該方式是通過讀取properties檔案中的配置資訊,進行加密後再寫入配置檔案.
  但是Spring 給我們提供了一種更好的方式,使用PropertyPlaceholderConfigurer類來實現對配置檔案的修改.
  其主要的原理在是。Spring容器初始化的時候,會讀取xml或者annotation對Bean進行初始化。初始化的時候,這個PropertyPlaceholderConfigurer會攔截Bean的初始化,初始化的時候會對配置的${pname}進行替換,根據我們Properties中配置的進行替換。從而實現表示式的替換操作 。
  
  PropertyPlaceholderConfigurer繼承自PropertyResourceConfigurer類,後者有幾個protected方法,用於在屬性使用之前對屬性列表中的屬性進行轉換.

//配置檔案中的所有屬性都在props,可對所有的屬性值進行轉換
protected void convertProperties(java.util.Properties props)
//在載入屬性配置檔案並讀取配置屬性時都會呼叫該方法,可以對所有的值進行轉換,返回的是新的propertyValue
protected java.lang.String convertProperty(java.lang.String propertyName,
                                           java.lang.String propertyValue)
//和上面的類似,不過傳入的是propertyValue,沒有傳入屬性名
protected java.lang.String convertPropertyValue(java.lang.String originalValue)

三個方法都是空的,也就是沒有對屬性值進行任何的修改,子類可以擴充套件該類,實現屬性值修改.

一般我們xml配置檔案要使用properties檔案中的屬性,就需要這樣用”${xxxx}”

<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />

但是其使用前必須引入配置檔案,存在兩種方式.
方式1:

<!-- 引入資料庫配置檔案 -->
<context:property-placeholder location="classpath:mysqljdbc.properties" />

方式2:

 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
         p:location="classpath:mysqljdbc.properties"    
         p:fileEncoding="utf-8"/>

但是兩種方式只能選擇其中之一,為了實現配置檔案屬性加密功能,選用方式2.

修改屬性值處理類

建立子類EnctryptPropertyPlaceholderConfigurer繼承PropertyPlaceholderConfigurer,實現對屬性的修改.
處理流程:呼叫函式-->檢查該屬性是否需要解密-->(需要解密)呼叫加密工具類對屬性值進行解密-->獲取到解密後的資料(為原始資料,比如使用者名稱或者密碼等)-->返回解密後的資料-->Spring會自動將該屬性更新為新的屬性值.

package org.lanmei.os.utils.property;

import org.lanmei.os.utils.des.DESUtils;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer;

public class EnctryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {

    //定義需要解密的屬性
    private String[] enctryptPropertyValue= {"jdbc.username","jdbc.password"};
    /**
     * 在載入屬性配置檔案並讀取配置屬性時都會呼叫該方法,可以對所有的值進行轉換,返回的是新的propertyValue
     */
    @Override
    protected String convertProperty(String propertyName, String propertyValue) {
        System.out.println(propertyName + " = " + propertyValue );
        if(isEnctryptPropertyValue(propertyName)) {

            String decryResult = null;
            try {       
                //解密操作
                decryResult = DESUtils.decrypt(propertyValue);
               System.out.println("解密後:"+decryResult);
            } catch (Exception e1) {
                    e1.printStackTrace();
            }
            //返回解密後的屬性值
            return (decryResult);
        }
        return propertyValue;
    } 
    /**
     * 判斷是否需要解密
     * @param value
     * @return
     */
    private boolean isEnctryptPropertyValue(String  value){

        for(String propertyValue:enctryptPropertyValue) {
            if(propertyValue.equals(value)) {
                return true;
            }
        }
        return false;
    }
}

加密工具類

加密演算法使用的是DES,其是一種對稱加密演算法.

package org.lanmei.os.utils.des;

import java.security.SecureRandom;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
/**
 * DFS加密工具類
 * @author lgj
 *
 */
public class DESUtils {
    //任意值,必須為8位,否則報錯
    private static String secKey = "12345678";

    public static void main(String args[]) {
        //這段是用於測試
        if(false) {
             //待加密內容
            String encryptValue = "cryptology";       
            System.out.println("加密的資料為 = " + encryptValue);
            //執行加密
            String enresult = DESUtils.encrypt(encryptValue);

            try {           
              //執行解密
               String decryResult = DESUtils.decrypt(enresult);
               System.out.println("解密後:"+decryResult);
            } catch (Exception e1) {
                    e1.printStackTrace();
            }
        }
        //這段是用於java指令執行時獲取加密資料
        if(true) {
            if((args == null) || (args.length < 1)) {
                System.out.println("請輸入需要加密的字元");
            }
            else {
                System.out.println("正在執行加密....");
                System.out.println("+---------------------------+");
                for(String arg:args) {
                    System.out.println("加密的字元為 = " + arg);
                    String enresult = DESUtils.encrypt(arg);
                    System.out.println("加密完成"); 
                    System.out.println("加密後的字元為: " + enresult);
                    System.out.println("進行解密測試....");
                    String decryResult = null;
                    try {            
                        decryResult = DESUtils.decrypt(enresult);
                       System.out.println("解密後:"+decryResult);
                    } catch (Exception e1) {
                            e1.printStackTrace();
                    }
                    if(decryResult.equals(arg)) {
                        System.out.println("加密成功");
                    }
                    else {
                        System.out.println("加密失敗");
                    }
                    System.out.println("+---------------------------+");
                }

            }
        }

   }
   /**
    * 加密
    * @param datasource byte[]
    * @param password String
    * @return byte[]
    */
   public static  String encrypt(String encryptValue) {   

       Encoder encoder = Base64.getEncoder();   

       try{
           SecureRandom random = new SecureRandom();
           DESKeySpec desKey = new DESKeySpec(secKey.getBytes());
           //建立一個密匙工廠,然後用它把DESKeySpec轉換成
           SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
           SecretKey securekey = keyFactory.generateSecret(desKey);
           //Cipher物件實際完成加密操作
           Cipher cipher = Cipher.getInstance("DES");
           //用密匙初始化Cipher物件,ENCRYPT_MODE用於將 Cipher 初始化為加密模式的常量
           cipher.init(Cipher.ENCRYPT_MODE, securekey, random);
           //現在,獲取資料並加密
           //正式執行加密操作
           byte[] encryptByte = cipher.doFinal(encryptValue.getBytes());
           return  encoder.encodeToString(encryptByte);
       }catch(Throwable e){
               e.printStackTrace();
       }
       return null;
}
   /**
    * 
    * @param str
    * @return
    * @throws Exception
    */
   public static String decrypt(String decryptValue) throws Exception {

          Decoder decoder = Base64.getDecoder();
          byte[] src = decoder.decode(decryptValue);
           // DES演算法要求有一個可信任的隨機數源
           SecureRandom random = new SecureRandom();
           // 建立一個DESKeySpec物件
           DESKeySpec desKey = new DESKeySpec(secKey.getBytes());
           // 建立一個密匙工廠
           SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");//返回實現指定轉換的 Cipher 物件
           // 將DESKeySpec物件轉換成SecretKey物件
           SecretKey securekey = keyFactory.generateSecret(desKey);
           // Cipher物件實際完成解密操作
           Cipher cipher = Cipher.getInstance("DES");
           // 用密匙初始化Cipher物件
           cipher.init(Cipher.DECRYPT_MODE, securekey, random);
           // 真正開始解密操作
           byte[] decryptByte = cipher.doFinal(src);
           return new String(decryptByte);
       }
}

獲取加密後的資料

加密工具類中有一個main()函式,可以通過其獲取加密後的資料
編譯上面的類,進入到工程存放類的路徑下,執行該工具類,並設定傳入的引數為需要加密的資料,比如name和password.
可以一次性加密多個屬性值,屬性值之間空格隔開這裡對使用者名稱和密碼進行加密.執行後便可以獲取到加密後的字元.

java org/lanmei/os/utils/des/DESUtils root 563739007

輸出

正在執行加密....
+---------------------------+
加密的字元為 = root
加密完成
加密後的字元為: FyZA3VCAdiU=
進行解密測試....
解密後:root
加密成功
+---------------------------+
加密的字元為 = 563739007
加密完成
加密後的字元為: LfNyV2NqhLbXICUpTIAFcA==
進行解密測試....
解密後:563739007
加密成功
+---------------------------+

這裡寫圖片描述

修改資料庫配置檔案

將加密後的字元替換原來的使用者名稱root和原始密碼

jdbc.username=FyZA3VCAdiU=
jdbc.password=LfNyV2NqhLbXICUpTIAFcA==

修改xml配置檔案

這裡建立的bean更改為EnctryptPropertyPlaceholderConfigurer類

 <bean class="org.lanmei.os.utils.property.EnctryptPropertyPlaceholderConfigurer"
         p:location="classpath:mysqljdbc.properties"    
         p:fileEncoding="utf-8"/>

完成.