加密演算法原理分析(MD5、SHA-256)

若丶相見發表於2019-05-24

最近回顧了一下資料結構,發現一直在使用HasMap,但是大學期間針對雜湊表的概念老師當時竟然跳過啦,因此重新瞭解了一下雜湊表的基礎知識。有個不錯的講解視訊: v.youku.com/v_show/id_X…

順便也瞭解了一下HashMap的原理,是如何使用雜湊表的,感興趣的小夥伴可以看一下: www.cnblogs.com/xiaoxi/p/72…www.cnblogs.com/chengxiao/p…

先放兩張雜湊表的結構吧:

  • JDK1.8之前,以及JDK1.8之後,同一個hash值的節點數小於8時

    加密演算法原理分析(MD5、SHA-256)

  • JDK1.8之後,當同一個hash值的節點數不小於8時,不再採用單連結串列形式儲存,而是採用紅黑樹

    加密演算法原理分析(MD5、SHA-256)

本文主要還是看一看再研究雜湊表過程中,比較有意思的雜湊的加密演算法(非對稱加密)

雜湊函式:Hash,一般翻譯做雜湊、雜湊,或音譯為雜湊,就是把任意長度的輸入(又叫做預對映, pre-image),通過雜湊演算法,變換成固定長度的輸出,該輸出就是雜湊值。這種轉換是一種壓縮對映,也就是,雜湊值的空間通常遠小於輸入的空間,不同的輸入可能會雜湊成相同的輸出,而不可能從雜湊值來唯一的確定輸入值。簡單的說就是一種將任意長度的訊息壓縮到某一固定長度的訊息摘要的函式。

本文主要講解兩種常用的雜湊函式MD5和SHA-256(目前最流行的安全加密演算法)

MD5

MD5訊息摘要演算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼雜湊函式,可以產生出一個128位(16位元組)的雜湊值(hash value),用於確保資訊傳輸完整一致。MD5由美國密碼學家羅納德·李維斯特(Ronald Linn Rivest)設計,於1992年公開,用以取代MD4演算法。

演算法步驟:

1、資料填充

對訊息進行資料填充,使訊息的長度對512取模得448,設訊息長度為X,即滿足X mod 512=448。根據此公式得出需要填充的資料長度。

填充方法:在訊息後面進行填充,填充第一位為1,其餘為0。

2、新增訊息長度

在第一步結果之後再填充上原訊息的長度,可用來進行的儲存長度為64位。如果訊息長度大於264,則只使用其低64位的值,即(訊息長度 對 264取模)。

經過上面兩步的處理,現在的資訊的位長=N*512+448+64=(N+1)*512,即長度恰好是512的整數倍。這樣做的原因是為滿足後面處理中對資訊長度的要求。

3、資料處理:分組處理

  • 準備需要用到的資料:

    4個常數:

      A = 0x67452301, B = 0x0EFCDAB89, C = 0x98BADCFE, D = 0x10325476;
    複製程式碼

    4個函式:(&是與,|是或,~是非,^是異或)

      F(X,Y,Z)=(X & Y) | ((~X) & Z); G(X,Y,Z)=(X & Z) | (Y & (~Z));   
      H(X,Y,Z)=X ^ Y ^ Z;   I(X,Y,Z)=Y ^ (X | (~Z));
    複製程式碼
  • 具體操作:

  1. 首先將訊息以512位為一分組進行處理,分為N組
  2. 將每組訊息N(i)進行4輪變換(四輪主迴圈),以上面所說4個常數首先賦值給a、b、c、d為起始變數進行計算,重新輸出4個變數,並重新賦值給a、b、c、d四個值。
  3. 以第2步獲得的新的a、b、c、d四個值,再進行下一分組的運算,如果已經是最後一個分組,則這4個變數的最後結果按照從低記憶體到高記憶體排列起來,共128位,這就是MD5演算法的輸出。

第二步詳細介紹:主迴圈有四輪(MD4只有三輪),每輪迴圈都很相似。每輪主迴圈都有16輪次迴圈,16輪次迴圈:其實是將每組512位分為16組,每次操作對a、b、c和d中的其中三個作一次非線性函式運算,然後將所得結果加上第四個變數,文字的一個子分組和一個常數。再將所得結果向左環移一個不定的數,並加上a、b、c或d中之一。最後用該結果取代a、b、c或d中之一。

注(a、b、c或d):可被稱為訊息摘要部分資訊,因此上面四個非線性函式,是針對a、b、c或d運算

*** 四輪主迴圈主要的運算函式為下面四個包含了上面四個非執行緒函式的函式:

設Mj表示訊息的第j個子分組(從0到15),<<< s表示迴圈左移s位,則四種操作為: FF(a,b,c,d,Mj,s,ti)表示a=b+((a+(F(b,c,d)+Mj+ti)<<< s)

GG(a,b,c,d,Mj,s,ti)表示a=b+((a+(G(b,c,d)+Mj+ti)<<< s)

HH(a,b,c,d,Mj,s,ti)表示a=b+((a+(H(b,c,d)+Mj+ti)<<< s)

II(a,b,c,d,Mj,s,ti)表示a=b+((a+(I(b,c,d)+Mj+ti)<<< s)

此四個函式中的前四個位置的a,b,c,d同理X、Y、Z等,不僅僅代表上面所說的訊息摘要的資訊a,b,c,d,可理解為程式碼中的四個引數。而引數一:就是本次次迴圈需要求的值。具體可通過下面具體的64步瞭解。

加密演算法原理分析(MD5、SHA-256)
此圖只選了FF函式說明一下,16輪次迴圈每一步FF函式中前四個引數a,b,c,d的變化,同時根據上面所闡述的,引數一即為此次函式所求值可知,FF會根據16組訊息M(i)、以及s和ti,迴圈的計算訊息摘要的a,b,c,d四個值。(GG、HH、II函式同理)

這四輪(64步)是:

第一輪 
FF(a,b,c,d,M0,7,0xd76aa478) 
FF(d,a,b,c,M1,12,0xe8c7b756) 
FF(c,d,a,b,M2,17,0×242070db) 
FF(b,c,d,a,M3,22,0xc1bdceee) 
FF(a,b,c,d,M4,7,0xf57c0faf) 
FF(d,a,b,c,M5,12,0×4787c62a) 
FF(c,d,a,b,M6,17,0xa8304613) 
FF(b,c,d,a,M7,22,0xfd469501) 
FF(a,b,c,d,M8,7,0×698098d8) 
FF(d,a,b,c,M9,12,0×8b44f7af) 
FF(c,d,a,b,M10,17,0xffff5bb1) 
FF(b,c,d,a,M11,22,0×895cd7be) 
FF(a,b,c,d,M12,7,0×6b901122) 
FF(d,a,b,c,M13,12,0xfd987193) 
FF(c,d,a,b,M14,17,0xa679438e) 
FF(b,c,d,a,M15,22,0×49b40821) 
第二輪 
GG(a,b,c,d,M1,5,0xf61e2562) 
GG(d,a,b,c,M6,9,0xc040b340) 
GG(c,d,a,b,M11,14,0×265e5a51) 
GG(b,c,d,a,M0,20,0xe9b6c7aa) 
GG(a,b,c,d,M5,5,0xd62f105d) 
GG(d,a,b,c,M10,9,0×02441453) 
GG(c,d,a,b,M15,14,0xd8a1e681) 
GG(b,c,d,a,M4,20,0xe7d3fbc8) 
GG(a,b,c,d,M9,5,0×21e1cde6) 
GG(d,a,b,c,M14,9,0xc33707d6) 
GG(c,d,a,b,M3,14,0xf4d50d87) 
GG(b,c,d,a,M8,20,0×455a14ed) 
GG(a,b,c,d,M13,5,0xa9e3e905) 
GG(d,a,b,c,M2,9,0xfcefa3f8) 
GG(c,d,a,b,M7,14,0×676f02d9) 
GG(b,c,d,a,M12,20,0×8d2a4c8a) 
第三輪 
HH(a,b,c,d,M5,4,0xfffa3942) 
HH(d,a,b,c,M8,11,0×8771f681) 
HH(c,d,a,b,M11,16,0×6d9d6122) 
HH(b,c,d,a,M14,23,0xfde5380c) 
HH(a,b,c,d,M1,4,0xa4beea44) 
HH(d,a,b,c,M4,11,0×4bdecfa9) 
HH(c,d,a,b,M7,16,0xf6bb4b60) 
HH(b,c,d,a,M10,23,0xbebfbc70) 
HH(a,b,c,d,M13,4,0×289b7ec6) 
HH(d,a,b,c,M0,11,0xeaa127fa) 
HH(c,d,a,b,M3,16,0xd4ef3085) 
HH(b,c,d,a,M6,23,0×04881d05) 
HH(a,b,c,d,M9,4,0xd9d4d039) 
HH(d,a,b,c,M12,11,0xe6db99e5) 
HH(c,d,a,b,M15,16,0×1fa27cf8) 
HH(b,c,d,a,M2,23,0xc4ac5665) 
第四輪 
II(a,b,c,d,M0,6,0xf4292244) 
II(d,a,b,c,M7,10,0×432aff97) 
II(c,d,a,b,M14,15,0xab9423a7) 
II(b,c,d,a,M5,21,0xfc93a039) 
II(a,b,c,d,M12,6,0×655b59c3) 
II(d,a,b,c,M3,10,0×8f0ccc92) 
II(c,d,a,b,M10,15,0xffeff47d) 
II(b,c,d,a,M1,21,0×85845dd1) 
II(a,b,c,d,M8,6,0×6fa87e4f) 
II(d,a,b,c,M15,10,0xfe2ce6e0) 
II(c,d,a,b,M6,15,0xa3014314) 
II(b,c,d,a,M13,21,0×4e0811a1) 
II(a,b,c,d,M4,6,0xf7537e82) 
II(d,a,b,c,M11,10,0xbd3af235) 
II(c,d,a,b,M2,15,0×2ad7d2bb) 
II(b,c,d,a,M9,21,0xeb86d391) 
複製程式碼

所有這些完成之後,將A,B,C,D分別加上a,b,c,d。然後用下一分組資料繼續執行演算法,最後的輸出是A,B,C和D的級聯。 把這四個數A -> B -> C -> D按照從低記憶體到高記憶體排列起來,共128位,這就是MD5演算法的輸出。

Java程式碼實現:

public class MD5{
    /*
    *四個連結變數:暫存器中數值(小端模式)// 真實數值
    */
    private final int A=0x67452301;// 真實A=0×01234567
    private final int B=0xefcdab89;// 真實B=0×89abcdef 
    private final int C=0x98badcfe;// 真實C=0xfedcba98 
    private final int D=0x10325476;// 真實D=0×76543210 
    /*
    *ABCD的臨時變數
    */
    private int Atemp,Btemp,Ctemp,Dtemp;
     
    /*
    * 常量ti:即程式碼中的K[i]
    *   公式:floor(abs(sin(i+1))×(2pow32)
    *   ti是4294967296*abs(sin(i))的整數部分,i的單位是弧度。 
    *   4294967296 == 2的32次方
    * 由於是常量,可以在計算時直接嵌入資料。
    */
    private final int K[]={
        0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
        0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8,
        0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,
        0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51,
        0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,
        0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,
        0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681,
        0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,
        0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,
        0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244,
        0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,
        0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314,
        0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391};
    /*
    *向左位移數,由於是常量,也可以在計算時直接嵌入資料。
    *(此資料有規律,實際程式碼中可以對此進行優化,即改變此部分值)。
    */
    private final int s[]={7,12,17,22,7,12,17,22,7,12,17,22,7,
        12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
        4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,
        15,21,6,10,15,21,6,10,15,21,6,10,15,21};
     
    /*
    *初始化函式
    */
    private void init(){
        Atemp=A;
        Btemp=B;
        Ctemp=C;
        Dtemp=D;
    }
    /*
    *移動一定位數
    */
    private int shift(int a,int s){
        return(a<<s)|(a>>>(32-s));//右移的時候,高位一定要補零,而不是補充符號位
    }
    /*
    *主迴圈
    */
    private void MainLoop(int M[]){
        int F,g;
        int a=Atemp;
        int b=Btemp;
        int c=Ctemp;
        int d=Dtemp;
        for(int i = 0; i < 64; i ++){
            if(i<16){
                F=(b&c)|((~b)&d);
                g=i;
            }else if(i<32){
                F=(d&b)|((~d)&c);
                g=(5*i+1)%16;
            }else if(i<48){
                F=b^c^d;
                g=(3*i+5)%16;
            }else{
                F=c^(b|(~d));
                g=(7*i)%16;
            }
            int tmp=d;
            d=c;
            c=b;
            b=b+shift(a+F+K[i]+M[g],s[i]);
            a=tmp;
        }
        Atemp=a+Atemp;
        Btemp=b+Btemp;
        Ctemp=c+Ctemp;
        Dtemp=d+Dtemp;
     
    }
    /*
    *填充函式
    *處理後應滿足bits≡448(mod512),位元組就是bytes≡56(mode64)
    *填充方式為先加一個0,其它位補零
    *最後加上64位的原來長度
    */
    private int[] add(String str){
        int num=((str.length()+8)/64)+1;//以512位,64個位元組為一組
        int strByte[]=new int[num*16];//64/4=16,所以有16個整數
        for(int i=0;i<num*16;i++){//全部初始化0
            strByte[i]=0;
        }
        int    i;
        for(i=0;i<str.length();i++){
            strByte[i>>2]|=str.charAt(i)<<((i%4)*8);//一個整數儲存四個位元組,小端序
        }
        strByte[i>>2]|=0x80<<((i%4)*8);//尾部新增1
        /*
        *新增原長度,長度指位的長度,所以要乘8,然後是小端序,所以放在倒數第二個,這裡長度只用了32位
        */
        strByte[num*16-2]=str.length()*8;
        return strByte;
    }
    /*
    *呼叫函式
    */
    public String getMD5(String source){
        init();
        int strByte[]=add(source);
        for(int i = 0; i < strByte.length / 16; i++){
            int num[] = new int[16];
            for(int j = 0; j < 16; j++){
                num[j] = strByte[i*16+j];
            }
            MainLoop(num);
        }
        return changeHex(Atemp)+changeHex(Btemp)+changeHex(Ctemp)+changeHex(Dtemp);
    }
    /*
    *整數變成16進位制字串
    */
    private String changeHex(int a){
        String str="";
        for(int i=0;i<4;i++){
            str+=String.format("%2s", Integer.toHexString(((a>>i*8)%(1<<8))&0xff)).replace(' ', '0');
 
        }
        return str;
    }
    /*
    *單例
    */
    private static MD5 instance;
    public static MD5 getInstance(){
        if(instance==null){
            instance=new MD5();
        }
        return instance;
    }
     
    private MD5(){};
     
    public static void main(String[] args){
        String str=MD5.getInstance().getMD5("");
        System.out.println(str);
    }
}
複製程式碼

MD5演算法的不足:

現在看來,MD5已經較老,雜湊長度通常為128位,隨著計算機運算能力提高,找到“碰撞”是可能的。因此,在安全要求高的場合不使用MD5。

2004年,王小云教授證明MD5數字簽名演算法可以產生碰撞。2007年,Marc Stevens,Arjen K. Lenstra和Benne de Weger進一步指出通過偽造軟體簽名,可重複性攻擊MD5演算法。研究者使用字首碰撞法(chosen-prefix collision),使程式前端包含惡意程式,利用後面的空間添上垃圾程式碼湊出同樣的MD5 Hash值。2007年,荷蘭埃因霍芬技術大學科學家成功把2個可執行檔案進行了MD5碰撞,使得這兩個執行結果不同的程式被計算出同一個MD5。2008年12月科研人員通過MD5碰撞成功生成了偽造的SSL證照,這使得在https協議中伺服器可以偽造一些根CA的簽名。

MD5被攻破後,在Crypto2008上, Rivest提出了MD6演算法,該演算法的Block size為512 bytes(MD5的Block Size是512 bits), Chaining value長度為1024 bits, 演算法增加了並行 機制,適合於多核CPU。 在安全性上,Rivest宣稱該演算法能夠抵抗截至目前已知的所有的 攻擊(包括差分攻擊)。

SHA-256

SHA256簡介

SHA256是SHA-2下細分出的一種演算法

SHA-2,名稱來自於安全雜湊演算法2(英語:Secure Hash Algorithm 2)的縮寫,一種密碼雜湊函式演算法標準,由美國國家安全域性研發,屬於SHA演算法之一,是SHA-1的後繼者。

SHA-2下又可再分為六個不同的演算法標準

包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

這些變體除了生成摘要的長度 、迴圈執行的次數等一些微小差異外,演算法的基本結構是一致的。

對於任意長度的訊息,SHA256都會產生一個256bit長的雜湊值,稱作訊息摘要。這個摘要相當於是個長度為32個位元組的陣列,通常用一個長度為64的十六進位制字串來表示

SHA256演算法步驟:

  • 初始化常量(初始化快取:8個雜湊初值)和演算法中用到的64個常量

    8個雜湊初值:是對自然數中前8個質數(2,3,5,7,11,13,17,19)的平方根的小數部分取前32位, 此8個雜湊初值位SHA256演算法中的最小運算單元稱為“字”(Word),一個字是32位。

      A=0x6A09E667 , B=0xBB67AE85 , C=0x3C6EF372 , D=0xA54FF53A, 
      E=0x510E527F , F=0x9B05688C , G=0x1F83D9AB , H=0x5BE0CD19 。
    複製程式碼

    64個演算法常量:對自然數中前64個質數(2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97…)的立方根的小數部分取前32位

      0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL,
      0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL,
      0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL,
      0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL,
      0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL,
      0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL,
      0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL,
      0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
      0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL,
      0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL,
      0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL,
      0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
      0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL
    複製程式碼
  • 預處理:附加填充位元和附加長度值

    1、附加填充位元

      在報文末尾進行填充,使報文長度在對512取模以後的餘數是448
      填充是這樣進行的:先補第一個位元為1,然後都補0,直到長度滿足對512取模後餘數是448。
      需要注意的是,資訊必須進行填充,也就是說,即使長度已經滿足對512取模後餘數是448,
      補位也必須要進行,這時要填充512個位元。因此,填充是至少補一位,最多補512位。
    複製程式碼

    2、附加長度值

      附加長度值就是將原始資料(第一步填充前的訊息)的長度資訊補到已經進行了填充操作的訊息後面。
      SHA256用一個64位的資料來表示原始訊息的長度。
      因此,通過SHA256計算的訊息長度必須要小於2^64,當然絕大多數情況這足夠大了。
      而SHA-384和SHA-512,訊息長度最大可接近2^128
    複製程式碼
  • 分組運算

加密演算法原理分析(MD5、SHA-256)
先上一張通用圖吧

1、分組運算所需的6個函式

Ch(x,y,z) = ((x & y) ^ ((~x) & z))
Maj(x,y,z) = ((x & y) ^ (x & z) ^ (y & z))
E0(x) = (x >> 2 | x << 30) ^ (x >> 13 | x << 19) ^ (x >> 22 | x << 10);
E1(x) = (x >> 6 | x << 26) ^ (x >> 11 | x << 21) ^ (x >> 25 | x << 7)
Q0(x) = (x >> 7 | x << 25) ^ (x >> 18 | x << 14) ^ (x >> 3)
Q1(x) = (x >> 17 | x << 15) ^ (x >> 19 | x << 13) ^ (x >> 10)
複製程式碼

2、將訊息分解成512-bit大小的塊,分成N組,每組都會經過上面6個函式的迴圈運算

3、構造64個字(word):將上述分完組的塊,每塊分解為16個32-bit的big-endian的字,記為w[0], …, w[15],即前16個字直接由訊息的第i個塊分解得到,而其餘的字由如下迭代公式得到:

Wt = Q1(Wt-2) + Wt-7 + Q0(Wt-15) + Wt-16
複製程式碼

4、進行64次迴圈:演算法使用64個32位字的訊息列表、8個32位工作變數以及8個32位字的雜湊值。

  • 初始化工作變數

    a = A // H(0)0 = A
    b = B // H(0)1 = B
    c = C // H(0)2 = C
    d = D // H(0)3 = D
    e = E // H(0)4 = E
    f = F // H(0)5 = F
    g = G // H(0)6 = G
    h = H // H(0)7 = H

  • 執行雜湊計算

    For t = 0 to 63 T1 = h + E1(e) + CH(e,f,g) + Kt + Wt
    T2 = E0(a) + MAj(a,b,c) h = g
    g = f
    f = e
    e = d + T1
    d = c
    c = b
    b = a
    a = T1 + T2

  • 計算中間雜湊值

    H(i)0 = a + H(i-1)0

    H(i)1 = b + H(i-1)1

    H(i)2 = c + H(i-1)2

    H(i)3 = d + H(i-1)3

    H(i)4 = e + H(i-1)4

    H(i)5 = f + H(i-1)5

    H(i)6 = g + H(i-1)6

    H(i)7 = h + H(i-1)7

在對所有訊息分組完成上述計算之後,計算最終輸出。對於SHA-256,是所有H(N)0、H(N)1到H(N)7的串聯。對於SHA-224,則是H(N)0、H(N)1直到H(N)6的串聯。

C++的程式碼實現:

借鑑一份別人寫好的C++實現(下方已標明引用地址)

//SHA-256
/*理解演算法最重要,最好自己動手實現試試看,可以使用MFC寫一個簡單的互動介面*/
 
#include <iostream> 
#include <cstdio>
#include <cstdlib>
 
using namespace std;
 
#define SHA256_ROTR(a,b) (((a>>(32-b))&(0x7fffffff>>(31-b)))|(a<<b))
#define SHA256_SR(a,b) ((a>>b)&(0x7fffffff>>(b-1)))
#define SHA256_Ch(x,y,z) ((x&y)^((~x)&z))
#define SHA256_Maj(x,y,z) ((x&y)^(x&z)^(y&z))
#define SHA256_E0(x) (SHA256_ROTR(x,30)^SHA256_ROTR(x,19)^SHA256_ROTR(x,10))
#define SHA256_E1(x) (SHA256_ROTR(x,26)^SHA256_ROTR(x,21)^SHA256_ROTR(x,7))
#define SHA256_Q0(x) (SHA256_ROTR(x,25)^SHA256_ROTR(x,14)^SHA256_SR(x,3))
#define SHA256_Q1(x) (SHA256_ROTR(x,15)^SHA256_ROTR(x,13)^SHA256_SR(x,10))
char* StrSHA256(const char* str, long long length, char* sha256){
    char *pp, *ppend;
    long l, i, W[64], T1, T2, A, B, C, D, E, F, G, H, H0, H1, H2, H3, H4, H5, H6, H7;
    H0 = 0x6a09e667, H1 = 0xbb67ae85, H2 = 0x3c6ef372, H3 = 0xa54ff53a;
    H4 = 0x510e527f, H5 = 0x9b05688c, H6 = 0x1f83d9ab, H7 = 0x5be0cd19;
    long K[64] = {
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
    };
    l = length + ((length % 64 > 56) ? (128 - length % 64) : (64 - length % 64));
    if (!(pp = (char*)malloc((unsigned long)l))) return 0;
    for (i = 0; i < length; pp[i + 3 - 2 * (i % 4)] = str[i], i++);
    for (pp[i + 3 - 2 * (i % 4)] = 128, i++; i < l; pp[i + 3 - 2 * (i % 4)] = 0, i++);
    *((long*)(pp + l - 4)) = length << 3;
    *((long*)(pp + l - 8)) = length >> 29;
    for (ppend = pp + l; pp < ppend; pp += 64){
        for (i = 0; i < 16; W[i] = ((long*)pp)[i], i++);
        for (i = 16; i < 64; W[i] = (SHA256_Q1(W[i - 2]) + W[i - 7] + SHA256_Q0(W[i - 15]) + W[i - 16]), i++);
        A = H0, B = H1, C = H2, D = H3, E = H4, F = H5, G = H6, H = H7;
        for (i = 0; i < 64; i++){
            T1 = H + SHA256_E1(E) + SHA256_Ch(E, F, G) + K[i] + W[i];
            T2 = SHA256_E0(A) + SHA256_Maj(A, B, C);
            H = G, G = F, F = E, E = D + T1, D = C, C = B, B = A, A = T1 + T2;
        }
        H0 += A, H1 += B, H2 += C, H3 += D, H4 += E, H5 += F, H6 += G, H7 += H;
    }
    free(pp - l);
    sprintf(sha256, "%08X%08X%08X%08X%08X%08X%08X%08X", H0, H1, H2, H3, H4, H5, H6, H7);
    return sha256;
}
char* FileSHA256(const char* file, char* sha256){
 
    FILE* fh;
    char* addlp, T[64];
    long addlsize, j, W[64], T1, T2, A, B, C, D, E, F, G, H, H0, H1, H2, H3, H4, H5, H6, H7;
    long long length, i, cpys;
    void *pp, *ppend;
    H0 = 0x6a09e667, H1 = 0xbb67ae85, H2 = 0x3c6ef372, H3 = 0xa54ff53a;
    H4 = 0x510e527f, H5 = 0x9b05688c, H6 = 0x1f83d9ab, H7 = 0x5be0cd19;
    long K[64] = {
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
    };
    fh = fopen(file, "rb");
    fseek(fh, 0, SEEK_END);
    length = _ftelli64(fh);
    addlsize = (56 - length % 64 > 0) ? (64) : (128);
    if (!(addlp = (char*)malloc(addlsize))) return 0;
    cpys = ((length - (56 - length % 64)) > 0) ? (length - length % 64) : (0);
    j = (long)(length - cpys);
    if (!(pp = (char*)malloc(j))) return 0;
    fseek(fh, -j, SEEK_END);
    fread(pp, 1, j, fh);
    for (i = 0; i < j; addlp[i + 3 - 2 * (i % 4)] = ((char*)pp)[i], i++);
    free(pp);
    for (addlp[i + 3 - 2 * (i % 4)] = 128, i++; i < addlsize; addlp[i + 3 - 2 * (i % 4)] = 0, i++);
    *((long*)(addlp + addlsize - 4)) = length << 3;
    *((long*)(addlp + addlsize - 8)) = length >> 29;
    for (rewind(fh); 64 == fread(W, 1, 64, fh);){
        for (i = 0; i < 64; T[i + 3 - 2 * (i % 4)] = ((char*)W)[i], i++);
        for (i = 0; i < 16; W[i] = ((long*)T)[i], i++);
        for (i = 16; i < 64; W[i] = (SHA256_Q1(W[i - 2]) + W[i - 7] + SHA256_Q0(W[i - 15]) + W[i - 16]), i++);
        A = H0, B = H1, C = H2, D = H3, E = H4, F = H5, G = H6, H = H7;
        for (i = 0; i < 64; i++){
            T1 = H + SHA256_E1(E) + SHA256_Ch(E, F, G) + K[i] + W[i];
            T2 = SHA256_E0(A) + SHA256_Maj(A, B, C);
            H = G, G = F, F = E, E = D + T1, D = C, C = B, B = A, A = T1 + T2;
        }
        H0 += A, H1 += B, H2 += C, H3 += D, H4 += E, H5 += F, H6 += G, H7 += H;
    }
    for (pp = addlp, ppend = addlp + addlsize; pp < ppend; pp = (long*)pp + 16){
        for (i = 0; i < 16; W[i] = ((long*)pp)[i], i++);
        for (i = 16; i < 64; W[i] = (SHA256_Q1(W[i - 2]) + W[i - 7] + SHA256_Q0(W[i - 15]) + W[i - 16]), i++);
        A = H0, B = H1, C = H2, D = H3, E = H4, F = H5, G = H6, H = H7;
        for (i = 0; i < 64; i++){
            T1 = H + SHA256_E1(E) + SHA256_Ch(E, F, G) + K[i] + W[i];
            T2 = SHA256_E0(A) + SHA256_Maj(A, B, C);
            H = G, G = F, F = E, E = D + T1, D = C, C = B, B = A, A = T1 + T2;
        }
        H0 += A, H1 += B, H2 += C, H3 += D, H4 += E, H5 += F, H6 += G, H7 += H;
    }
    free(addlp); fclose(fh);
    sprintf(sha256, "%08X%08X%08X%08X%08X%08X%08X%08X", H0, H1, H2, H3, H4, H5, H6, H7);
    return sha256;
}
 
char* StrSHA256(const char* str, long long length, char* sha256);
 
int main(void){
    char text[256];
    cout<<"請輸入原文:\n" ;
    while(cin>>text) 
    {
        cout<<"請輸入原文:\n" ;
        char sha256[256];
        StrSHA256(text,sizeof(text)-1,sha256);  // sizeof()包含了末尾的終止符'\0'故 -1
        cout<<"執行SHA-256演算法後的結果如下:\n";
        puts(sha256);
        
    }
    
    system("pause");
    return 0;
}
複製程式碼

參考資料:

百度百科

zh.wikipedia.org/wiki/SHA-2

www.cnblogs.com/chars/p/498…

blog.csdn.net/u011583927/…

blog.csdn.net/cbacq/artic…

(注:若有什麼地方闡述有誤,敬請指正。)

相關文章