1. 程式人生 > >DES演算法流程分析與實現

DES演算法流程分析與實現

DES(Data Encryption Standard,資料加密標準)作為一種基本結構為Feistel結構的加密演算法,其加密核心在於F函式。而Feistel結構決定了其加密解密流程是相同的,無論是硬體實現還是軟體實現都只需要一種結構,不需要分別實現。關於Feistel和DES在Feistel結構中的設計可以參考: Feistel網路結構與DES加密演算法的框架簡單分析 。今天我們重點來分析關於加密流程中用到的15張表的使用(初始置換表、
金鑰置換表、子金鑰移位表、子金鑰壓縮置換表、明文擴充套件置換表、S盒的8張壓縮置換表、P盒置換表、末置換表),及其具體的實現方式。

1、DES基本結構:

我們採用CBC-DES的加密模式,即先用初始化向量iv和明文分組進行異或,得到的結果進行加密即是密文分組,從第二組開始iv就是上一組的密文。解密方式與加密方式相反:先解密密文分組,再與iv進行異或得到明文分組(關於CBC分組加密模式可參考:TEA加密演算法與分組密碼的ECB、CBC模式選定 )。
這裡寫圖片描述
檔案讀取與CBC分組加密的程式碼如下:

//此時初始向量和金鑰已經隨機生成(或者從金鑰檔案中讀取出來)
FILE * fpin = fopen(infile, "r");   
assert(fpin != NULL);
FILE * fpout = fopen(outfile, "w"); 
assert(fpout !=
NULL); int len = 0; while(!feof(fpin)){ if((len = fread(data, 1, 8, fpin)) == 0){//沒有讀到內容則結束 if(!feof(fpin)) printf("檔案讀寫出錯!\n"); break; } else{ //enum OperType{ENCRYPT, DECRYPT};type是根據命令列傳參-e還是-d來確定的 if(type == ENCRYPT){ //先異或,再加密,加密完成後更新向量為加密後的祕文
xor_bit(iv, data, data, 64); des_AlgorithmDeal(data, key, type); memcpy(iv, data, 8); } else{ memcpy(temp , data, 8);//因為data會改變,data改變後需要用其改變前的值,因此要儲存 //先解密,再異或,異或完成後更新向量為上一組祕文 des_AlgorithmDeal(data, key, type); xor_bit(iv, data, data, 64); memcpy(iv, temp, 8);//祕文組複製非iv } fwrite(data, 1, 8, fpout); } memset(data, 0, 8); } fclose(fpin); fclose(fpout);

其中DES模組的基本結構如下所示:
這裡寫圖片描述
關於各個部分及細節,我們後面一一來詳細分析。

2、子金鑰產生:

首先由於DES是16輪迴圈,所以需要由64bit的隨機金鑰Key生成16個子金鑰Ki(i從0~15),Key共64bit,但是其每個位元組的最後一位都用不到(即第8、16、24、32、40、48、56、64位共8位),所以先通過初始置換將64bit的金鑰轉換為56bit。而轉換是根據金鑰轉換表(程式設計時一系列表均用陣列來儲存)來操作的,如下所示:
這裡寫圖片描述
該表共有56個元素(可以看到不包含8、16、24、32、40、48、56、64),代表了轉換後的56bit的位置(即output的下標)。而對應位置的數字x,表示轉換前64bit金鑰的下標+1(即input的bit位置)。比如:第1位(下標為0,位置為1)的值為57,即set_bit(output, 0, ge_bit(input, 56));獲取原來的第57位的bit值(0或1),作為置換後的第1位的bit值。依次類推(表的讀取方式為從左到右,從上到下,如:第3行第5個為置換後的第2*14+5=33位)。

置換的函式如下所示(該函式後面會多次用到,所以需要考慮不同位數的置換,最多64個元素置換,temp[]的大小就選為8*8=64):

//source為要置換的分組(可能大小8Byte、7B、6B、4B),index為置換表,len為置換的位數(即index表中的元素個數)
void permute(unsigned char * source, const int * index, int len)
{
    unsigned char temp[8] = {0};
    for(int i = 0; i < len; i++){
        set_bit(temp, i, get_bit(source, index[i]-1));//表中是從1開始,陣列下表要減去1
    }
    memcpy(source, temp, len/8);
}

經過置換的key變為56bit,該56bit分為左右兩半部分:left_keyright_key,各28bit,然後左右兩部分分別迴圈左移一定位數n。n的大小根據子金鑰Ki的下標決定(i從0~15),如下表所示(最多迴圈左移兩位,最少一位):
這裡寫圖片描述
經過移位的左右兩部分合併為56bit,然後經過壓縮置換將56bit壓縮為48bit,則產生一個子金鑰Ki,經過16輪迴圈產生16個子金鑰(對應DES結構圖)。而壓縮置換的壓縮表如下:
這裡寫圖片描述
子金鑰的產生程式碼如下所示:

if(key != NULL){//子金鑰生成,key為隨機產生的64bit金鑰    
    memcpy(temp, key, 8);//temp暫存key
    //金鑰置換:64bit->56bit
    permute(temp, DesTransform, 56);//DesTransform為金鑰置換表
    //金鑰分為左右兩部分各自28bit
    memset(lkey, 0, 4);
    memset(rkey, 0, 4);
    for(i = 0; i < 28; i++){
        set_bit(lkey, i, get_bit(temp, i));//temp的前28給left
        set_bit(rkey, i, get_bit(temp, i+28));//temp的後28給right
    }

    //進行16次迴圈,分別求出16個子金鑰
    for(i = 0; i < 16; i++){
    //DesSubkey為迴圈移位表
        rol_bit(lkey, 28, DesSubkey[i]);//左邊28位迴圈左移
        rol_bit(rkey, 28, DesSubkey[i]);//右邊28位迴圈左移
        for(j = 0; j < 28; j++){//和併為56bit是子金鑰
        //subKey[16][7]共16個(7*8=56)未壓縮的子金鑰
            set_bit(subKey[i], j, get_bit(lkey, j));    
            set_bit(subKey[i], j+28, get_bit(rkey, j)); 
        }
        //子金鑰壓縮置換成48位子金鑰
        permute(subKey[i], DesPermuted, 48);//DesPermuted為壓縮置換表
    }
}

3、明文初始置換與擴充套件:

64bit的明文分組,首先經過明文初始置換表(64bit->64bit的置換),打亂原有明文bit順序。類似於金鑰初始置換表,但是金鑰初始置換表是需要進行壓縮的(64bit->56bit),明文初始置換表如下:
這裡寫圖片描述
經過初始置換,將64bit明文分組分為左右兩部分,各32bit。下一輪的左半部分是本輪的右半部分;下一輪的右半部分:是本輪的右半部分和子金鑰Ki經過F函式運算的結果,再和本輪的左半部分進行異或得到的。

而F函式中第一步是進行擴充套件置換,將32bit的rdata擴充套件成為48bit的exp_data,擴充套件置換表為如下所示(擴充套件函式依舊是permute()只不過引數是rdata、擴充套件置換表、擴充套件置換表長度):
這裡寫圖片描述
經過擴充套件置換的明文rdata和經過壓縮置換的子金鑰Ki均為48bit,二者進行異或以後進入下一步操作:S盒置換。

4、S盒置換、P盒置換與末置換:

由於上一步的異或結果是48bit,我們要將異或結果重新壓縮到32bit並經過P盒置換(32bit->32bit)後,與原有左半部分32bit異或得到下輪的右半部分。而48bit壓縮到32bit不是用一張長度為32的置換表從48bit資訊中選取32位進行置換。而是通過8個S盒進行8組6bit->4bit的置換(8*4=32bit):即將48bit按順序分為8組,每組6bit。然後將每一組的6bit資訊輸入對應的S盒,輸出4bit的資訊,8組輸出合併為32bit的壓縮結果。8個S盒的內容均不一樣,但是使用方式都是一樣的,S盒如下所示:
這裡寫圖片描述

使用方式為輸入6bit為B1B2B3B4B5B6,B1B6兩位元(00~11,0~3)定位S盒的行,B2B3B4B5(0000~1111,0~15)定位S盒的列,行和列的組合定位到S盒中的具體的元素(元素值均為0~15,即0000~1111四位元)。該值的4bit 資訊作為輸出的4bit。如:S1盒的輸入為101011,則行為11 = 3,列為0101 = 5,即(3,5)= 9 = 1001,則輸出為1001。

經過8組6bit到4bit的轉換之後,將合併的32bit經過P盒置換提高混亂度,P盒置換表如下所示:
這裡寫圖片描述

明文置換、擴充套件置換、S盒置換、P盒置換的程式碼如下:

//明文初始置換
memcpy(temp, data, 8);
permute(temp, DesInitial, 64);//DesInitial為初始置換表

//明文分組分成左右兩部分各自32bit
memcpy(ldata, &temp[0], 4);
memcpy(rdata, &temp[4], 4);
for(i = 0; i<16; i++){
    //32bit經過擴充套件後為48bit
    memcpy(exp_data, rdata, 4);
    permute(exp_data, DesExpansion, 48);//DesExpansion擴充套件置換表
    //壓縮後的子金鑰和擴充套件後的明文有半部分進行異或操作,結果放入xor_data[]中
    if(type == ENCRYPT){
        xor_bit(subKey[i], exp_data, exp_data, 48);//加密:K0~K15
    }
    else{   
        xor_bit(subKey[15-i], exp_data, exp_data, 48);//解密:K15~K0
    }
    //S盒代替壓縮:(6*8)48bit->(4*8)32bit
    for(j = 0, p = 0; j < 8; j++){//p從0~32
        //根據6bit資訊獲取行和列
        row = (get_bit(exp_data, j*6+0) * 2) + (get_bit(exp_data, j*6+5) * 1); //b1b6
        col = (get_bit(exp_data, j*6+1) * 8) + (get_bit(exp_data, j*6+2) * 4) +
              (get_bit(exp_data, j*6+3)) * 2 + (get_bit(exp_data, j*6+4) * 1); //b2b3b4b5
        //根據行和列獲取S盒中對應值,計算4bit資訊
        value = (unsigned char)DesSbox[j][row][col];//DesSbox為S盒置換表DesSbox[8][4][16],8個4行16列
        //value為0~15,從左到右取第4、5、6、7位(低四位)即可,即4bit
        for(k = 4; k < 8; k++){
            set_bit(exp_data, p, get_bit(&value, k));
            p++;
        }
    }
    //做P盒置換
    permute(exp_data, DesPbox, 32);
    //置換結果與最初左半部分異或得到右半部分
    xor_bit(exp_data, ldata, exp_data, 32);
    //右半部分32bit作為左半部分
    memcpy(ldata, rdata, 4);
    memcpy(rdata, exp_data, 4);
}

在十六輪運算完畢之後,需要進行左右兩部分置換(其實是:最後一輪不用左右置換,但是16輪迴圈中為一種模式,即均置換。所以再左右置換一次相當於最後一輪沒置換)。然後將結果做末置換(類似於初始置換),最終的結果就是明文分組的DES加密結果,末置換表如下:
這裡寫圖片描述

末置換程式碼如下:

//最後一輪操作不需要左右調換,則複製回去時再將已經調換的調換一次,即沒有調換
memcpy(data, rdata, 4);//data為輸入的明文64bit,輸出時(函式返回時)應為64bit密文
memcpy(data + 4, ldata, 4);
//末置換
permute(data, DesFinal, 64);//DesFinal末置換表
return;//data此時為密文

5、用到的bit操作函式:

#include "bit.h"
//獲取指定地址指定位資訊(0/1)
int get_bit(unsigned char * input, int pos)
{
    //單位元組最高位為第0位,最低位為第7位
    unsigned char mask, i, bit;
    for(bit = pos % 8, mask = 0X80, i = 0; i < bit; i++){
        mask >>= 1;
    }
    return ( ( (mask & input[pos/8]) == mask ) ? 1 : 0 );
}
//設定指定地址指定位資訊(value = 0/1)
void set_bit(unsigned char * input, int pos, int value)
{
    unsigned char mask, i, bit;
    for(bit = pos % 8, mask = 0X80, i = 0; i < bit; i++){
        mask >>= 1;
    }
    (value) ? (input[pos/8] |= mask) : (input[pos/8] &= (~mask));
}
//異或操作,output儲存異或後的結果,size為需要異或的位數
void xor_bit(unsigned char * input1, unsigned char * input2, unsigned char * output, int size)
{
    int i;
    for(i = 0; i < size; i++){
        if(get_bit(input1, i) == get_bit(input2, i)){
            set_bit(output, i, 0);//相同為0
        }
        else{   
            set_bit(output, i, 1);//不同為1
        }
    }
}
//迴圈左移,size為總迴圈的長度,num為迴圈左移的位數
void rol_bit(unsigned char * input, int size, int num)
{
    //注意:低位元組為左,高位元組為右(與字元儲存方式對應)
    int left_bit = 0, loop_bit = 0, i = 0, j = 0;

    if(size > 0){
        for(i = 0; i < num; i++){//迴圈左移一位,共num次
            for(j = 0; j <= (size-1)/8; j++){//多位元組操作
                left_bit = get_bit(&input[j], 0);//獲取最高位
                if(j == 0){
                    //如果是低位元組,則其第一位,是左邊溢位的,需要儲存並在最終放到高位元組右邊最後一位
                    loop_bit = left_bit;
                }
                else{
                    //不是則直接將本位元組第一位,設定到上一位元組(低位元組也是左邊的位元組)最後一位
                    set_bit(&input[j-1], 7, left_bit);
                }
                input[j] <<= 1;
            }
            //把左邊移出的一位,移入最右邊一位(即迴圈左移)
            set_bit(input, size-1, loop_bit);
        }
    }
}

測試1:
關於隨機金鑰的產生和隨機初始向量的產生採用rand()隨機數函式模擬,可以看到每次加密產的金鑰和初始向量都不同(前半部分為金鑰,後半部分為iv),我們在加密時產生key.txt並儲存金鑰和iv,將該DES加密金鑰和初始向量通過雙鑰金鑰的公鑰金鑰加密後(如RSA),與密文一塊兒傳遞給通訊對方即可:
這裡寫圖片描述

測試2:
一首我最喜歡的《成功的花》用來測試,可以正常加密解密。需要注意的是:如果原明文檔案大小LEN%8不為0,則加密後的檔案是會增大的。因為最後一個分組可能有用資訊只有mbit(m<8),則剩餘8-mbit會被自動填充為0,而填充的0也是被加密了的。所以解密後文件末尾有一串’\0’,Windwos記事本、cat命令可能都看不見,但是用vim開啟就可以觀察到(但是:這對通訊沒有任何影響,只是一個現象而已,因為即使密文被截獲,也知道了最後一組的名文中包含’\0’,但是由於是CBC加密模式,後一組加密收受前一組影響,是不能根據最後一組的明文破解出金鑰和iv的。但如果是ECB模式就不好說了)。
這裡寫圖片描述

測試程式碼:DES檔案加密解密演算法實現,需要免費獲取的可郵箱聯絡索取([email protected])。

相關推薦

DES演算法流程分析實現

DES(Data Encryption Standard,資料加密標準)作為一種基本結構為Feistel結構的加密演算法,其加密核心在於F函式。而Feistel結構決定了其加密解密流程是相同的,無論是硬體實現還是軟體實現都只需要一種結構,不需要分別實現。關於Fe

DB Scan演算法分析實現

    根據上面第二個資料集的簇的形狀比較怪異,分簇結果應該是連起來的屬於一個簇,但是k-means結果分出來很不如人意,所以這裡介紹一種新的聚類方法,此方法不同於上一個基於劃分的方法,基於劃分主要發現圓形或者球形簇;為了發現任意形狀的簇,用一個基於密度的聚類方法,這類方法將簇看做是資料空間中被低

《機器學習實戰》決策樹(ID3演算法)的分析實現

        決策樹是一個預測模型;他代表的是物件屬性與物件值之間的一種對映關係。樹中每個節點表示某個物件,而每個分叉路徑則代表的某個可能的屬性值,而每個葉結點則對應從根節點到該葉節點所經歷的路徑所表示的物件的值。決策樹僅有單一輸出,若欲有複數輸出,可以建立獨立的決策樹以處理不同輸出。 資料探勘中決策樹是一

《推薦系統》基於使用者和Item的協同過濾演算法分析實現(Python)

開啟微信掃一掃,關注《資料與演算法聯盟》1:協同過濾演算法簡介2:協同過濾演算法的核心3:協同過濾演算法的應用方式4:基於使用者的協同過濾演算法實現5:基於物品的協同過濾演算法實現一:協同過濾演算法簡介    關於協同過濾的一個最經典的例子就是看電影,有時候不知道哪一部電影是

機器學習系列文章:Apriori關聯規則分析演算法原理分析程式碼實現

1.關聯規則淺談     關聯規則(Association Rules)是反映一個事物與其他事物之間的相互依存性和關聯性,如果兩個或多個事物之間存在一定的關聯關係,那麼,其中一個事物就能通過其他事物預測到。關聯規則是資料探勘的一個重要技術,用於從大量資料中挖掘出有價值的資料

十四 第三章再續 快速選擇SELECT演算法的深入分析實現

                                          十四、亦第三章再續:快速選擇SELECT演算法的深入分析與實現前言    經典演算法研究系列已經寫了十三個演算法,共計22篇文章(詳情,見這:十三個經典演算法研究與總結、目錄+索引),我很怕我自己不再把這個算法系列給繼續寫下去

歸併排序演算法原理分析程式碼實現

  歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用,歸併排序將兩個已排序的表合併成一個表。 歸併排序基本原理

七種排序演算法的簡單分析實現

{    int nFirst = nLow;    int nMid = (nLow + nHigh) /2;    int nSecond = nMid +1;    int* p = (int*)malloc(sizeof(int) * (nHigh - nLow +1));    int nIndex

Apriori演算法關聯分析pyhon實現

演算法中核心性質:頻繁項集的所有非空子集也必須是頻繁的。逆反命題 也成立:如果一個項集是非頻繁的,那麼所有它的超集也是非頻繁。 一、Apriori演算法簡介:  Apriori演算法是一種挖掘關聯規則的頻繁項集演算法,其核心思想是通過候選集生成和情節的向下封閉檢測兩個階

演算法導論-最大子陣列問題-線性時間複雜度演算法分析實現

之前寫了最大子陣列問題的分治法,今天把這個問題的線性時間複雜度的演算法寫出來。 這個方法在演算法導論最大子陣列問題的課後思考題裡面提出來了,只是說的不夠詳細。 思考題如下:使用如下思想為最大子陣列問題設計一個非遞迴的,線性時間複雜度的演算法。從陣列左邊界開始,由左至右處理,

Java排序演算法分析實現:快排、氣泡排序、選擇排序、插入排序、歸併排序(一)

轉載  https://www.cnblogs.com/bjh1117/p/8335628.html   一、概述:   本文給出常見的幾種排序演算法的原理以及java實現,包括常見的簡單排序和高階排序演算法,以及其他常用的演算法知識。   簡單排序:氣泡排序、選擇排序、

第五篇:樸素貝葉斯分類演算法原理分析程式碼實現

1 #==================================== 2 # 輸入: 3 # 空 4 # 輸出: 5 # postingList: 文件列表 6 # classVec: 分類標籤列表 7 #=

求子集問題演算法分析實現(遞迴、非遞迴)

問題描述: 若有數字集合{1,2,3},則其子集為NULL、{1}、{2}、{3}、{1,2}、{1,3}、{2,3}、{1,2,3}。現給定陣列,求其的全部子集。 實現如下: //非

網路安全學習之C語言版DES加密解密演算法的程式設計實現

其實明白了DES演算法的流程程式設計實現是不難的,當然可能會在S盒實現那碰到點問題。下面的DES演算法包括加密和解密兩個功能,主要有生成16個子金鑰和DES演算法的主程式組成。輸出的資訊有16輪子金鑰以及每輪的中間值以及最後的結果。具體的程式碼中都有註釋,就看程式碼吧。關於D

最短路徑演算法—Floyd(弗洛伊德)演算法分析實現(Python)

December 19, 2015 10:56 PM Floyd演算法是解決任意兩點間的最短路徑的一種演算法,可以正確處理帶權有向圖或負權的最短路徑問題 解決此問題有兩種方法: 其一是分別以圖中每個頂點為源點共呼叫n次演算法; 其二是採用Floyd演算法

最短路徑演算法—Dijkstra(迪傑斯特拉)演算法分析實現(Python)

December 18, 2015 12:56 PM Dijkstra(迪傑斯特拉)演算法是典型的最短路徑演算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止。Dijkstra演算法能得出最短路徑的

最短路徑演算法—Dijkstra(迪傑斯特拉)演算法分析實現(C/C++)

Dijkstra(迪傑斯特拉)演算法是典型的最短路徑路由演算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止。Dijkstra演算法能得出最短路徑的最優解,但由於它遍歷計算的節點很多,所以效率低。   Dijks

LRU快取淘汰演算法分析實現

概述 記錄一下LRU快取淘汰演算法的實現。 原理 LRU(Least recently used,最近最少使用)快取演算法根據資料最近被訪問的情況來進行淘汰資料,其核心思想是“如果資料最近被訪問過,那麼將來被訪問的機率也更高”。 介紹 下圖中,介紹

漢諾塔非遞迴演算法分析實現

漢諾塔的遞迴演算法很容易理解,也非常容易實現。下面,本文討論了漢諾塔問題的非遞迴演算法,核心內容就是棧的使用技巧。 首先,對於每個柱子來說,就是一個棧,這個棧有個特點就是,大數放在下面,小數放在上面。在首次建立棧時,我們可以先儲存好這些資料,假設最小的盤子序號

最短路徑演算法—Dijkstra(迪傑斯特拉)演算法分析實現(C/C++)及其他 + leetcode習題實踐

   最短路徑求解  最短路徑的常用解法有迪傑克斯特拉演算法Dijkstra Algorithm, 弗洛伊德演算法Floyd-Warshall Algorithm, 和貝爾曼福特演算法Bellman-Ford Algorithm,其中,Floyd演算法是多源最短路徑,即求