基于Spring的JAVA开源项目如何保护隐私数据

在 Git 以及 [GitOS](https://git.oschina.net/) 上参与各类开源JAVA项目的过程中, 尤其是自建的开源项目,如何既能有效地保护自己的隐私数据,又能不破坏项目的完整性呢。 在 [JBlog](https://git.oschina.net/newflydd/jblog) 项目开源的过程中,我遇到一个有点头疼的问题: 如何在公开源代码的过程中,保护私有数据不公开,同时又不要破坏整个项目的完整性和连贯性 开源项目,Git 提交,所有的源代码被第三方一览无余。 基于Spring的JAVA项目,数据库URL地址,端口号,用户名,密码等信息一般会被作者明文保存在`.properties`文件中。 而如果我们的数据库不是localhost,而是直接配置在公网上,这些信息的暴露一般是毁灭性的。 无论检出你项目源代码的程序员节操有多么高尚,当看到一个暴露在公网上数据库,端口,用户名,密码完全暴露在自己眼前时,都会忍不住搞破坏的,相信我。 而 JBlog 开发的初期,我还曾经在 [斗鱼网](https://www.douyu.com) 实况直播写代码一段时间,期间关于 IDE 、各种小工具的使用,写到每个模块时的吐槽和注意点什么的,跟网友互动很happy,但当我要写到`.properties`文件时,却不知怎么下手,屏幕暴露在互联网上,我键入的任何字符都公布于众,此时如何保护公网数据库信息呢?(因为家里使用的动态IP地址,所以没法在数据库中对接入的客户端IP地址做限制) 于是我将`.properties`文件写成这样: ``` #application configs #database password encrypt file path #it is important but the file's text can be any string you like jblog.token.filepath = c:/jblog.token #jdbc druid config jdbc.url = ENC(oo+7uqEVC/W3FQPJTUdbEFCPjmsYoSbkk5n/uX8LyD7naZiiqBWnaIT1na85TOApl/xJczx9EyWri0GazU0pB3V9Vf0FSh1IIKbPJMfFw3w=) #jblog_newflypig jdbc.username = newflypig jdbc.password = ENC(xGGGsn8Ni/gDZqumX/5ilg==) jdbc.driverClassName = com.mysql.jdbc.Driver ``` 代码中凡是用`ENC()`括起来的都属于隐私数据,需要解密才能得到明文 那么hibernate是怎么得到明文的呢? 我重新定义了阅读`properties`的类`PropertyPlaceholderConfigurer`: ``` public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { @Override protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { System.out.println("=================EncryptPropertyPlaceholderConfigurer setting successful!=================="); /** * 遍历key,通过key遍历value,对value进行正则匹配 * 将匹配的值删"ENC(",再删")",继续对剩下的字符串进行DES解密 */ String filePath = props.getProperty("jblog.token.filepath"); String secretKey = this.getSecretKey(filePath); if(secretKey == null){ System.out.println("无法读取数据库加密文件,文件地址应该位于:" + filePath); return; } Set keys=props.keySet(); for(Object o:keys){ String key = (String)o; String value = props.getProperty(key); if (value != null && value.matches("ENC\\([\\w+=/]+\\)")) { props.setProperty(key, DESUtil.decryptString(secretKey, value.substring(4,value.length()-1))); } } super.processProperties(beanFactoryToProcess, props); } private String getSecretKey(String filePath){ StringBuffer keyBuffer = new StringBuffer(); try{ InputStream is = new FileInputStream(filePath); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line = reader.readLine(); while(line != null){ keyBuffer.append(line); line = reader.readLine(); } reader.close(); is.close(); }catch(Exception e){ return null; } return keyBuffer.toString(); } } ``` 在这个类中,首先要去读一个文本文件,这个文本文件中装配了解密KEY,文件地址保存在上述`.properties`文件的第4行,也就是`c:/jblog.token`,取到了解密KEY后,在阅读`.properties`文件时如果遇到`ENC()`包围的字符串,则用其对加密后的密文进行DES解密操作,再将明文返回给spring供其注入。 对JAVA中的DES加解密不熟悉的朋友可以去研究一下,并不需要深入理解其中的算法,只需要知道DES是一种可逆加密算法,加密解密需时需要同一个KEY。 我将我自己的`DESUtil.java`类贴出来,可供参照: ``` public class DESUtil { private static byte[] crypt(byte[] data, byte[] key, int mode) throws Exception { // 生成一个可信任的随机数源 SecureRandom sr = new SecureRandom(); // 从原始密钥数据创建DESKeySpec对象 DESKeySpec dks = new DESKeySpec(key); // 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey securekey = keyFactory.generateSecret(dks); // Cipher对象实际完成解密操作 Cipher cipher = Cipher.getInstance("DES"); // 用密钥初始化Cipher对象 cipher.init(mode, securekey, sr); return cipher.doFinal(data); } /** * DES解密 * @param 密文 * @return 明文 */ public static String decryptString(String key, String data){ if (data == null || "".equals(data)) return ""; String str=null; try { str=new String(crypt(new BASE64Decoder().decodeBuffer(data), key.getBytes(),Cipher.DECRYPT_MODE)); } catch (Exception e) { e.printStackTrace(); } return str; } /** * 加密 * @param 明文 * @return 密文 */ public static String encryptString(String key, String data){ if(data==null || "".equals(data)) return ""; String str=null; try { str=new BASE64Encoder().encode(crypt(data.getBytes(), key.getBytes(), Cipher.ENCRYPT_MODE)); } catch (Exception e) { e.printStackTrace(); } return str; } } ``` 最后,我们需要将Spring默认的阅读`.properties`的类替换成我们自己的类 在Spring的配置文件中,重新定义`propertyConfigurer`bean 即可,然后像什么都没发生一样,配置正常的`datasource`以供Hibernate使用: ``` classpath:config.properties ``` 这样,我们的整个流程就清晰了: 1. 首先Spring安排我们自定义的的`propertyConfigurer`去读`.properties`文件。 2. 它在读`properties`时会对每一个key-value做判断,如果这个value被`ENC()`包围了,那么就对其中的字符串进行解密,再返回给Spring,否则直接返回给Spring。 3. 解密时,它会去读一个定义好的磁盘文件,这个文件中存放这着我们的解密密钥,用这个密钥去对密文解密。 如果下载我们源代码的朋友磁盘上没有这个密钥文件,那么他就无法使用我们自用的数据库,对于信任的朋友,我们可以将密钥文件放到云盘上以供下载。普通用户下载回来时,我们建议他们将带有`ENC()`的键值对修改成自己的数据库连接信息,他们可以不使用`ENC()`包围,直接在本地明文配置。

丁丁生于 1987.07.01 ,30岁,英文ID:newflydd

  • 现居住地 江苏 ● 泰州 ● 姜堰
  • 创建了 Jblog 开源博客系统
  • 坚持十余年的 独立博客 作者
  • 大学本科毕业后就职于 中国电信江苏泰州分公司,前两年从事Oracle数据库DBA工作,两年后公司精简技术人员,被安排到农村担任支局长(其本质是搞销售),于2016年因志向不合从国企辞职,在小城镇找了一份程序员的工作。
  • Git OSChina 上积极参与开源社区