天天看點

SM4國密算法實作分析

SM4國密算法實作分析

代碼下載下傳請見 上一篇文章 AES算法實作分析

SM4的說明(pdf):http://download.csdn.net/detail/leechiyang/5008528

算法調用參數

該算法需要一個結構體 sm4_context ctx 來儲存上下文資訊,即加密模式和各輪子密鑰。

該結構體定義如下:

typedef struct
{
    int mode;                   /*!<  encrypt/decrypt   */
    unsigned long sk[];       /*!<  SM4 subkeys       */
}
sm4_context;
           

加密

密鑰排程算法

首先調用sm4_setkey_enc(&ctx,key)設定密鑰,這個函數會設定mode為加密,并調用static void sm4_setkey( unsigned long SK[32], unsigned char key[16] )來完成設定密鑰的操作:

static void sm4_setkey( unsigned long SK[], unsigned char key[] )
{
    unsigned long MK[];
    unsigned long k[];
    unsigned long i = ;

    GET_ULONG_BE( MK[], key,  );
    GET_ULONG_BE( MK[], key,  );
    GET_ULONG_BE( MK[], key,  );
    GET_ULONG_BE( MK[], key,  );
    k[] = MK[]^FK[];
    k[] = MK[]^FK[];
    k[] = MK[]^FK[];
    k[] = MK[]^FK[];
    for(; i<; i++)
    {
        k[i+] = k[i] ^ (sm4CalciRK(k[i+]^k[i+]^k[i+]^CK[i]));
        SK[i] = k[i+];
    }
}
           

類似于加密中的操作,首先通過宏将初始的密鑰轉換為4個32位bit整數,MK0,MK1,MK2,MK3,并為計算各輪密鑰預先準備好初始值,其中FK數組為系統參數:

k[] = MK[]^FK[];
    k[] = MK[]^FK[];
    k[] = MK[]^FK[];
    k[] = MK[]^FK[];
           

此後,對于第i輪的密鑰SK[i] ,其是由k[i]和對k[i+1]^k[i+2]^k[i+3]^CK[i]的複合變換T’異或得到的:

SK[i] = k[i+4] = k[i] ^ (sm4CalciRK(k[i+1]^k[i+2]^k[i+3]^CK[i]))

其中CK是固定參數,雖然代碼中直接給出了CK,實際上,其是有一定的計算方法的:設CKij為CKi的第j位元組,即CKi=(cki0, cki1, cki2, cki3),則ckij=(4i+j)*7(mod 256)。

函數sm4CalciRK,也就是變換T’,與加密輪函數中的T基本相同,同樣是先進行Sbox的非線性替換,然後進行線性變換,隻是線性變換L改為了:

rk = bb^(ROTL(bb, 13))^(ROTL(bb, 23));

static unsigned long sm4CalciRK(unsigned long ka)
{
    unsigned long bb = ;
    unsigned long rk = ;
    unsigned char a[];
    unsigned char b[];
    PUT_ULONG_BE(ka,a,)
    b[] = sm4Sbox(a[]);
    b[] = sm4Sbox(a[]);
    b[] = sm4Sbox(a[]);
    b[] = sm4Sbox(a[]);
    GET_ULONG_BE(bb,b,)
    rk = bb^(ROTL(bb, ))^(ROTL(bb, ));
    return rk;
}
           

加密過程

通過調用void sm4_crypt_ecb( sm4_context *ctx,int mode,int length,unsigned char *input,unsigned char *output)對密文input進行電碼本模式的加密,加密的核心是調用了 對每一塊密文進行加密:static void sm4_one_round( unsigned long sk[32], unsigned char input[16], unsigned char output[16]):

static void sm4_one_round( unsigned long sk[],
                    unsigned char input[],
                    unsigned char output[] )
{
    unsigned long i = ;
    unsigned long ulbuf[];

    memset(ulbuf, , sizeof(ulbuf));
    GET_ULONG_BE( ulbuf[], input,  )
    GET_ULONG_BE( ulbuf[], input,  )
    GET_ULONG_BE( ulbuf[], input,  )
    GET_ULONG_BE( ulbuf[], input,  )
    while(i<)
    {
        ulbuf[i+] = sm4F(ulbuf[i], ulbuf[i+], ulbuf[i+], ulbuf[i+], sk[i]);
// #ifdef _DEBUG
//          printf("rk(%02d) = 0x%08x,  X(%02d) = 0x%08x \n",i,sk[i], i, ulbuf[i+4] );
// #endif
        i++;
    }
    PUT_ULONG_BE(ulbuf[],output,);
    PUT_ULONG_BE(ulbuf[],output,);
    PUT_ULONG_BE(ulbuf[],output,);
    PUT_ULONG_BE(ulbuf[],output,);
}
           

函數中使用了兩個宏GET_ULONG_BE(n,b,i)和PUT_ULONG_BE(n,b,i),作用分别為:

GET_ULONG_BE(n,b,i):将字元型數組b的第i到第i+3位的二進制拼接成一個4*8=32bit的整數,存入n中

#define GET_ULONG_BE(n,b,i)                             \
{                                                       \
    (n) = ( (unsigned long) (b)[(i)    ] <<  )        \
        | ( (unsigned long) (b)[(i) + ] <<  )        \
        | ( (unsigned long) (b)[(i) + ] <<   )        \
        | ( (unsigned long) (b)[(i) + ]       );       \
}
           

PUT_ULONG_BE(n,b,i):将整數n的32位的二進制表示轉換為4個char的數組,存入數組b的第i到第i+3位

#define PUT_ULONG_BE(n,b,i)                             \
{                                                       \
    (b)[(i)    ] = (unsigned char) ( (n) >>  );       \
    (b)[(i) + ] = (unsigned char) ( (n) >>  );       \
    (b)[(i) + ] = (unsigned char) ( (n) >>   );       \
    (b)[(i) + ] = (unsigned char) ( (n)       );       \
}
           

是以,在函數sm4_one_round()中,先将128位的輸入input轉為四個32位的整數,放入ulbuf[4]中,然後疊代地調用函數static unsigned long sm4F(unsigned long x0, unsigned long x1, unsigned long x2, unsigned long x3, unsigned long rk) 進行32輪加密,每一輪加密都需要使用之前的128位結果 ulbuf[i], ulbuf[i+1], ulbuf[i+2], ulbuf[i+3] 和該輪的密鑰 sk[i],産生出該輪的密文 ulbuf[i+4],最後的密文存儲在ulbuf[35]~ulbuf[32]中,轉換為字元數組的形式放入output中。

一輪加密static unsigned long sm4F(unsigned long x0, unsigned long x1, unsigned long x2, unsigned long x3, unsigned long rk)

static unsigned long sm4F(unsigned long x0, unsigned long x1, unsigned long x2, unsigned long x3, unsigned long rk)
{
    return (x0^sm4Lt(x1^x2^x3^rk));
}
           

每一輪加密中,輸入為(x0, x1, x2, x3),xi為32位比特,共計128比特。通過

x0^sm4Lt(x1^x2^x3^rk)

得到該輪加密的結果。在此時,SM4就将輪密鑰應用在了加密中。

sm4Lt()是一個合成變換,由非線性變換t和線性變換L複合而成:

首先将輸入的整數ka轉換為8比特一個的字元

PUT_ULONG_BE(ka,a,0)

,然後使用S盒進行非線性變換,再将變換結果轉換為32比特的整數

GET_ULONG_BE(bb,b,0)

,最後對得到的32位整數bb進行線性變換:

c=bb異或(bb<<<2)異或(bb<<<10)異或(bb<<<18)異或(bb<<<24)。進而得到複合變換的結果c。

static unsigned long sm4Lt(unsigned long ka)
{
    unsigned long bb = ;
    unsigned long c = ;
    unsigned char a[];
    unsigned char b[];
    PUT_ULONG_BE(ka,a,)
    b[] = sm4Sbox(a[]);
    b[] = sm4Sbox(a[]);
    b[] = sm4Sbox(a[]);
    b[] = sm4Sbox(a[]);
    GET_ULONG_BE(bb,b,)
    c =bb^(ROTL(bb, ))^(ROTL(bb, ))^(ROTL(bb, ))^(ROTL(bb, ));
    return c;
}
           

算法中對循環移位的實作較為巧妙:

#define  SHL(x,n) (((x) & 0xFFFFFFFF) << n)
#define ROTL(x,n) (SHL((x),n) | ((x) >> (32 - n)))
           

SHL(x,n)可以得到左移n位之後的結果,然後與右移的結果((x) >> (32 - n))逐位或來将右邊空缺的n位補齊,效率比較高。

加密過程小結

至此,sm4算法的加密過程就分析完了。其加密過程與DES算法相似,每一輪中先使用sbox進行非線性變換,然後再通過循環移位操作進行線性變換。其每輪加密用到了之前四輪加密的結果,進一步提高了加密的強度。

解密過程

解密前,首先要通過void sm4_setkey_dec( sm4_context *ctx, unsigned char key[16] )函數設定解密時使用的key,這個函數還會将密鑰的順序倒置,然後調用sm4_crypt_ecb()即可解密。

實際上,SM4的解密變換與加密變換結構相同,不同的僅僅是輪密鑰的使用順序相反。

繼續閱讀