代码下载请见 上一篇文章 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)
{
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的解密变换与加密变换结构相同,不同的仅仅是轮密钥的使用顺序相反。