接到一个需求,背景是对公司的各个服务器环境下的配置文件内存有数据库用户名,数据库密码,因为在配置文件中,许多shell脚本都需要调用配置文件中的数据库用户名,密码,所以一直以明文保存,需求内容就是实现对配置文件内的用户名,密码加密,同时加密后要解决shell脚本文件还可以调用到正确的用户名密码,因此还需要解密。
因为是在服务器环境下,所以可以编写java程序,然后将程序打成jar包,通过shell指令调用jar包实现加密与解密。
因为要实现逆向解密,所以没有采用MD5,只能用比较老的DES或者AES等,这里采用的是DES。
首先给出配置文件的内容,
不便给出全部内容,因此只有部分重要内容,
*********************
*********************
#数据库用户名
dbuser=cmis
#数据库密码
dbpass=cmis
#用户名
douser=cmis
#密码
dopass=cmis
*********************
*********************假如需要对cmis加密
首先我们需要先实现对一串字符串进行加密与解密,代码如下
import java.awt.List;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;/** * 加密解密 * * doencrypt方法进行加密 * dodencrypt方法进行解密 * @author liuhao * */public class EncryptAndDencrypt {private static Cipher cipher ;
private static byte[] result ; private static SecretKey convertsecretKey; /** * 生成DES加密解密key值 * @return * @throws Exception */ public static SecretKey doKey() throws Exception{ //生成key KeyGenerator keyGenerator = KeyGenerator.getInstance("DES"); keyGenerator.init(56); SecretKey secretKey = keyGenerator.generateKey(); byte[] bytesKey = secretKey.getEncoded(); //KEY转换 DESKeySpec desKeySpec = new DESKeySpec(bytesKey); SecretKeyFactory factory = SecretKeyFactory.getInstance("DES"); SecretKey convertsecretKey = factory.generateSecret(desKeySpec); return convertsecretKey; } /** * 传入明文密码,加密后返回密文 * @param src * @return * @throws Exception */ public static keyAndSecret doencrypt(String src) throws Exception{ keyAndSecret ks = new keyAndSecret(); convertsecretKey = doKey(); cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); //加密 cipher.init(Cipher.ENCRYPT_MODE, convertsecretKey); result = cipher.doFinal(src.getBytes()); ks.setKey(convertsecretKey); ks.setByt(result); return ks; } /** * 传入密文,解密后返回明文密码 * @param byt * @return * @throws Exception */ public static String dodecrypt(byte[] byt,SecretKey sKey) throws Exception { try{ cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); //解密 cipher.init(Cipher.DECRYPT_MODE, sKey); byt = cipher.doFinal(byt); }catch(Exception e){ throw e; } return new String(byt); }}上边的代码是具体实现加密解密的部分。传入字符串后在加密前先会随机生成一个Key,这个是java提供的,由一个factory生成,这个key十分重要,因为加密时的cipher类调用的init方法只传入两个值一个相当于标示作用既Cipher.DECRYPT_MODE,或Cipher.ENCRYPT_MODE,一个为加密标示,一个为解密标示,另一个传入值就是key。我的代码是加密一个字符串就会生成一个新的key,因此因为还有解密过程所以这个key需要保存下来,保存方式接下来会说到。字符串加密后的类型为byte[].
同时呢为了简化代码,将密文与密钥封装:
import javax.crypto.SecretKey;
/** * 对秘钥key与相应密文封装 * @author liuhao * */public class keyAndSecret { private SecretKey key;//密钥 private byte[] byt;//密文 public SecretKey getKey(){ return key; } public void setKey(SecretKey key){ this.key = key; } public byte[] getByt(){ return byt; } public void setByt(byte[] byt){ this.byt = byt; }}接下来是对于配置文件的IO操作了:
import java.io.BufferedReader;
import java.io.FileInputStream;import java.io.InputStreamReader;import java.io.PrintWriter;import java.util.ArrayList;import java.util.List;import java.util.ListIterator; import com.ibm.misc.BASE64Encoder;public class DoEncrypt {
private static String file = "/etldata/etl/bin/config_pass.conf"; private static String file2 = "/etldata/etl/bin/key.conf"; public static void main(String[] args) throws Exception { //字符字节转换编码规范 BASE64Encoder enc = new BASE64Encoder(); BufferedReader brkey = new BufferedReader(new InputStreamReader(new FileInputStream(file2)));BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
List list = new ArrayList(); //按行读取文件内容存入list //读取配置文件内容 int t = 0;//定位标记 while(true){ String str = br.readLine(); if(str==null) break; list.add(str); } br.close(); //读取密钥文件内容 List keylist = new ArrayList(); while(true){ String str = brkey.readLine(); if(str==null) break; keylist.add(str); } brkey.close(); keyAndSecret ks = new keyAndSecret(); //生成密文与相应的密钥 keylist.clear(); ListIterator ite = list.listIterator(); //遍历找到对应行 while(ite.hasNext()){ String str0= ite.next().toString(); if(str0.contains(new String("数据库用户名".getBytes("GBK")))){ t = ite.nextIndex(); break; } } String str = (String) list.get(t); int temp = str.lastIndexOf("=")+1; ks = EncryptAndDencrypt.doencrypt(str.substring(temp)); list.set(t, "dbuser="+enc.encode(ks.getByt()));//对应1密钥 keylist.add(0,enc.encode(ks.getKey().getEncoded()));//1密钥 System.out.println(t+"----"+list.get(t)); while(ite.hasNext()){ String str1 = ite.next().toString(); if(str1.contains(new String("数据库密码".getBytes("GBK")))){ t = ite.nextIndex(); break; } } str = (String) list.get(t); ks = EncryptAndDencrypt.doencrypt(str.substring(temp));//对应2密钥 list.set(t, "dbpass="+enc.encode(ks.getByt())); keylist.add(1, enc.encode(ks.getKey().getEncoded()));//2密钥 System.out.println(t+"----"+list.get(t));while(ite.hasNext()){
String str2 = ite.next().toString(); if(str2.contains(new String("用户名".getBytes("GBK")))){ t = ite.nextIndex(); break; } } str = (String) list.get(t); ks = EncryptAndDencrypt.doencrypt(str.substring(temp)); list.set(t, "douser="+enc.encode(ks.getByt()));//对应3密钥 keylist.add(2,enc.encode(ks.getKey().getEncoded()));//3密钥 System.out.println(t+"----"+list.get(t));while(ite.hasNext()){
String str3 = ite.next().toString(); if(str3.contains(new String("密码".getBytes("GBK")))){ t = ite.nextIndex(); break; } } str = (String) list.get(t); ks = EncryptAndDencrypt.doencrypt(str.substring(temp)); list.set(t, "dopass="+enc.encode(ks.getByt()));//对应4密钥 keylist.add(3,enc.encode(ks.getKey().getEncoded()));//4密钥 System.out.println(t+"----"+list.get(t));//密钥文件路径 PrintWriter pw1 = new PrintWriter(file2); for(int i=0;i<keylist.size();i++){ String str2 =keylist.get(i).toString(); pw1.println(str2); } pw1.close(); //密文保存 PrintWriter pw = new PrintWriter(file); for(int i=0;i<list.size();i++){ String str1 =list.get(i).toString(); System.out.println(str1); pw.println(str1); } pw.close(); }}
其中对于如何key类型的密钥,与byte[]类型的密文存入文件,就需要转换成string字符了,并且在解密时能正确的转换回原来的类型是一个需要特别注意的地方。
这里直接告诉大家byte转换string不能使用toString()方法,因为toString()是返回表示此 Byte
的值的 String
对象。例如,如果byte[]内容是a1b2c3d4=,string内容也是a1b2c3d4=,这并不是我们需要的,因为在解密时读取到a1b2c3d4=后转换为byte[]时结果就不是a1b2c3d4=了。
因此需要BASE64的出场了。调用的encode方法可以将byte[]对象转换成字符串(这里返回的对象不是byte的值,而是真正意义上的转换值),在解密时调用BASE64Decoder的decodeBuffer方法就能将字符串转换BASE64Encoder回我们原先的byte[]类型了。
同理,key类型用方法getEncoded()可以将key类型转换为byte,再使用BASE64Encoder转换为字符串存储即可。
解密代码:
import java.io.BufferedReader;
import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List;import java.util.ListIterator;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;import com.ibm.misc.BASE64Decoder;
public class DoDencypt {
/**
* @param args */ private static String file = "/etldata/etl/bin/config_pass.conf";//配置文件路径 private static String file2 = "/etldata/etl/bin/key.conf"; public static String JM(String str) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); BufferedReader brkey = new BufferedReader(new InputStreamReader(new FileInputStream(file2)));//读取配置文件内容
List list = new ArrayList(); while(true){ String str1 = br.readLine(); if(str1==null) break; list.add(str1); } br.close(); //读取密钥文件内容 List keylist = new ArrayList(); while(true){ String str1 = brkey.readLine(); if(str1==null) break; keylist.add(str1); } brkey.close(); SecretKey key = null; byte[] byt = null; BASE64Decoder dec = new BASE64Decoder();//字符字节转换编码规范 int t = 0;//定位标记 /** * 根据传入值匹配对哪个类型解密 */ if("dbuser".equals(str)){ ListIterator ite = list.listIterator(); while(ite.hasNext()){ String str1 = ite.next().toString(); if(str1.contains(new String("数据库用户名".getBytes("GBK")))){ t = ite.nextIndex(); break; } } str = list.get(t).toString(); byt = (dec.decodeBuffer(keylist.get(0).toString())); key = new SecretKeySpec(byt, "DES"); try { str = EncryptAndDencrypt.dodecrypt(dec.decodeBuffer(str.substring(7)), key); } catch (Exception e) { e.printStackTrace(); } return str; }else if("dbpass".equals(str)){ ListIterator ite = list.listIterator(); while(ite.hasNext()){ String str1 = ite.next().toString(); if(str1.contains(new String("数据库密码".getBytes("GBK")))){ t = ite.nextIndex(); break; } } str = list.get(t).toString(); byt = (dec.decodeBuffer(keylist.get(1).toString())); key = new SecretKeySpec(byt, "DES"); try { str = EncryptAndDencrypt.dodecrypt(dec.decodeBuffer(str.substring(7)), key); } catch (Exception e) { e.printStackTrace(); } return str; }else if("douser".equals(str)){ ListIterator ite = list.listIterator(); while(ite.hasNext()){ String str1 = ite.next().toString(); if(str1.contains(new String("#用户名".getBytes("GBK")))){ t = ite.nextIndex(); break; } } str = list.get(t).toString(); byt = (dec.decodeBuffer( keylist.get(2).toString())); key = new SecretKeySpec(byt, "DES"); try { str = EncryptAndDencrypt.dodecrypt(dec.decodeBuffer(str.substring(7)), key); } catch (Exception e) { e.printStackTrace(); } return str; }else if("dopass".equals(str)){ ListIterator ite = list.listIterator(); while(ite.hasNext()){ String str1 = ite.next().toString(); if(str1.contains(new String("#密码".getBytes("GBK")))){ t = ite.nextIndex(); break; } } str = list.get(t).toString(); byt = (dec.decodeBuffer( keylist.get(3).toString())); key = new SecretKeySpec(byt, "DES"); try { str = EncryptAndDencrypt.dodecrypt(dec.decodeBuffer(str.substring(7)), key); } catch (Exception e) { e.printStackTrace(); } return str; }else{ return "error"; }}
}
解密就不需要多说了,需要注意的就是一定要选对正确的key,因为在加密时的key必须与解密的key相同。
这也是我初次写加密解密以及对文件特殊IO操作的代码,内容可能有很多可以简化或者更加优秀的写法。
对于打包也有一些需要注意,如果需要在Linux下运行jar程序,在打包时需要定一个main函数,一个jar只能定一个main。如果想要给这个jar的main传入参数的话参看一下代码:
import java.io.IOException;
public class ThisMain { public static void main(String[] args) { try { String str = args[0]; String result = DoDencypt.JM(str); System.out.print(result); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}在服务器上的shell指令加密指令是:java -jar 包名
解密指令为:java -jar 包名 传入值