DES加密算法原理及使用Java不導(dǎo)包的實現(xiàn)

一. DES概述

數(shù)據(jù)加密標(biāo)準(zhǔn)(Data Encryption Standard),一種使用密鑰加密的塊算法,1977年被美國聯(lián)邦政府的國家標(biāo)準(zhǔn)局確定為聯(lián)邦資料處理標(biāo)準(zhǔn)(FIPS),并授權(quán)在非密級政府通信中使用,隨后該算法在國際上廣泛流傳開來。需要注意的是,在某些文獻中,作為算法的DES稱為數(shù)據(jù)加密算法(Data Encryption Algorithm,DEA),已與作為標(biāo)準(zhǔn)的DES區(qū)分開來。

1. 幾個重要的歷史時間

  • 1973年美國國家標(biāo)準(zhǔn)局(NBS)向社會公開征集加 密算法,以制定加密算法標(biāo)準(zhǔn);

  • 1974年第二次征集;

  • 1975年選中IBM的算法,并公布征求意見;

  • 1977年1月15日正式頒布;

  • 1998年底以后停用;

  • 1999年頒布3DES為新標(biāo)準(zhǔn)。

2. 標(biāo)準(zhǔn)加密算法的目標(biāo)

  • 用于加密保護政府機構(gòu)和商業(yè)部門的非機密的敏感 數(shù)據(jù)。

  • 用于加密保護靜態(tài)存儲和傳輸信道中的數(shù)據(jù)。

  • 安全使用10 ~ 15年。

3.密碼的整體特點

  • 分組密碼,明文、密文和密鑰的分組長度都是64位。

  • 面向二進制的密碼算法,因而能夠加解密任何形式的計算機數(shù)據(jù)。

  • 對合運算:

    • f = f^-1
  • 加密和解密共用同一算法,使工程實現(xiàn)的工作量減半。

  • 綜合運用了置換、代替、代數(shù)等基本密碼技術(shù)。

  • 基本結(jié)構(gòu)屬于Feistel結(jié)構(gòu)。

4. 應(yīng)用

  • 在全世界范圍得到廣泛應(yīng)用。

  • 許多國際組織采用為標(biāo)準(zhǔn)。

  • 產(chǎn)品形式:軟件(嵌入式,應(yīng)用軟件) 硬件(芯片,插卡)

5. 結(jié)論

  • 用于其設(shè)計目標(biāo)是安全的。

  • 設(shè)計精巧、實現(xiàn)容易、使用方便,堪稱典范。

  • 為國際信息安全發(fā)揮了重要作用。

二. 加密過程

  • 64位密鑰經(jīng)子密鑰產(chǎn)生算法產(chǎn)生出16個子密鑰: K1, K2, ..., K16 , 分別供第一次, 第二次, ..., 第十六次加密迭代使用。

  • 64位明文經(jīng)初始置換IP, 將數(shù)據(jù)打亂重排并分成左右兩半。左邊32位構(gòu)成L0 , 右邊2位構(gòu)成R0 。

  • 第一次加密迭代:由加密函數(shù)f實現(xiàn)子密鑰k1對R0的加密,得到32位的f(R0, K1),然后L0⊕f(R0, K1),32位的結(jié)果作為第二次加密迭代的R1,以R0作為第二次加密迭代的L1。

  • 第二次加密迭代至第十六次加密迭代分別用子密鑰K2 ,..., K16進行,其過程與第一次加密迭代相同。

  • 第十六次加密迭代結(jié)束后,產(chǎn)生一個64位的數(shù)據(jù)組,以其左邊32位作為R16, 以其右邊32位作為L16 。

  • R16與L16合并,再經(jīng)過逆初始置換IP ^–1, 將數(shù)據(jù)重新排列,便得到64位密文。

image

1. 子密鑰的產(chǎn)生

64位密鑰經(jīng)過置換選擇1、循環(huán)左移、置換選擇2等變換,產(chǎn)生16個子密鑰 K1,K2。… K16,分別供各次加密迭代使用。

image

( 1 ). 置換選擇1 (Permuted Choice 1)

64位的密鑰分為8字節(jié),每個字節(jié)的第八位是奇偶校驗位,前七位才是真正的密鑰位。奇偶校驗位用于密鑰檢錯,確保其完整性;它不是隨機的,可由前七位密鑰位算得。因此,DES真正的密鑰只有56位。

置換選擇1的作用:

  • 去掉密鑰中的8個奇偶校驗位。

  • 把其余的56位打亂重排,將前28位作為C0, 后28位作為D0 。

置換規(guī)則:C0的各位依次為原密鑰的第57, 49, ..., 1, ..., 44, 36位;D0的各位依次為原密鑰的第63, 55, ..., 7, ..., 12, 4位。

image

( 2 ). 循環(huán)左移 (Left Shift)

對Ci , Di分別循環(huán)左移n位,其中n是會隨著迭代次數(shù)變化的,其與迭代次數(shù)映射表如下所示

image

( 3 ). 置換選擇2 (Permuted Choice 2)

64位的密鑰分成32位的兩份,進過置換選擇后各成為28位的數(shù)據(jù)(即C0和D0),就是說Ci和Di都是28位的數(shù)據(jù)。將Ci和Di合并成一個56位的數(shù)據(jù),置換選擇2從中選出一個48位的子密鑰Ki。規(guī)定子密鑰Ki的56位依次是該56位中間數(shù)據(jù)的第14, 17, ..., 5, 3, ..., 29, 32位,其置換表如下所示

image

( 4 ). 代碼實現(xiàn)

為了方便理清生成子密鑰的邏輯,其中的打印輸出的代碼并沒有給出,完整源碼見該項目Github倉庫

/**
* 生成16個48位的子密鑰
* @param keyBytes 64位原密鑰,用字符數(shù)組存儲其比特串
* @return 子密鑰數(shù)組,用二維字符數(shù)組表示
*/
private char[][] generateSubKeys(char[] keyBytes) {
    char[][] subKeys = newchar[16][48];
    // 置換選擇 1
    char[] c = ArrayUtil.disruptArray(keyBytes,DESConstants.replace1C);
    char[] d = ArrayUtil.disruptArray(keyBytes,DESConstants.replace1D);

    // 循環(huán)左移
    for(int i = 0;i < 16; i++) {
      c = ArrayUtil.leftShift(c,DESConstants.moveBit[i]);
      d = ArrayUtil.leftShift(d,DESConstants.moveBit[i]);
      // 將Ci和Di合并得到56位的中間數(shù)據(jù)
      char[] concatChars = ArrayUtil.concat(c,d);
      // 置換選擇 2,得到48位的子密鑰Ki
      char[] key = ArrayUtil.disruptArray(concatChars,
                    DESConstants.replace2);
      subKeys[i] = key;
   }
    return subKeys;
}

2. 初始置換IP

初始置換是DES算法的第一步密碼變換,它的作用是將64位的明文打亂重排,并分成左右兩半。左邊32位作為L0,右邊32位作為R0,供后面迭代使用。規(guī)定置換后的64位數(shù)據(jù)的各位依次是原明文數(shù)據(jù)的第58, 50, ..., 2, 60, ..., 15, 7位,其置換表如下所示

image

3. 加密函數(shù)

加密函數(shù)是DES的核心,它的租用是在第i次迭代中用子密鑰Ki對R(i-1)進行加密。其運行規(guī)則是:在第i次迭代加密中選擇運算E對32位的R(i-1)的各位進行選擇排列,產(chǎn)生48位的結(jié)果,此結(jié)果與48位的子密鑰進行異或運算,然后送入替代函數(shù)組S。S由8個替代函數(shù)(替代盒,Substitute Box)組成,每個S盒有6位輸入和4位輸出。8個S盒的輸出合并得到一個32位的數(shù)組,此數(shù)據(jù)組經(jīng)過置換運算P,將各位打亂重排,得到的結(jié)果便是加密函數(shù)的返回值f(R(i-1), Ki)。

image

1. 選擇運算 E

該過程對32位的數(shù)據(jù)組Half Block的各位進行選擇和排列,產(chǎn)生一個48位的結(jié)果,可見該運算是一個擴展運算,它將32位的數(shù)據(jù)擴展成了48位的數(shù)據(jù),以便與48位的子密鑰進行異或運算,下面是選擇運算的運算矩陣,可見它是通過重復(fù)選擇某些數(shù)據(jù)位來達到數(shù)據(jù)擴展的目的的。

image

2. 替代函數(shù)組 S

由8個S盒(S1, S2, S3, S4, S5, S6, S7, S8)組成,S的輸入是一個48位的數(shù)據(jù),從1到48位依次加到8個S盒的輸入端。每個S盒有一個替代矩陣,規(guī)定了其輸入輸出的替代規(guī)則。替代矩陣有4行16列,每行都是0到15這16個數(shù)字,但每行數(shù)字的排列都不同,且8個替代矩陣彼此不同。每個S盒有6位輸入,4位輸出,S盒的運算結(jié)果是用輸出數(shù)據(jù)替代輸入數(shù)據(jù),故稱為替代函數(shù)。

S盒的替代規(guī)則為:6位輸入的第1位和第6位組成二進制數(shù)b1b6代表對應(yīng)矩陣中被選中的行號,其余四位數(shù)字b2b3b4b5組成的二進制數(shù)代表對應(yīng)矩陣中被選中的列號。以S1為例,假設(shè)輸入數(shù)據(jù)為b1b2b3b4b5b6 = 101011,則第1位和第6位組成二進制數(shù)b1b6 = 11 = 3,表示選中行號為3的那行;b2b3b4b5 = 0101 = 5,表示選中列號為5的那列,行列交點為9,則S1的輸出為1001。替代函數(shù)組 S中各S盒矩陣如下所示

image

3. 置換運算 P

該過程是把S盒輸出的32位數(shù)據(jù)打亂重排,得到32位的加密函數(shù)輸出,用P置換來擴散,將S盒的混淆租用擴散開來,正是置換P與S盒的互相配合提高了DES的安全性,置換矩陣P如下所示

image

4. 代碼實現(xiàn)

/**
* DES核心加密函數(shù)
* 包括擴展、替代、選擇等操作
* @param right R(i-1)
* @param subKey Ki
* @return result 運算結(jié)果
*/
private char[] coreEncrypt(char[] right, char[] subKey) {
    // 1\. 選擇運算 E
    char[] extendedRight = ArrayUtil.disruptArray(right, DESConstants.E);
    // 2\. 將晉國選擇運算E擴展得到的48位的數(shù)據(jù)與子密鑰進行異或
    char[] xorResult = ArrayUtil.xor(extendedRight,subKey);

    // 3\. 用替代函數(shù)組進行替代
    // 為便于處理,將上述1x48位的數(shù)據(jù)矩陣轉(zhuǎn)換成8x6的數(shù)據(jù)矩陣
    char[][] twoDimensionArray = ArrayUtil.segmentDimension(xorResult,8,6);

    StringBuilder outputBuilder = new StringBuilder();

    // 根據(jù)替代規(guī)則進行替代
    for(inti=0;i<twoDimensionArray.length;i++) {
        char[] rowBits = {
                twoDimensionArray[i][0],
                twoDimensionArray[i][5]
        };
        char[]columnBits = {
          twoDimensionArray[i][1], twoDimensionArray[i][2],
          twoDimensionArray[i][3], twoDimensionArray[i][4]
        };

        // 獲取對應(yīng)S盒的輸出的坐標(biāo)
        int rowIndex = Integer.parseInt(String.valueOf(rowBits), 2);
        int columnIndex = Integer.parseInt(String.
                      valueOf(columnBits), 2);

        // 獲取對應(yīng)S盒的輸出
        short output = DESConstants.SUBSTITUTE_BOX[i][rowIndex][columnIndex];
        outputBuilder.append(Integer.toBinaryString((output&0x0f) + 0x10).substring(1));
   }
    char[] substitutedResult = outputBuilder.toString().toCharArray();

    // 4\. 進行置換運算 P,返回28位的數(shù)據(jù)
    return ArrayUtil.disruptArray(substitutedResult,DESConstants.P);
}

4. 整個加密過程

回顧DES的總體加密過程

  • 64位密鑰經(jīng)子密鑰產(chǎn)生算法產(chǎn)生出16個子密鑰: K1, K2, ..., K16 , 分別供第一次, 第二次, ..., 第十六次加密迭代使用。

  • 64位明文經(jīng)初始置換IP, 將數(shù)據(jù)打亂重排并分成左右兩半。左邊32位構(gòu)成L0 , 右邊2位構(gòu)成R0 。

  • 第一次加密迭代:由加密函數(shù)f實現(xiàn)子密鑰k1對R0的加密,得到32位的f(R0, K1),然后L0⊕f(R0, K1),32位的結(jié)果作為第二次加密迭代的R1,以R0作為第二次加密迭代的L1。

  • 第二次加密迭代至第十六次加密迭代分別用子密鑰K2 ,..., K16進行,其過程與第一次加密迭代相同。

  • 第十六次加密迭代結(jié)束后,產(chǎn)生一個64位的數(shù)據(jù)組,以其左邊32位作為R16, 以其右邊32位作為L16 。

  • R16與L16合并,再經(jīng)過逆初始置換IP ^–1, 將數(shù)據(jù)重新排列,便得到64位密文。

image

整體的加密過程函數(shù)

/**
* 對外的加密接口,主要邏輯封裝在encode方法中
* @param plaintext 明文
* @param key 密鑰
* @return encrypted text
* @throws UnsupportedEncodingException caused by String.getBytes()
*/
public Stringencrypt(String plaintext,String key) throws UnsupportedEncodingException {
    // 獲取明文及密鑰對應(yīng)的比特串,用字符數(shù)組存儲
    char[] plaintextBytes = ArrayUtil.bytesToChars(
        plaintext.getBytes("UTF-8"));

    char[]keyBytes=ArrayUtil.bytesToChars(
        key.getBytes("UTF-8"));

    // 子密鑰的生成
    char[][] subKeys = generateSubKeys(keyBytes);
    char[] result = encode(plaintextBytes,subKeys);

    // 使用Base64編碼對不可見及非打印字符進行編碼
    returnBase64Util.encode(result);
}

/**
* 主體加密邏輯
* @param plaintextBytes 用字符數(shù)組存放的明文的比特串
* @param subKeys 子密鑰數(shù)組
* @return encryption 64位加密結(jié)果
*/
private char[] encode(char[] plaintextBytes, char[][] subKeys) {
    // 初始置換 IP
    char[] chars = ArrayUtil.disruptArray(plaintextBytes,DESConstants.IP);

    // 將明文分成兩半
    int length = chars.length;
    String binaryArrayStr = String.valueOf(chars);
    char[] left = binaryArrayStr.substring(0, length/2).toCharArray();
    char[] right=binaryArrayStr.substring(length/2).toCharArray();

    char[]coreEncrypted,xorResult;

    for(inti=0;i<16;i++) {
        // 調(diào)用核心加密函數(shù),用子密鑰Ki對R(i-1)進行加密,得到28位數(shù)據(jù)
        coreEncrypted = coreEncrypt(right, subKeys[i]);

        // L(i - 1)與f(R(i - 1), Ki)進行異或運算
        xorResult = String.valueOf(ArrayUtil.xor(left, coreEncrypted))
                     .substring(16).toCharArray();

        left=right;
        right=xorResult;
     }
    char[] calResult = ArrayUtil.concat(right,left);
    // 逆初始置換
    return ArrayUtil.disruptArray(calResult,DESConstants.inverseIP);
}

三. 解密過程

通過數(shù)學(xué)推理可證明DES具有可逆性和對合性的(限于蝙蝠,在此不作證明),即加密和解密可共用同一個運算,只是子密鑰的使用順序調(diào)轉(zhuǎn)而已,即第一次解密迭代使用子密鑰K16,第十六次解密迭代使用子密鑰K1。

代碼實現(xiàn)

基于上述的思想,我們可以將通過密鑰獲取到的子密鑰數(shù)組逆轉(zhuǎn),然后調(diào)用encode方法即可

/**
* 解密
* @param encryptedText 加密數(shù)據(jù)
* @param key  密鑰
* @return decrypted origin plaintext
* @throws UnsupportedEncodingException caused by String.getBytes()
*/
@Override
public String decrypt(String encryptedText,String key) throws UnsupportedEncodingException {
    // 將已使用Base64編碼的密文解碼
    char[] encryptedTextBytes = Base64Util.decode(encryptedText);
    char[]keyBytes = ArrayUtil.bytesToChars(
              key.getBytes("UTF-8"));

    // 將通過密鑰獲取到的子密鑰數(shù)組逆轉(zhuǎn)
    char[][] inverseKeys = inverseSubKeys(generateSubKeys(keyBytes));
    // 解密
    char[] result = encode(encryptedTextBytes, inverseKeys);

    // 將比特串還原為字符串明文
    return ArrayUtil.segmentAndPrintChars("decrypt plaintext text", result);
}

/**
* 將通過密鑰獲取到的子密鑰數(shù)組逆轉(zhuǎn)
* @param subKeys 原子密鑰數(shù)組
* @return  翻轉(zhuǎn)的子密鑰數(shù)組
*/
private char[][] inverseSubKeys(char[][] subKeys) {
    char[][] inverseKeys = new char[subKeys.length][];
    for(inti = 0; i < subKeys.length; i++) {
        inverseKeys[i] = subKeys[subKeys.length - 1 - i];
   }
    returninverseKeys;
}

四. 測試

1. 測試代碼

使用Junit進行單元測試

@Test
public void testService() {
    Stringplaintext = "01234567", key = "12345678";
    CipherService cipherService = new DESCipherService();
    try{
        String encryptedText = cipherService.encrypt(plaintext, key);
        cipherService.decrypt(encryptedText,key);
    }catch(UnsupportedEncodingExceptione) {
        e.printStackTrace();
   }
} 

由于簡書文章的字?jǐn)?shù)限制,測試結(jié)果請見Github倉庫

五. 代碼說明

1. 源碼

本文中的代碼已發(fā)布到Github倉庫供學(xué)習(xí)討論,如果你覺得寫得還不錯,歡迎star和fork; 如果對代碼有疑惑,歡迎通過郵箱與我討論。

2. 缺陷

該DES實現(xiàn)旨在理解DES的原理,在軟件工程的視角它存在一些實用性上的不足,例如

  • 明文小于64位應(yīng)該進行填充

  • 明文大于64位應(yīng)該進行分組,待加密完成后進行密文的合并

  • 沒有就數(shù)據(jù)編碼進行擴展,暫不支持中文數(shù)據(jù)加密

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,914評論 2 372