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"/>
完成.