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"/>

完成.

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值