前言
- 本篇博文將介紹對稱密碼算法中的DES密碼的算法原理與代碼實現(JAVA)
DES算法原理
DES加密算法是 對稱加密 算法(加密和解密使用同一個密鑰)中的一種,DES也是 分組密碼,以64位為分組對明文進行加密。
DES算法會對明文進行16輪的迭代加密,具體的算法過程可以看下面這圖(來自文末參考博文中的圖,做了一些修改)。看一遍有點繞就那筆跟著走一遍。
下面這張圖是每次迭代的的一個提取,我們從中可以直接觀察到的就是 迭代的兩個規律 :
Li = Ri-1 Ri = Li-1 ^ F(Ri-1, Ki)
上一輪的輸出作為下一輪加密的輸入(也就是迭代的過程)。同樣, 子密鑰也是迭代產生 。
在總體概覽了一遍后,我們可以將DES算法分為3部分來講解。從第一張圖從右往左,輪子密鑰(子密鑰)的生成、F函數的實現以及16次迭代的過程。
子密鑰的產生
如圖上的流程圖所示,將所給的 初始64位密鑰(若是密鑰不足64位則前面加0補充至64位),經過PC-1置換壓縮成56位 。然后 分成左右28位 ,表示成C0, D0。C0和D0按照循環左移表來分別循環左移,此處是第一次循環,所以循環左移1次,生成C1和D1。然后C1和D1合并成56位密鑰 經過PC-2置換壓縮成48位 的K1。
K2的生成過程:C1和D1分別循環左移1次,然后合并經過PC-2置換壓縮成K2。Ki的生成就為Ci-1和Di-1分別循環左移,然后合并經過PC-2置換壓縮而成。
PC-1 置換表 PC-2置換表 循環左移表
//PC-1置換表 private int[] 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}; //PC-2置換表 private int[] 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}; //循環左移次數表 private int[] leftTable = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1};
F函數的原理
F函數的內部還是比較復雜,不過問題不大。我們按照F函數內部執行順序來可以分為以下幾步:
- Ri-1做一個E擴展,從 32位擴展成48位
- Ri-1與Ki異或運算,然后將異或運算的結果 經過S盒選擇壓縮成32位
- 從S盒出來的32位結果再經過P置換,就得到最終的32位Ri
Ri-1做擴散選擇的表如下:
//E擴展 private int[] ETable={ 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};
從S盒出來的32位結果經過的P表如下:
//P置換 private int[] 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};
在這三步中最為機密的就是S盒的選擇壓縮了。S盒是如何實現選擇壓縮呢?我們就要知道S的結構了。
S盒的結構
我們可以看出,進入S盒后將Ri-1與Ki異或的值分成8組,每組6位,分別進入S1-S8盒,然后從每個盒中出4位,合并成32位的結果。
若是還想探究6位變成4位是如何的變換的,就需要看下面這點介紹:
我們以進入S!盒為例,假設進入的6位二進制數為101001,我們一般將 第一位和最后一位(從左到右)作為行坐標,中間四位作為縱坐標 找值。11即3行,0100即4列(從0開始編號),最后選出的值就為4,四位二進制表示則為0100。
16次的迭代加密
最初我們需要將初始的明文做一個IP置換,然后分成左右各32位即L0 R0,帶入L0、R0去計算L1和R1。
16次迭代的規律為:
Li = Ri-1 Ri = Li-1 ^ F(Ri-1, Ki)
最后,L16與R16直接交換賦給L17和R17(L17=R16, R17=L16),然后L17與R17合并后通過IP逆置換生成最終的密文。
以上,便是加密過程,解密可以說是加密的逆向過程。解密為何反向就可以解密,還需要各位看官另覓資料~
DES算法Java實現(完整)
package symmetricipher; /** * @description: 代碼實現Des算法加解密 * @author sakura * @date 2019年3月25日 下午12:52:21 */ /* * 1.主要的一個迭代公式 Li=Ri Ri = Li-1 ⊕F(Li-1,Ki) * 2.整體可以分為 加解密運算 F函數的處理 子密鑰的產生 * 3.子秘鑰產生:64位經過PC-1密鑰置換成56位 分為Ci Di左右各28為位 然后根據循環左移表來左移 最后經過PC-2置換成48位的密鑰Ki * 4.F函數的處理:Li-1(32位)經過E盒擴展成48位; 48位的Li-1與 子秘鑰Ki進行異或 ; * 異或的結果經過S盒(8個盒子 6進4出)生成32位;32位再經過P盒轉換成最后32位F函數處理后的結果 * 5.加解密運算這邊:先將明文做一個IP置換,然后將64位分成左右32位L0,R0 然后開始迭代 ;到第16次,做IP逆置換生成最終的密文 * * 6.解密運算: * 加密反過來 * */ public class DES { //初始IP置換 private int[] 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}; //IP逆置換 private int[] IP1={ 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}; //E擴展 private int[] ETable={ 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}; //P置換 private int[] 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}; //S盒 private static final int[][][] SBox = { { { 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 } } }; //PC-1置換表 private int[] 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}; //PC-2置換表 private int[] 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}; //循環左移次數表 private int[] leftTable = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1}; //加密輪數16輪 private static final int LOOP = 16; private String[] keys = new String[LOOP]; private String[] pContent; private String[] cContent; private int originLength; //初始明文長度 //16個子密鑰 private int[][] subKey = new int[16][48]; //存儲16次的子密鑰 private String content; private int pOriginLegth; //明文初始長度? //構造函數 public DES(String key, String content) { this.content = content; pOriginLegth = content.getBytes().length; generateSubKey(key); } //主函數入口 public static void main(String[] args) { String plainText = "SakuraOne"; System.out.println("明文: n" + plainText); String key = "IAMKEY"; DES des = new DES(key,plainText); byte[] c = des.group(plainText.getBytes(), true);//加密 System.out.println("密文:n" + new String(c)); byte[] p = des.group(c, false); //解密 byte[] pd = new byte[plainText.getBytes().length]; System.arraycopy(p, 0, pd, 0, plainText.getBytes().length); System.out.println("解密后的明文:n" + new String(pd)); } /** *拆分分組 */ public byte[] group(byte[] plainText, boolean decryption) { //填充明文長度為64位的整數 originLength = plainText.length; int gNum; int rNum; gNum = originLength/8; rNum = 8-(originLength-gNum*8); byte[] pPadding; if(rNum<8) { pPadding = new byte[originLength+rNum]; System.arraycopy(plainText, 0, pPadding, 0, originLength); for(int i=0; i<rNum; i++) { pPadding[originLength+1]=(byte)rNum; } }else { pPadding = plainText; } gNum = pPadding.length/8; byte[] groupPT = new byte[8]; //64位分組單位 byte[] resultData = new byte[pPadding.length]; for(int i=0; i<gNum; i++) { System.arraycopy(pPadding, i*8, groupPT, 0, 8); System.arraycopy(encryptUnit(groupPT, subKey, decryption), 0, resultData, i*8, 8); } //如果是解密 這里感覺什么也沒有做呢?? if(decryption == false) { byte[] pResultData = new byte[pOriginLegth]; System.arraycopy(resultData, 0, pResultData, 0, pOriginLegth); return pResultData; } return resultData; } /** *加密一個64位分組 * */ public byte[] encryptUnit(byte[]unit, int keysArray[][], boolean decryption) { //得到明文的01字符串 StringBuilder sb = new StringBuilder(); for(int i=0; i<8; i++) { String tmpBit = Integer.toBinaryString(unit[i] & 0xff); while(tmpBit.length()%8!=0) { tmpBit="0"+tmpBit; } sb.Append(tmpBit); } //將明文01字符串轉換為數字01存放在數組中 int[] pBit = new int[64]; String pStr = sb.toString(); for(int i=0; i<64; i++) { int bit = Integer.valueOf(pStr.charAt(i)); if(bit == 48) { bit = 0; }else if(bit == 49){ bit = 1; }else { System.out.println("To bit error"); } pBit[i] = bit; } /*=========IP置換==========*/ int[] pIP = new int[64]; for(int i=0; i<64; i++) { pIP[i] = pBit[IP[i]-1]; } //加密 if(decryption) { //迭代16次 for(int i=0; i<16; i++) { loop(pIP, i, decryption, keysArray[i]); } }else { //解密 反向迭代 for(int i=15; i>-1; i--) { loop(pIP, i, decryption, keysArray[i]); } } /*===========IP逆置換=============*/ int[] c = new int[64]; for(int i=0; i<IP1.length; i++) { c[i] = pIP[IP1[i]-1]; } byte[] cByte = new byte[8]; for(int i=0; i<8; i++) { cByte[i] = (byte)((c[8*i]<<7)+(c[8*i+1]<<6)+(c[8*i+2]<<5)+(c[8*i+3]<<4)+(c[8*i+4]<<3)+(c[8*i+5]<<2)+(c[8*i+6]<<1)+(c[8*i+7])); } return cByte; //最終的密碼字節數組 } //依次迭代過程 public void loop(int[] median, int times, boolean decryption, int[]keyArray ) { int[] l0 = new int[32]; int[] r0 = new int[32]; int[] l1 = new int[32]; int[] r1 = new int[32]; int[] f = new int[32]; //調用F函數后生成的結果 System.arraycopy(median, 0, l0, 0, 32); System.arraycopy(median, 32, r0, 0, 32); l1 = r0; f = fFunction(r0, keyArray); //調用F函數 for(int i=0; i<32; i++) { r1[i] = l0[i]^f[i]; //ri = li-1 ^ f[i] if(((decryption==false) && (times==0)) || ((decryption==true) && (times==15))) { median[i] = r1[i]; median[i+32] = l1[i]; }else { median[i] = l1[i]; median[i+32] = r1[i]; } } } /** * F函數 */ public int[] fFunction(int[] rContent, int[] key) { int[] result = new int[32]; int[] rXORkey = new int[48]; //ri擴展 與 keyi異或 for(int i=0; i<ETable.length; i++) { rXORkey[i] = rContent[ETable[i]-1]^key[i]; } /*=============S-box替換 將48位變成32位==============*/ int[][] s= new int[8][6]; int[] sAfter = new int[32]; for(int i=0; i<8; i++) { System.arraycopy(rXORkey, i*6, s[i], 0, 6); int r = (s[i][0]<<1)+s[i][5]; //橫坐標 int c = (s[i][1]<<3) + (s[i][2]<<2) + (s[i][1]<<1) + s[i][4]; //縱坐標 String str = Integer.toBinaryString(SBox[i][r][c]); while(str.length() < 4) { str = "0"+str; } for(int j=0; j<4; j++) { int p=Integer.valueOf(str.charAt(j)); if(p==48) { p=0; }else if(p==49) { p=1; }else { System.out.println("To bit error!"); } sAfter[4*i+j] = p; } } /*===============P盒替換=====================*/ for(int i=0; i<P.length; i++) { result[i] = sAfter[P[i]-1]; } return result; } /** * description:生成子密鑰 * * @param key 密鑰 * */ public void generateSubKey(String key) { //當key的長度小于64位時要擴展至64位 while(key.length()<8) { key = key + key; } key = key.substring(0, 8); //將字符密鑰轉換成二進制形式 byte[] keys = key.getBytes(); int[] kBit = new int[64]; for(int i=0; i<8; i++) { //每個字節即每8位&0000 0000 String kStr = Integer.toBinaryString(keys[i] & 0xff); //補齊8位 if(kStr.length()<8) { for(int t=0; t<8-kStr.length(); t++) { kStr = "0" + kStr; } } //將01字符串轉換成二進制01 for(int j=0; j<8; j++) { int p = Integer.valueOf(kStr.charAt(j)); if(p == 48) { p=0; }else if(p == 49) { p=1; }else { System.out.println("To bit error!"); } kBit[i*8+j] = p; } } //得到kBit 初始化的64位密鑰 然后進行PC-1壓縮成56位 /*==============PC-1壓縮===============*/ int[] kNewBit = new int[56]; for(int i=0; i<PC1.length; i++) { kNewBit[i] = kBit[PC1[i]-1]; } /*================初始密鑰分組=============*/ int[] c0 = new int[28]; int[] d0 = new int[28]; System.arraycopy(kNewBit, 0, c0, 0, 28); System.arraycopy(kNewBit, 28, d0, 0, 28); //生成16個子密鑰 for(int i=0; i<16; i++) { int[] c1 = new int[28]; int[] d1 = new int[28]; /*============ci、di分別循環左移===========*/ if(leftTable[i] == 1) { System.arraycopy(c0, 1, c1, 0, 27); c1[27]=c0[0]; System.arraycopy(d0, 1, d1, 0, 27); d1[27]=d0[0]; }else if(leftTable[i] == 2) { System.arraycopy(c0, 2, c1, 0, 26); c1[26]=c0[0]; c1[27]=c0[1]; System.arraycopy(d0, 2, d1, 0, 26); d1[26]=d0[0]; d1[27]=d0[1]; }else { System.out.println("leftTable error!"); } /*================ci、di合并 PC-2壓縮置換=============*/ int[] tmp = new int[56]; System.arraycopy(c1, 0, tmp, 0, 28); System.arraycopy(d1, 0, tmp, 28, 28); for(int j=0; j<PC2.length; j++) { subKey[i][j] = tmp[PC2[j]-1]; } c0 = c1; d0 = d1; } } }
小結
這學期在上密碼學的課程,課堂上聽老師講了對稱加密算法中的DES算法,一直覺得挺繞。在上完實驗課后勉強對其算法流程有了一個清晰認識。后面想著用算法實現或許會更明了, 于是寫代碼實現。確實,自己實現一遍后對算法會更理解。粗糙地記錄了下DES加解密的實現,以供參考。解密算法的驗證需要大家另覓資料,本篇博文就不再介紹了~