概念
加密領域主要有國際演算法和國密演算法兩種體系。國密演算法是國家密碼局認定的國產密碼演算法。國際演算法是由美國安全域性釋出的演算法。由於國密演算法安全性高等一系列原因。國內的銀行和支付機構都推薦使用國密演算法。
從上圖可知,對稱加密演算法在演算法體系裡佔了半壁江山。因為國際和國密演算法的過程差異並不大。只是應用的數學公式和祕鑰位數不同。DES在裡面算是基礎,所以今天主要介紹一下DES的原理。
原理
密碼
我們們從加密的原理說起。舉個最簡單的加密:
我有一段明文:520
我的祕鑰是:221
我的加密演算法是:加法
加密後的密文就是:741
如果這個密文被截獲了,接受者看到的資訊是:741(氣死你),和原來明文520是完全不同的,不能知道資訊原有的意思。
從這個簡單的例子可知:加密是包含:明文、祕鑰、加密演算法和密文四個要素的。加密演算法可公開可不公開,常見的演算法有:位移、迴圈位移、異或、置換、數學函式。
凱撒密碼
加密技術從古羅馬凱撒時候就在用:凱撒密碼
凱撒密碼是古羅馬時期凱撒大帝和他的將軍們通訊時使用的加密方式:
明文:由26個字母組成
祕鑰:1到25之間的任意數字
加密演算法:迴圈位移
密文:舉例明文為eat 祕鑰為2,對照上面圖片的凱撒密碼盤可以得到密文是gcv。
我們們來動手寫個凱撒密碼(程式碼已經上傳github:
(https://github.com/xiexiaojing/yuna):
@Test
public void caesarCipher() {
String text = "love was growing in eyes"; //明文
int key = 3; //祕鑰
String cipher = encryptCaesarCipher(text, key); //密文
System.out.println(cipher);
}
//凱撒密碼加密演算法
private String encryptCaesarCipher(String text, int key) {
char[] chars = text.toCharArray();
for(int i=0; i<chars.length; i++) {
if(chars[i] != ' ') {
chars[i] = (char)(chars[i]+key);
}
//如果超過了26個字母,則減去26
if(chars[i]>122) {
chars[i] = (char)(chars[i]-26);
}
}
return new String(chars);
}
執行得到密文:oryh zdv jurzlqj lq hbhv
凱撒密碼連小朋友都能破解。一旦被人知道用的凱撒密碼,演算法是已知的。要破解祕鑰拿個明文和密文試試就知道了。就是平時說的暴力破解法可以很容易破解。對於這種全是英文字元的也可以使用頻率分析法。頻率分析法可以理解為基於大資料的方法,因為26個字母中,e的使用頻率高。如果比如一篇文章,單詞足夠多的話,出現頻率最高的字母xxx就是e。xxx的char值e的char值就是祕鑰了。
DES
概述
DES全稱為Data Encryption Standard,即資料加密標準,是一種分組加密演算法。其分組長度為64bit,金鑰長度為64bit,其中8bit為校驗位,所以實際長度為56bit。
先介紹一下校驗位。舉個例子,我們們的身份證號碼都是18位。這18位包含:
其中最後一位就是校驗位,原理是利用將前面部分利用某種演算法計算得到一個數。如果校驗位與演算法得到的不一致,則資料是有問題的。所以身份證本身是有不通過查庫就可以簡單驗證有效性功能的。
回到DES演算法。DES演算法的祕鑰必須是64位,參與加密計算的是56位。這是原始祕鑰。這個原始祕鑰會用一個函式轉換成16個64位祕鑰。
加密過程
DES的加密過程:
明文64位->初始IP置換->16輪加密變換->逆初始IP置換->密文
DES演算法的這個過程又被稱為Feistel網路。
簡單解釋下:
明文我們自己想寫多長寫多長。但是加
密的時候每次以64bit作為一個分組。最後將密文拼接起來。
然後執行一個IP置換(初始置換)操作。IP置換就是按位置換。舉例來說64bit就是64個0和1。把第40個位置上的數換成第50個位置上的數就是置換了。
置換好的64個bit會分成兩個32bit。然後用相同的加密演算法每次傳不同的轉換後祕鑰做16輪。
然後將兩組32bit拼接起來再進行一次IP置換(終結置換)變成密文
分組組合
剛才介紹的加密過程是把明文的一塊怎麼加密成密文。DES密碼塊與密碼塊連線方式遵循對稱加密的方式。
對稱加密有兩種方式,一種是分組加密,一種是序列加密。
分組加密,也叫塊加密(block cyphers),一次加密明文中的一個塊。是將明文按一定的位長分組,明文組經過加密運算得到密文組,密文組經過解密運算(加密運算的逆運算),還原成明文組。
序列加密,也叫流加密(stream cyphers),一次加密明文中的一個位。是指利用少量的金鑰通過某種複雜的運算(密碼演算法)產生大量的偽隨機位流,用於對明文位流的加密。
分組加密演算法中,分組密碼的設計基本遵循混淆原則和擴散原則。有ECB,CBC,CFB,OFB這4種演算法模式。DES有ECB和CBC兩種實現。
ECB模式
ECB模式就是每組明文分別加密後拼接起來。
CBC模式
CBC即密碼分組連結(Cipher-block chaining)的簡稱。在CBC模式中,每個明文塊先與前一個密文塊進行異或後,再進行加密。在這種方法中,每個密文塊都依賴於它前面的所有明文塊。同時,為了保證每條訊息的唯一性,在第一個塊中需要使用初始化向量。在DES中,初始化向量就是祕鑰。
程式實現
我們用程式來實現一下DES加密演算法
@Test
public void desCiper() throws Exception{
String key = "12345678";
String text = "我知道我是任性很任性,傷透了你的心"; //明文
String cipher = encryptDesCipher(text, key); //密文
System.out.println(cipher);
text = decryptDesCipher(cipher, key); //明文
System.out.println(text);
}
//DES加密演算法
private String encryptDesCipher(String text, String origKey) throws Exception{
Key key = new SecretKeySpec(origKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return Base64.encode(cipher.doFinal(text.getBytes()));
}
//DES加密演算法
private String decryptDesCipher(String text, String origKey) throws Exception{
Key key = new SecretKeySpec(origKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64.decode(text)));
}
執行結果:
xo6mmVe8j1/d60cAAiFz1HAXxihi2FH5d0zMWILvEYISWR52lguy2TbMZQ4vuulCdO8WvxMRuXE=
我知道我是任性很任性,傷透了你的心
3DES
3DES即3重加密演算法,是對每個資料塊應用三次DES演算法。這是為了應對計算機計算能力增強,DES變的容易破解而產生的。
主要方法就是DES祕鑰64位,3DES祕鑰64*3=192位,分成個三個祕鑰,進行2輪DES加密,1輪DES解密。最終得到結果。
AES
AES全稱是高階加密標準(Advanced Encryption Standard)。是用來替代DES/3DES的。主要過程如下,這裡不過多介紹。
應用
我們來回顧下https的SSL握手過程:
SSL握手的最後,雙方會用非對稱祕鑰協商出一個對稱祕鑰。用對稱祕鑰來加密傳輸的資料。之所以這樣做是因為非對稱加密安全性高但是效率低,對稱祕鑰正好相反。對稱祕鑰可以被暴力破解,破解需要時間。如果破解出來時就過期了,再通訊就用另外的祕鑰就能保證資訊保安。
我們來模擬一下已經協商好的祕鑰之後http客戶端與伺服器端的通訊。(程式碼已經上傳github:(https://github.com/xiexiaojing/yuna):
@Test
public void client() throws Exception {
int i = 1;
while (i <= 2) {
Socket socket = new Socket("127.0.0.1", 520);
//向伺服器端第一次傳送字串
OutputStream netOut = socket.getOutputStream();
InputStream io = socket.getInputStream();
String msg = i == 1 ? "客戶端:我知道我是任性太任性,傷透了你的心。我是追夢的人,追一生的緣分。":
"客戶端:我願意嫁給你,你卻不能答應我。";
System.out.println(msg);
netOut.write(encryptDesCipher(msg.getBytes(), "12345678"));
netOut.flush();
byte[] bytes = new byte[i==1?104:64];
io.read(bytes);
String response = new String(decryptDesCipher(bytes,"12345678"));
System.out.println(response);
netOut.close();
io.close();
socket.close();
i++;
}
}
@Test
public void server() throws Exception {
ServerSocket serverSocket = new ServerSocket(520);
int i = 1;
while (i <= 2) {
String msg = i == 1 ? "服務端:我知道你是任性太任性,傷透了我的心。同是追夢的人,難捨難分。" :
"服務端:你願意嫁給我,我卻不能向你承諾。";
Socket socket = serverSocket.accept();
InputStream io = socket.getInputStream();
byte[] bytes = new byte[i==1?112:64];
io.read(bytes);
System.out.println(new String(decryptDesCipher(bytes,"12345678")));
OutputStream os = socket.getOutputStream();
System.out.println(msg);
byte[] outBytes = encryptDesCipher(msg.getBytes(), "12345678");
os.write(outBytes);
os.flush();
os.close();
io.close();
i++;
}
}
//DES加密演算法
private byte[] encryptDesCipher(byte[] text, String origKey) throws Exception {
Key key = new SecretKeySpec(origKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(text);
}
//DES加密演算法
private byte[] decryptDesCipher(byte[] text, String origKey) throws Exception {
Key key = new SecretKeySpec(origKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(text);
}
執行結果
客戶端:我知道我是任性太任性,傷透了你的心。我是追夢的人,追一生的緣分。
服務端:我知道你是任性太任性,傷透了我的心。同是追夢的人,難捨難分。
客戶端:我願意嫁給你,你卻不能答應我。
服務端:你願意嫁給我,我卻不能向你承諾。
這個程式實現了簡單的客戶端和伺服器端的DES加密方式通訊。稍加改造可以實現一個資訊加密的聊天小程式。
總結
本文使用概念、原理、應用的傳統型邏輯架構來對DES做系統梳理。裡面涉及到的一些基本知識限於篇幅省略了一些。
比如Base64編碼。它是加密時常用的編碼方式,我們平時所看到的金鑰都是base64後的結果。可以簡單理解為對2的6次方進行64進位制運算,可防止亂碼丟失位元組。
再比如填充位元組這部分也沒有介紹,有興趣可以自己查閱下。
加解密很多人工作中或多或少都有涉及。我個人認為只要涉及的地方至少要了解到原理和架構層面,才能避免遇到問題時【拿著錘子找釘子】找不到真正問題的窘境。而只有動手實踐才能避免當時瞭解了,過後又忘了需要再看一遍而產生重複工作的問題。