SM4国密算法实现分析

SM4国密算法实现分析

代码下载请见 上一篇文章 AES算法实现分析

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

算法调用参数

该算法需要一个结构体 sm4_context ctx 来保存上下文信息,即加密模式和各轮子密钥。

该结构体定义如下:

typedef struct
{
    int mode;                   /*!< encrypt/decrypt */
    unsigned long sk[32];       /*!< 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[32], unsigned char key[16] )
{
    unsigned long MK[4];
    unsigned long k[36];
    unsigned long i = 0;

    GET_ULONG_BE( MK[0], key, 0 );
    GET_ULONG_BE( MK[1], key, 4 );
    GET_ULONG_BE( MK[2], key, 8 );
    GET_ULONG_BE( MK[3], key, 12 );
    k[0] = MK[0]^FK[0];
    k[1] = MK[1]^FK[1];
    k[2] = MK[2]^FK[2];
    k[3] = MK[3]^FK[3];
    for(; i<32; i++)
    {
        k[i+4] = k[i] ^ (sm4CalciRK(k[i+1]^k[i+2]^k[i+3]^CK[i]));
        SK[i] = k[i+4];
    }
}

类似于加密中的操作,首先通过宏将初始的密钥转换为4个32位bit整数,MK0,MK1,MK2,MK3,并为计算各轮密钥预先准备好初始值,其中FK数组为系统参数:

    k[0] = MK[0]^FK[0];
    k[1] = MK[1]^FK[1];
    k[2] = MK[2]^FK[2];
    k[3] = MK[3]^FK[3];

此后,对于第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 = 0;
    unsigned long rk = 0;
    unsigned char a[4];
    unsigned char b[4];
    PUT_ULONG_BE(ka,a,0)
    b[0] = sm4Sbox(a[0]);
    b[1] = sm4Sbox(a[1]);
    b[2] = sm4Sbox(a[2]);
    b[3] = sm4Sbox(a[3]);
    GET_ULONG_BE(bb,b,0)
    rk = bb^(ROTL(bb, 13))^(ROTL(bb, 23));
    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[32],
                    unsigned char input[16],
                    unsigned char output[16] )
{
    unsigned long i = 0;
    unsigned long ulbuf[36];

    memset(ulbuf, 0, sizeof(ulbuf));
    GET_ULONG_BE( ulbuf[0], input, 0 )
    GET_ULONG_BE( ulbuf[1], input, 4 )
    GET_ULONG_BE( ulbuf[2], input, 8 )
    GET_ULONG_BE( ulbuf[3], input, 12 )
    while(i<32)
    {
        ulbuf[i+4] = sm4F(ulbuf[i], ulbuf[i+1], ulbuf[i+2], ulbuf[i+3], 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[35],output,0);
    PUT_ULONG_BE(ulbuf[34],output,4);
    PUT_ULONG_BE(ulbuf[33],output,8);
    PUT_ULONG_BE(ulbuf[32],output,12);
}

函数中使用了两个宏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)    ] << 24 )        \
        | ( (unsigned long) (b)[(i) + 1] << 16 )        \
        | ( (unsigned long) (b)[(i) + 2] <<  8 )        \
        | ( (unsigned long) (b)[(i) + 3]       );       \
}

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) >> 24 );       \
    (b)[(i) + 1] = (unsigned char) ( (n) >> 16 );       \
    (b)[(i) + 2] = (unsigned char) ( (n) >>  8 );       \
    (b)[(i) + 3] = (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 = 0;
    unsigned long c = 0;
    unsigned char a[4];
    unsigned char b[4];
    PUT_ULONG_BE(ka,a,0)
    b[0] = sm4Sbox(a[0]);
    b[1] = sm4Sbox(a[1]);
    b[2] = sm4Sbox(a[2]);
    b[3] = sm4Sbox(a[3]);
    GET_ULONG_BE(bb,b,0)
    c =bb^(ROTL(bb, 2))^(ROTL(bb, 10))^(ROTL(bb, 18))^(ROTL(bb, 24));
    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的解密变换与加密变换结构相同,不同的仅仅是轮密钥的使用顺序相反。

你可能感兴趣的:(算法,加密,sm4)