一.什么是對稱加密
常見的加密方式分為三種:
1.正向加密:如MD5,加密后密文固定,目前還沒有辦法破解,但是能夠通過數據庫撞庫有一定概率找到,不過現在一般用這種方式加密都會加上鹽值。
2.對稱加密:通過一個固定的對稱密鑰,對需要傳輸的數據進行加密,速度快,但是安全性不高,主要用於企業級內部系統中數據傳輸。
3.非對稱加密:N把公鑰,一把私鑰,私鑰存放在服務器一方保管,公鑰可以放在任意一個客戶端,客戶端向服務器請求的密文只有拿到了秘鑰的服務器一端可以解密。
本文主要介紹對稱加密。對稱加密是一種使用單鑰密碼系統的加密方法,同一個密鑰可以同時用作信息的加密和解密。由於其速度快,對稱性加密通常在消息發送方需要加密大量數據時使用。對稱加密也稱為密鑰加密。所謂對稱,就是采用這種加密方法的雙方使用方式用同樣的密鑰進行加密和解密。密鑰是控制加密和解密過程的指令。算法是一組規則,規定如何進行加密和解密。因此加密的安全性不僅取決於加密算法本身,密鑰管理的安全性更是重要。因為加密和解密都使用同一個密鑰,如何把密鑰安全地傳遞到解密者手上就成了必須要解決的問題。
二.什么是隨機鹽值
wiki百科對鹽值的介紹如下:
鹽(Salt),在密碼學中,是指通過在密碼任意固定位置插入特定的字符串,讓散列后的結果和使用原始密碼的散列結果不相符,這種過程稱之為“加鹽”。
安全因素
通常情況下,當字段經過散列處理(如MD5),會生成一段散列值,而散列后的值一般是無法通過特定算法得到原始字段的。但是某些情況,比如一個大型的彩虹表,通過在表中搜索該MD5值,很有可能在極短的時間內找到該散列值對應的真實字段內容。
加鹽后的散列值,可以極大的降低由於用戶數據被盜而帶來的密碼泄漏風險,即使通過彩虹表尋找到了散列后的數值所對應的原始內容,但是由於經過了加鹽,插入的字符串擾亂了真正的密碼,使得獲得真實密碼的概率大大降低。
實現原理:
比如用戶使用“abcd”這個字符串作為密碼,經過MD5散列后得到了:E2FC714C4727EE9395F324CD2E7F331F
但是由於用戶密碼較簡單,短密碼的散列結果很容易通過撞庫破解。
如果我們定義一個只有自己知道的字符串,放在要加密的文字任何地方,比如在abcd的第二個字符前加上:helloworld123123,那么要加密的字符變成了:ahelloword123123worldbcd
我們再對這個字符串進行散列得到978E1014EEFF5E0A708314DB2E7D6DA1,這樣用撞庫的方式就很難破解。這里的鹽值的作用相當於擾亂了正常要加密的字符,而這個規律只有開發者,服務端的人知道。
三.使用異或的特性實現簡單的對稱加密
異或的運算規則是二進制運算中,相同得0,不同得1。異或還有一個特殊的性質就是逆運算,一個數異或相同得數兩次還會得到它本身。通過異或這種特殊的運算性質,用它來做對稱加密是比較好的選擇。
下面給一個Java的簡單實現
以abc作為密鑰對1234進行加密。先將1,2,3,4分別轉成ASCII碼。查詢ASCII碼表,1,2,3,4分別對應着49,50,51,52。
加密:
49 -> 加密過程為:49 ^ 密鑰 ^ 鹽值 -> m1
50 -> 加密過程為:50 ^ 密鑰 ^ m1 -> m2
51 -> 加密過程為:51 ^ 密鑰 ^ m2 -> m3
52 -> 加密過程為:52 ^ 密鑰 ^ m3 -> m4
經過上述運算后,密文就變成了m1、m2、m3、m4。
解密:
解密是上面加密的逆運算:已知條件為m1、m2、m3、m4
m4 -> 解密過程為:m4 ^ m3 ^ 密鑰 -> 52
m3 -> 解密過程為:m3^ m2 ^ 密鑰 -> 51
m2 -> 解密過程為:m2 ^ m1 ^ 密鑰 -> 50
m1 -> 解密過程為:m1 ^ 鹽值 ^ 密鑰 -> 49
下面是實現代碼:
package com.zuikc.test; public class Test6 { public static void main(String[] args) { String content = "1234"; // 需要加密的字符 String key = "abc"; // 密鑰 byte[] result = encryption(content, key); System.out.println("1234加密后的值:" + new String(result)); System.out.println("---------------"); System.out.println("1234解密后的值:" + new String(decipher(new String(result), key))); } public static byte[] encryption(String content, String key) { byte[] contentBytes = content.getBytes(); byte[] keyBytes = key.getBytes(); byte dkey = 0; for (byte b : keyBytes) { dkey ^= b; } byte salt = 0; // 隨機鹽值 byte[] result = new byte[contentBytes.length]; for (int i = 0; i < contentBytes.length; i++) { salt = (byte) (contentBytes[i] ^ dkey ^ salt); result[i] = salt; } return result; } public static byte[] decipher(String content, String key) { byte[] contentBytes = content.getBytes(); byte[] keyBytes = key.getBytes(); byte dkey = 0; for (byte b : keyBytes) { dkey ^= b; } byte salt = 0; // 隨機鹽值 byte[] result = new byte[contentBytes.length]; for (int i = contentBytes.length - 1; i >= 0; i--) { if (i == 0) { salt = 0; } else { salt = contentBytes[i - 1]; } result[i] = (byte) (contentBytes[i] ^ dkey ^ salt); } return result; } }
運行后控制台輸出如下

但是這樣通過查看源碼可以獲取鹽值和密鑰可以輕易破解,如果將秘鑰和鹽值不以明碼寫在代碼中可以提高安全性,改善后的代碼如下:
package com.zuikc.test; import java.util.Random; public class Test6 { private static byte salt = (byte) new Random().nextInt(); //生成隨機鹽值 private static String key = getRandomString(new Random().nextInt()); //生成隨機密鑰 public static void main(String[] args) { String content = "1234"; // 需要加密的字符 byte[] result = encryption(content, key,salt); System.out.println("1234加密后的值:" + new String(result)); System.out.println("---------------"); System.out.println("1234解密后的值:" + new String(decipher(new String(result), key,salt))); } //加密 public static byte[] encryption(String content, String key,byte salt) { byte[] contentBytes = content.getBytes(); byte[] keyBytes = key.getBytes(); byte dkey = 0; for (byte b : keyBytes) { dkey ^= b; } byte[] result = new byte[contentBytes.length]; for (int i = 0; i < contentBytes.length; i++) { salt = (byte) (contentBytes[i] ^ dkey ^ salt); result[i] = salt; } return result; } //解密 public static byte[] decipher(String content, String key,byte salt) { byte[] contentBytes = content.getBytes(); byte[] keyBytes = key.getBytes(); byte dkey = 0; for (byte b : keyBytes) { dkey ^= b; } byte[] result = new byte[contentBytes.length]; for (int i = contentBytes.length - 1; i >= 0; i--) { if (i == 0) { salt = 0; } else { salt = contentBytes[i - 1]; } result[i] = (byte) (contentBytes[i] ^ dkey ^ salt); } return result; } //生成隨機字符串的方法 public static String getRandomString(int length) { String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(62); sb.append(str.charAt(number)); } return sb.toString(); } }
