最近在項目中,接觸到了數據加解密的業務。數據加密技術是網絡中最基本的安全技術,主要是通過對網絡中傳輸的信息進行數據加密來保障其安全性,這是一種主動安全防禦策略,用很小的代價即可爲信息提供相當大的安全保護。在介紹如何實現之前,需要先來了解一下什麼是加密解密。
1、加密的基本概念
"加密",是一種限制對網絡上傳輸數據的訪問權的技術。原始數據(也稱爲明文,plaintext)被加密設備(硬件或軟件)和密鑰加密而產生的經過編碼的數據稱爲密文(ciphertext)將密文還原爲原始明文的過程稱爲解密,它是加密的反向處理,但解密者必須利用相同類型的加密設備和密鑰對密文進行解密。
加密的基本功能包括:
1. 防止不速之客查看機密的數據文件;
2. 防止機密數據被泄露或篡改;
3. 防止特權用戶(如系統管理員)查看私人數據文件;
4. 使入侵者不能輕易地查找一個系統的文件。
2、加解密工具類的實現
在項目中我主要探究了4種加密技術的實現,分別是:Base64加解密、MD5加解密、AES加解密、DES加解密
2.1 Base64(可逆)
Base64是一種根據ASCII碼來進行加密的可逆算法,這類算法簡單來說,就等於將每個字符對應一個特定的字符(常常是根據進制進行匹配),一一對應,特別像電臺密碼本,因爲每個字符都必須要加密,加密比較笨重。
Base64加密算法
1、獲取字符串中每個字符的ASCII碼
2、按照每3個8bit的字符爲一組來分組,即每組24bit;
3、將這24bit劃分成4個6bit的4個單位,每個單位前面添加2個0,得到4個8bit的單位;
4、將每個8bit的單位轉換成十進制數字,對照Base64編碼表找到對應的字符進行拼接,得到最終的加密後的字符串。
Base64解密算法
1、讀入4個字符,對照Base64編碼表找到字符對應的索引,生成4個6爲的值;
2、將這4個6爲的值拼接起來,形成一個24爲的值;
3、將這個24位的值按照8位一組截斷成3個8位的值;
4、對照ASCII表找到這三個8位的值對應的字符,即解碼後的字符。
/** 工具類實現代碼 **/
public class BASE64Util {
private static final Map<Integer, Character> base64CharMap = new HashMap<>();
private static final String base64CharString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
private static BASE64Util instance;
private BASE64Util() {
for (int i = 0; i < base64CharString.length(); i++) {
char c = base64CharString.charAt(i);
base64CharMap.put(new Integer(i), new Character(c));
}
}
public static BASE64Util getInstance() {
if (instance == null) {
synchronized (BASE64Util.class) {
if (instance == null) {
instance = new BASE64Util();
}
}
}
return instance;
}
/**
* This method is used to encode a normal string to base64 string @param
* origin The String to be encoded @return The String after encoded.
*/
public String encode(String origin) {
if (origin == null) {
return null;
}
if (origin.length() == 0) {
return "";
}
int length = origin.length();
String binaryString = "";
// to binary String
for (int i = 0; i < length; i++) {
int ascii = origin.charAt(i);
String binaryCharString = Integer.toBinaryString(ascii);
while (binaryCharString.length() < 8) {
binaryCharString = "0" + binaryCharString;
}
binaryString += binaryCharString;
}
// to base64 index
int beginIndex = 0;
int endIndex = beginIndex + 6;
String base64BinaryString = "";
String charString = "";
while ((base64BinaryString = binaryString.substring(beginIndex, endIndex)).length() > 0) {
// if length is less than 6, add "0".
while (base64BinaryString.length() < 6) {
base64BinaryString += "0";
}
int index = Integer.parseInt(base64BinaryString, 2);
char base64Char = base64CharMap.get(index);
charString = charString + base64Char;
beginIndex += 6;
endIndex += 6;
if (endIndex >= binaryString.length()) {
endIndex = binaryString.length();
}
if (endIndex < beginIndex) {
break;
}
}
if (length % 3 == 2) {
charString += "=";
}
if (length % 3 == 1) {
charString += "==";
}
return charString;
}
public String decode(String encodedString) {
if (encodedString == null) {
return null;
}
if (encodedString.length() == 0) {
return "";
}
// get origin base64 String
String origin = encodedString.substring(0, encodedString.indexOf("="));
String equals = encodedString.substring(encodedString.indexOf("="));
String binaryString = "";
// convert base64 string to binary string
for (int i = 0; i < origin.length(); i++) {
char c = origin.charAt(i);
int ascii = base64CharString.indexOf(c);
String binaryCharString = Integer.toBinaryString(ascii);
while (binaryCharString.length() < 6) {
binaryCharString = "0" + binaryCharString;
}
binaryString += binaryCharString;
}
// the encoded string has 1 "=", means that the binary string has append
// 2 "0"
if (equals.length() == 1) {
binaryString = binaryString.substring(0, binaryString.length() - 2);
}
// the encoded string has 2 "=", means that the binary string has append
// 4 "0"
if (equals.length() == 2) {
binaryString = binaryString.substring(0, binaryString.length() - 4);
}
// convert to String
String charString = "";
String resultString = "";
int beginIndex = 0;
int endIndex = beginIndex + 8;
while ((charString = binaryString.substring(beginIndex, endIndex)).length() == 8) {
int ascii = Integer.parseInt(charString, 2);
resultString += (char) ascii;
beginIndex += 8;
endIndex += 8;
if (endIndex > binaryString.length()) {
break;
}
}
return resultString;
}
}
2.2 MD5加密算法(不可逆)
MD5碼加解密算法Message-Digest Algorithm 5(信息-摘要算法),是一種不可逆的算法,具有很高的安全性。它對應任何字符串都可以加密成一段唯一的固定長度的代碼。(小貼士:爲啥MD5加密算法不可逆呢~ 按道理來說有加密方式,就會有解密方式呀?因爲MD5加密是有種有損的加密方式,比如一段數據爲'123',我在加密的時候,遇到1和3都直接當做是a,加密後變成了'a2a',所以解密的時候就出現了4種組合'323''121''123''321',數據一多,自然找不到原始的數據了,當然這種方式加密的密文也不需要解密)
// MD5碼工具類代碼
public class MD5Util {
public static final String MD5 = "MD5";
public static final String HmacMD5 = "HmacMD5";
public static final String charset = null; // 編碼格式;默認null爲GBK
private static MD5Util instance;
private MD5Util() {
}
// 單例
public static MD5Util getInstance() {
if (instance == null) {
synchronized (MD5Util.class) {
if (instance == null) {
instance = new MD5Util();
}
}
}
return instance;
}
/**
* 使用 MD5 方法加密(無密碼)
*/
public String encode(String res) {
try {
MessageDigest md = MessageDigest.getInstance(MD5);
byte[] resBytes = charset == null ? res.getBytes() : res.getBytes(charset);
return BASE64Util.getInstance().encode(md.digest(resBytes).toString());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 使用 MD5 方法加密(可以設密碼)
*/
public String encode(String res, String key) {
try {
SecretKey sk = null;
if (key == null) {
KeyGenerator kg = KeyGenerator.getInstance(HmacMD5);
sk = kg.generateKey();
} else {
byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
sk = new SecretKeySpec(keyBytes, HmacMD5);
}
Mac mac = Mac.getInstance(HmacMD5);
mac.init(sk);
byte[] result = mac.doFinal(res.getBytes());
return BASE64Util.getInstance().encode(result.toString());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2.3 DES加密算法(可逆)
public class DESUtil {
public static final String DES = "DES";
public static final String charset = null; // 編碼格式;默認null爲GBK
public static final int keysizeDES = 0;
private static DESUtil instance;
private DESUtil() {
}
// 單例
public static DESUtil getInstance() {
if (instance == null) {
synchronized (MD5Util.class) {
if (instance == null) {
instance = new DESUtil();
}
}
}
return instance;
}
/**
* 使用 DES 進行加密
*/
public String encode(String res, String key) {
return keyGeneratorES(res, DES, key, keysizeDES, true);
}
/**
* 使用 DES 進行解密
*/
public String decode(String res, String key) {
return keyGeneratorES(res, DES, key, keysizeDES, false);
}
// 使用KeyGenerator雙向加密,DES/AES,注意這裏轉化爲字符串的時候是將2進制轉爲16進制格式的字符串,不是直接轉,因爲會出錯
private String keyGeneratorES(String res, String algorithm, String key, int keysize, boolean isEncode) {
try {
KeyGenerator kg = KeyGenerator.getInstance(algorithm);
if (keysize == 0) {
byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
kg.init(new SecureRandom(keyBytes));
} else if (key == null) {
kg.init(keysize);
} else {
byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
kg.init(keysize, new SecureRandom(keyBytes));
}
SecretKey sk = kg.generateKey();
SecretKeySpec sks = new SecretKeySpec(sk.getEncoded(), algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
if (isEncode) {
cipher.init(Cipher.ENCRYPT_MODE, sks);
byte[] resBytes = charset == null ? res.getBytes() : res.getBytes(charset);
return parseByte2HexStr(cipher.doFinal(resBytes));
} else {
cipher.init(Cipher.DECRYPT_MODE, sks);
return new String(cipher.doFinal(parseHexStr2Byte(res)));
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 將二進制轉換成16進制
private String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
// 將16進制轉換爲二進制
private byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length() / 2];
for (int i = 0; i < hexStr.length() / 2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
}
2.4 AES加密算法(可逆)
AES(Advanced Encryption Standard,AES),是密碼學中的高級加密標準。這個標準用來替代原先的DES(Data Encryption Standard),已經被多方分析且廣爲全世界所使用。經過五年的甄選流程,高級加密標準由美國國家標準與技術研究院 (NIST)於2001年11月26日發佈於FIPS PUB 197,並在2002年5月26日成爲有效的標準。現在,高級加密標準已然成爲對稱密鑰加密中最流行的算法之一。
AES也是,採用密鑰,意思是傳入一個需要加密的對象的同時,約定一個key(可以是字符串),然後解密端,通過這個key進行解密獲取對象。AES的實現原理,是先確定密鑰,確定方式:算法/模式/補碼。
public class AESUtil {
/**
* 加密
* @return 加密後的字符串
*/
public static String Encrypt(String src, String key) throws Exception {
// 判斷密鑰是否爲空
if (key == null) {
System.out.print("密鑰不能爲空");
return null;
}
// 密鑰補位
int plus= 16-key.length();
byte[] data = key.getBytes("utf-8");
byte[] raw = new byte[16];
byte[] plusbyte={ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
for(int i=0;i<16;i++)
{
if (data.length > i)
raw[i] = data[i];
else
raw[i] = plusbyte[0];
}
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 算法/模式/補碼方式
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(src.getBytes("utf-8"));
//return new Base64().encodeToString(encrypted);//base64
return binary(encrypted, 16); //十六進制
}
/**
* 解密
* @return 解密後的字符串
*/
public static String Decrypt(String src, String key) throws Exception {
try {
// 判斷Key是否正確
if (key == null) {
System.out.print("Key爲空null");
return null;
}
// 密鑰補位
int plus= 16-key.length();
byte[] data = key.getBytes("utf-8");
byte[] raw = new byte[16];
byte[] plusbyte={ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
for(int i=0;i<16;i++)
{
if (data.length > i)
raw[i] = data[i];
else
raw[i] = plusbyte[0];
}
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
//byte[] encrypted1 = new Base64().decode(src);//base64
byte[] encrypted1 = toByteArray(src);//十六進制
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original,"utf-8");
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
/**
* 將byte[]轉爲各種進制的字符串
* @param bytes byte[]
* @param radix 可以轉換進制的範圍,從Character.MIN_RADIX到Character.MAX_RADIX,超出範圍後變爲10進制
* @return 轉換後的字符串
*/
public static String binary(byte[] bytes, int radix){
return new BigInteger(1, bytes).toString(radix); // 這裏的1代表正數
}
/**
* 16進制的字符串表示轉成字節數組
*
* @param hexString 16進制格式的字符串
* @return 轉換後的字節數組
**/
public static byte[] toByteArray(String hexString) {
if (hexString.isEmpty())
throw new IllegalArgumentException("this hexString must not be empty");
hexString = hexString.toLowerCase();
final byte[] byteArray = new byte[hexString.length() / 2];
int k = 0;
for (int i = 0; i < byteArray.length; i++) {//因爲是16進制,最多隻會佔用4位,轉換成字節需要兩個16進制的字符,高位在先
byte high = (byte) (Character.digit(hexString.charAt(k), 16) & 0xff);
byte low = (byte) (Character.digit(hexString.charAt(k + 1), 16) & 0xff);
byteArray[i] = (byte) (high << 4 | low);
k += 2;
}
return byteArray;
}
}
3. 測試結果
4. 結論
最後探究了幾種方法後,我選用了AES加密,因爲我認爲這類委託密鑰的方式,顯然是比較優秀的,而且不像md5碼不可逆,可以解密,比較符合業務需求。
以下是AES得加密解密實例代碼
try {
msg = AESUtil.Encrypt("hangli","love"); // 加密
} catch (Exception e) {
e.printStackTrace();
}
/**
* 進行解密
*/
String msg2 = null;
try {
msg2 = AESUtil.Decrypt(msg,"love");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("加密:" + msg);
System.out.println("解密:" + msg2);