# 【IS】DES #2 ## DES的歷史 * 這部分其實不太重要,但因為蠻有趣的就寫下來了XD * 只想看實作的人可以直接下拉。 ### 開發 - 在電腦資訊準備發展起來的時間點,還沒有所謂**國家認證的標準加密法**,因此大家都各做各的加密法。 - 當時超級龍頭公司**IBM**也不例外地在研發自己的加密法,而他們不只有自己研究,還和NSA(美國國家安全局)一同研發,最後研發出了DES(後來有些微調整,但也可以說是DES的前身了)。 - 後來大家意識到資安的重要性之後,NBS(國家標準局)辦了一場投稿大賽,讓大家投稿自己的加密演算法,最好的那個就會成為標準。 - 大家都踴躍的投稿,IBM也不例外。然後NBS就發現一個問題......他們不知道哪個比較好,因此只好求助同樣為國家單位的NSA來協助評測。 - 而NSA最後當然選擇了自己有協助開發的DES為標準。然而這導致後續的爭議不斷。 ### 爭議 - 爭議是質疑DES不夠好嗎?錯!是**太好了**! - DES不愧是龍頭公司IBM和國家單位共同研究出來的,它強到甚至對2000年常見的攻擊手段有防禦能力。也就是說DES領先了世界上加密相關的學者們大約30年。 - 而且DES不但強,實作也十分簡單。大多只用到一些table位移和XOR等運算。 - DES實作中使用到的幾個talbe(尤其是S-Box)基本上沒有明顯邏輯,卻有如此好的效果,因此被大多人懷疑藏有後門,只要使用某一組萬能表就必定能解密。 ### 後續 - 即便大家一直懷疑DES有問題,卻也找不到所謂的後門,DES就這樣繼續用了幾年後,還是被暴力破解法給破解了。 - 由於DES設計上只支援`56`bits的金鑰,在現今的硬體強度之下簡直不堪一擊。又加上後門的風險,後來標準加密法就被AES取代。 ## DES實作 ### 輸入輸出 * DES加密法的明文、Key、密文長度皆為`64`bits。 * 我們假設輸入格式是十六進制,直接使用argc的方式讀入。 ```C++ /* Input: 0x1259ACBD6544FCDA 0xabcdef0123456789 */ /* Transform to unsigned long long int by stringstream. */ /* Note that you should use unsigned long long int or it will overflow. */ unsigned long long int key; unsigned long long int plaintext; string str = argc[1]; stringstream ss(str); ss >> std::hex >> key; str = argc[2]; ss = stringstream(argc[2]); ss >> std::hex >> plaintext; ``` * 為了方便後續處理,我們將數字轉成Vector的型態。 ```C++ vector<int> ChangeToVector(unsigned long long int data) { vector<int> newData; while (data != 0) { newData.push_back(data % 2); data /= 2; } return newData; } ``` ```C++ // Transform to vector vector<int> v_key, v_plaintext; v_key = ChangeToVector(key); while ((int)v_key.size() != 64) v_key.push_back(0); v_plaintext = ChangeToVector(plaintext); while ((int)v_plaintext.size() != 64)v_plaintext.push_back(0); reverse(v_plaintext.begin(), v_plaintext.end()); reverse(v_key.begin(), v_key.end()); ``` * 記得要將vector補滿`64`bits。 * reverse是為了讓最左邊的數字擺在`vector[0]`,方便理解。 ### 第一次換位 * 單純地將明文做一次換位的動作,這個換位是**固定的**。 * 根據下面的表作換位,代表將第`58`個bit換到第`1`個去,以此類推。 ``` 58 50 42 34 26 18 10 2 60 52 44 36 28 20 12 4 62 54 46 38 30 22 14 6 64 56 48 40 32 24 16 8 57 49 41 33 25 17 9 1 59 51 43 35 27 19 11 3 61 53 45 37 29 21 13 5 63 55 47 39 31 23 15 7 ``` * 我們可以很簡單的做一個轉換用的function。 ```C++ vector<int> ChangeWithTable(vector<int> data, vector<int> table) { int s = table.size(); vector<int> newdata; for (int i = 0; i < s; i++) { if ((int)data.size() < table[i] - 1) throw string("table error!"); newdata.push_back(data[table[i] - 1]); } return newdata; } ``` * 接下來只要初始化好table的內容,就可以很快速地轉換。 ```C++ const vector<int> table_IP{ 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9 , 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 }; // Initial permutation v_plaintext = ChangeWithTable(v_plaintext, table_IP); ``` ### 明文、鑰匙前處理 * 接著我們將明文切成左右兩個部分。 ```C++ // Break plaintext to 2 parts //////////////////////////////////////////// // // l[0] l[1] ... l[31] r[0] r[1] ... r[31] // //////////////////////////////////////////// vector<int> l_plaintext, r_plaintext; for (int i = 0; i < (int)v_plaintext.size(); i++) { if (i < (int)v_plaintext.size() / 2) l_plaintext.push_back(v_plaintext[i]); else r_plaintext.push_back(v_plaintext[i]); } ``` * 然後將key壓縮成`56`bits。 * 這代表key裡面有`8`個bit根本沒用到。 ```C++ // 64 bits of key goto PC1 to 56 bits const vector<int> table_PC1{ 57, 49, 41, 33, 25, 17, 9 , 1 , 58, 50, 42, 34, 26, 18, 10, 2 , 59, 51, 43, 35, 27, 19, 11, 3 , 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7 , 62, 54, 46, 38, 30, 22, 14, 6 , 61, 53, 45, 37, 29, 21, 13, 5 , 28, 20, 12, 4 }; v_key = ChangeWithTable(v_key, table_PC1); ``` * 然後我們將key也拆成兩個部分。 ```C++ // Break key to 2 parts vector<int> l_subkey, r_subkey; for (int i = 0; i < (int)v_key.size(); i++) { if (i < (int)v_key.size() / 2) l_subkey.push_back(v_key[i]); else r_subkey.push_back(v_key[i]); } ``` ### 16 Rounds * 接下來我們會進行`16`次**round**。 * 每一個round包含以下動作: * 位移兩把subkey * 將subkey合併且丟進table重組 * 利用key和右邊的明文做**FeistelCipher** * 將結果與左邊的明文做XOR後,放到明文右邊 * 將原本的右邊明文放置左邊 ```C++ // 16 rounds // L' = R // R' = L XOR F(R,k) for (int i = 0; i < 16; i++) { // get subkey KeyRotate(l_subkey, i); KeyRotate(r_subkey, i); vector<int> subkey = l_subkey; subkey.insert(subkey.end(), r_subkey.begin(), r_subkey.end()); subkey = ChangeWithTable(subkey, table_PC2); // Function F vector<int> temp; temp = FeistelCipher(r_plaintext, subkey); for (int j = 0; j < 32; j++) { temp[j] = temp[j] ^ l_plaintext[j]; } l_plaintext = r_plaintext; r_plaintext = temp; } ``` ### Subkey * 其中位移鑰匙的部分,左右兩邊的方式是一樣的。 * 位移是往左位移,其中第`1` `2` `9` `16` 次只位移一個bit,否則位移兩次。 ```C++ void KeyRotate(vector<int>& key, int round) { key.push_back(key.front()); key.erase(key.begin()); if (round != 0 && round != 1 && round != 8 && round != 15) { key.push_back(key.front()); key.erase(key.begin()); } } ``` * 然後是位移完的鑰匙使用的table。 ```C++ const vector<int> table_PC2{ 14, 17, 11, 24, 1 , 5 , 3 , 28, 15, 6 , 21, 10, 23, 19, 12, 4 , 26, 8 , 16, 7 , 27, 20, 13, 2 , 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 }; ``` ### FeistelCipher(Function F) * 再來是FeistelCipher,俗稱Function F。 * 內容稍微有點多,但也是DES最核心的部分。 * 先將傳進來的右邊明文做一個增廣,一樣直接用前面的table function即可。 ```C++ const vector<int> table_E{ 32, 1 , 2 , 3 , 4 , 5 , 4 , 5 , 6 , 7 , 8 , 9 , 8 , 9 , 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 }; ``` * 然後讓`48`bits的右邊明文對前面處理完的subkey做XOR。 * 將結果切成8等分,每一份有`6`bits;拿去做**S_BOX**的運算。 * 將八個BOX的結果合併起來,得到`32`bits的密文,再做一次table位移即完成。 ```C++ const vector<int> table_P{ 16, 7 , 20, 21, 29, 12, 28, 17, 1 , 15, 23, 26, 5 , 18, 31, 10, 2 , 8 , 24, 14, 32, 27, 3 , 9 , 19, 13, 30, 6 , 22, 11, 4 , 25 }; ``` ```C++ vector<int> FeistelCipher(vector<int> R, vector<int> subkey) { // Expansion R to 48 bits R = ChangeWithTable(R, table_E); if (R.size() != 48 || subkey.size() != 48) { throw string("Function F error!"); } for (int i = 0; i < 48; i++) { R[i] = R[i] ^ subkey[i]; } // S-Box substitution vector<int> s_box[8]; for (int i = 0; i < 8; i++) { vector<int> sub_R(R.begin() + i * 6, R.begin() + (i + 1) * 6); s_box[i] = S_Box(sub_R, i); } R.clear(); for (int i = 0; i < 8; i++) { R.insert(R.end(), s_box[i].begin(), s_box[i].end()); } if ((int)R.size() != 32) throw string("S-Box output error!"); R = ChangeWithTable(R, table_P); if ((int)R.size() != 32) throw string("Function F output error!"); return R; } ``` ### S_Box * S_Box其實就是一個查表的動作。 * 輸入為`6`bits,我們將這`6`bits拆成頭尾兩個和中間四個,分別對應到直行和橫列的索引值。 * 查到的數字轉換為二進制即為輸出的`4`bits。 ```C++ const int table_s_box[8][4][16] = { { {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7}, { 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8}, { 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0}, {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}, }, { {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10}, { 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5}, { 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15}, {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}, }, { {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8}, {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1}, {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7}, { 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}, }, { { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15}, {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9}, {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4}, { 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}, }, { { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9}, {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6}, { 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14}, {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}, }, { {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11}, {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8}, { 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6}, { 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}, }, { { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1}, {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6}, { 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2}, { 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}, }, { {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7}, { 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2}, { 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8}, { 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}, } }; ``` * 須隨時注意bit數是否有誤,然後記得八個S_BOX都不一樣。 ```C++ vector<int> S_Box(vector<int> input, int boxID) { if ((int)input.size() != 6) throw string("S-Box error!"); int row = input.front() * 2 + input.back(); int col = input[4] + input[3] * 2 + input[2] * 4 + input[1] * 8; int temp = table_s_box[boxID][row][col]; vector<int> newdata; for (int i = 0; i < 4; i++) { newdata.push_back(temp % 2); temp /= 2; } reverse(newdata.begin(), newdata.end()); if ((int)newdata.size() != 4) throw string("S-box error!"); return newdata; } ``` ### 最後處理 * 完成十六round後其實已經差不多結束了。 * 最後將<u>左邊接到右邊後面</u>,因為最後一次其實不需要交換。 ```C++ // Combine two part of plaintext, change back left and right v_plaintext = r_plaintext; v_plaintext.insert(v_plaintext.end(), l_plaintext.begin(), l_plaintext.end()); ``` * 然後再做一個table位移就完成啦! ```C++ const vector<int> table_IPP{ 40, 8 , 48, 16, 56, 24, 64, 32, 39, 7 , 47, 15, 55, 23, 63, 31, 38, 6 , 46, 14, 54, 22, 62, 30, 37, 5 , 45, 13, 53, 21, 61, 29, 36, 4 , 44, 12, 52, 20, 60, 28, 35, 3 , 43, 11, 51, 19, 59, 27, 34, 2 , 42, 10, 50, 18, 58, 26, 33, 1 , 41, 9 , 49, 17, 57, 25 }; // Final permutation v_plaintext = ChangeWithTable(v_plaintext, table_IPP); ``` * 如果你想將vector轉換回去,用for loop跑即可。 ```C++ // Transform to hex format plaintext = 0; for (int i = 0; i < 64; i++) { plaintext <<= 1; plaintext += v_plaintext[i]; } cout << "0x" << std::uppercase << std::hex << plaintext; ``` ###### tags: `IS` `note`