一、前言

在之前的文章中介绍了《SM3国密杂凑值算法的原理和c语言实现》,这篇文章主要是用c语言撸一个SM4分组加密算法。

随着信息安全的普及以及等级保护等安全政策落地,国密算法越来越频繁的出现在项目开发中,在较新的一些openssl版本中已经有了SM2、SM3、SM4等国密接口,还有Gmssl等开源加密库也均对国密算法进行了支持。

不过在一些底层的开发中,尤其涉及到内核驱动层开发、TCM等密码卡开发,现有的一些加密库由于过多的依赖,是无法直接使用的,这时候恐怕就得要求开发人员对国密接口进行重新实现。

二、SM4分组加密算法

常见的分组加密算法有DES、3DES、AES、SM4等,该类算法的一个特点就是对称加密,既解密过程所使用的密钥与加密过程所使用的密钥是同一个,或者对加密密钥做一些简单的数学换算得到。

使用分组加密算法加密数据之后,信息数据的保护就变成了密钥的保护,谁得到了密钥谁就可以破解密文数据。

SM4分组加密算法可以按照字面意思理解,如果有一批数据需要进行加密,首先需要对数据分成若干小组,每次加密其实处理(经过32轮的非线性迭代运算)的是单个小组中的数据,然后将所有分组的密文放一起就是整个密文了。

SM4分组加密算法的每个分组大小是128bit(16字节),所用的密钥长度也是128bit(16字节),最后生成的密文长度也是128bit(16字节)。我们可以直接认为SM4就是对16字节的数据进行加密,加密之后生成16字节的密文数据。

如果遇到某个分组数据不足16字节怎么办?

那么我们可以填充特定字符串至满16字节为止,只要解密方知道你填充了多少个字节的无用数据,或者知道你填充的特定字符串是什么,解密之后直接去掉就可以了。具体填充的数据可以根据开发人员个人喜好去确定,无实际意义。

三、加解密流程

SM4分组加密算法的详细流程可参照 GM/T0002-2012《SM4分组密码算法》中所述。

1、密钥扩展过程

密钥扩展主要是为了得到轮密钥,而整个加密和解密过程使用的就是轮密钥,轮密钥是一个类似rk[32]数组的形式,每一个元素为32bit(4字节)。

(1)SM4所用密钥ekey长度为16字节,既128bit,首先创建MK[4](单个元素长度为32bit,可当成int型数值),将16字节的ekey放入MK数组中,需要注意的是,要转化为大端格式。

(2)然后可以创建一个K[36]数组(单个元素长度为32bit,可当成int型数值),K数组的0~3下标元素直接从对应下标的MK与对应下标的FK异或得到。FK为系统参数,是一个FK[4]数组类型的格式,每个元素32bit。

而K数组对应的4~35下标的元素则通过K[i+4]  = k[i] ^ T' (K[i+1] ^ K[i+2] ^ k[i+3])获得,同样就可到了对应下标的rk值。

(3)T'是置换函数,置换函数是非线性变换函数和一个线性变换函数合成的。其中非线性变换函数用到了Sbox,可以将Sbox理解为一个16乘16的二维数组,每个元素为8bit。既每个字节的数据(高4位得到行号,低4位可以得到列号)可以通过查看Sbox表变成新的字节。

2、加解密过程

加密过程中,明文数据128bit,既16字节,可以用data[16]来表示。在加密过程中首先要创建一个X[36]的数组,每个元素32bit。和密钥扩展过程类似,真实的data数组填充到X数组的0~3的下标中,赋值过程需要转换为大端。

X其他4~35的下标数据,需要根据 X[i+4] = X[i] ^ T ( X[i+1] ^ X[i+2] ^ X[i+3] ^ rk[i])得到。经过32轮的迭代计算,最后X[35]~X[32]的4个元素既最后的密文结果。

解密过程与加密过程是一致的,只不过解密过程中的源数据是密文数据,目标数据是明文数据,而且所用到的轮密钥rk需要逆序过来。

四、c语言实现

lk_sm4.h头文件,宏定义方式定义了基本密钥扩展接口、循环左移接口,大小端转换接口,置换函数,线性迭代函数,非线性迭代函数,加密接口以及解密接口等。

#ifndef __lk_sm4_h__
#define __lk_sm4_h__#ifdef __cpluscplus
extern "C" {
#endif#include <strings.h>
#include <string.h>#ifdef __cpluscplus
}
#endif#define LK_WORD_SIZE        32
#define LK_GCHAR_SIZE       16
#define LK_GWORD_SIZE       4 typedef void                LK_VOID;
typedef int                 LK_INT;
typedef unsigned int        UINT;
#ifdef i386
typedef unsigned long long   UWORD;
#else
typedef unsigned long       UWORD;
#endif
typedef unsigned char   UCHAR;//大端转化
#define LK_GE(c, i) (\(c[(i)+0] << 24) | ((c[(i)+1] << 16)) |\(c[(i)+2] << 8) | (c[(i)+3]) )
//小端转化
#define LK_LE(c, n, i) {\c[(i)+0] = ((n) >> 24) & 0x000000ff;\c[(i)+1] = ((n) >> 16) & 0x000000ff;\c[(i)+2] = ((n) >> 8) & 0x000000ff;\c[(i)+3] = (n) & 0x000000ff; }
//循环左移
#define LOOPSHFT(LK_LSHIFA, LK_LSHIFN) (\((LK_LSHIFA) << (LK_LSHIFN)) | \((LK_LSHIFA) >> (LK_WORD_SIZE - (LK_LSHIFN))))//线性变换函数L' L'(B)=B^(B<<<13)^(B<<<23);
#define LK_L0(LK_L0B) (\(LK_L0B)^(LOOPSHFT((LK_L0B), 13))^(LOOPSHFT((LK_L0B), 23)) )//线性变换函数L L(B) = B^(B<<<2)^(B<<<10)^(B<<<18)^(B<<<24)
#define LK_L1(LK_L1B) (\(LK_L1B)^(LOOPSHFT((LK_L1B), 2))^(LOOPSHFT((LK_L1B), 10))\^(LOOPSHFT((LK_L1B), 18))^(LOOPSHFT((LK_L1B), 24)) )//非线性变换 τ函数 (b0 , b1, b2 , b3 ) = τ (A) = ( Sbox (a0 ), Sbox (a1 ), Sbox (a2 ), Sbox (a3 ) )
#define LK_ST(LK_STA) ({\LK_INT LK_i;\UINT LK_STB = LK_STA;\UCHAR *pcr = (UCHAR *)(&LK_STB);\for (LK_i = 0; LK_i < 4; LK_i++) {\UCHAR high_bval = ((pcr[LK_i] >> 4) & 0x0f);\UCHAR low_bval = (pcr[LK_i] & 0x0f);\*(pcr+LK_i) = lk_sbox[high_bval][low_bval];\}\LK_STB;\
})//合成置换 T  T(.)=L(τ(.))
#define LK_T1(LK_TA) (\LK_L1(LK_ST(LK_TA)) )//合成置换 T'  T'(.)=L'(τ(.))
#define LK_T0(LK_TA) (\LK_L0(LK_ST(LK_TA)) )//密钥扩展算法生成轮密钥
#define LK_SM4_INIT(lk_context, ekey) {\UINT k[36], mk[4];\LK_INT i;\lk_sm4_context_t *t = (lk_sm4_context_t *)(lk_context);\bzero(t, sizeof(lk_sm4_context_t));\for (i = 0; i < 4; i++) {\mk[i] = LK_GE(ekey, i * 4);\k[i] = mk[i]^lk_fk[i];\}\for (i = 0; i < 32; i++) {\k[i+4] = (k[i] ^ LK_T0(k[i+1]^k[i+2]^k[i+3]^(lk_ck[i])));\t->e_rk[i] = k[i+4];\t->d_rk[31 - i] = t->e_rk[i];\}\
}//加密函数
#define LK_SM4_ENC_F(lk_context) {\LK_INT i;\UINT x[36];\lk_sm4_context_t *t = (lk_sm4_context_t *)(lk_context);\for (i = 0; i < 4; i++) {\x[i] = LK_GE(t->buf, i * 4);\}\for (i = 0; i < 32; i++) {\x[i + 4] = x[i]^(LK_T1(x[i+1]^x[i+2]^x[i+3]^t->e_rk[i]));\}\LK_LE(t->ebuf, x[35], 0)\LK_LE(t->ebuf, x[34], 4)\LK_LE(t->ebuf, x[33], 8)\LK_LE(t->ebuf, x[32], 12)\
}
//解密函数
#define LK_SM4_DEC_F(lk_context) {\LK_INT i;\UINT x[36];\lk_sm4_context_t *t = (lk_sm4_context_t *)(lk_context);\for (i = 0; i < 4; i++) {\x[i] = LK_GE(t->ebuf, i * 4);\}\for (i = 0; i < 32; i++) {\x[i + 4] = x[i]^LK_T1(x[i+1]^x[i+2]^x[i+3]^t->d_rk[i]);\}\LK_LE(t->buf, x[35], 0)\LK_LE(t->buf, x[34], 4)\LK_LE(t->buf, x[33], 8)\LK_LE(t->buf, x[32], 12)\
}typedef struct lk_sm4_context_s
{//需要加密的明文,加密之前需要填充UCHAR    buf[LK_GCHAR_SIZE];//加密之后的密文,如果是解密的话,解密之前需要填充UCHAR    ebuf[LK_GCHAR_SIZE];//原密钥经过扩展之后的轮密钥,初始化接口自行填充UINT    e_rk[LK_WORD_SIZE];//解密过程需要的轮密钥,初始化接口自行填充UINT    d_rk[LK_WORD_SIZE];
} lk_sm4_context_t;
#ifdef __cpluscplus
extern "C" {
#endifextern LK_VOID lk_sm4_enc(LK_VOID *p_context);
extern LK_VOID lk_sm4_dec(LK_VOID *p_context);#ifdef __cpluscplus
}
#endif#endif

lk_sm4.c文件,定义了Sbox、FK以及CK等数组,同时也定义了lk_sm4_enc加密接口和lk_sm4_dec解密接口,加解密函数具体的实现在头文件中。

#include <stdio.h>
#include "lk_sm4.h"
//非线性转化用到的sbox对照表
UCHAR lk_sbox[LK_GCHAR_SIZE][LK_GCHAR_SIZE] =  {{0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05},{0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99},{0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62},{0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6},{0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8},{0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35},{0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87},{0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e},{0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1},{0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3},{0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f},{0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51},{0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8},{0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0},{0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84},{0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48}};
//系统参数FK
UINT lk_fk[4] = { 0xA3B1BAC6,0x56AA3350,0x677D9197,0xB27022DC
};
//固定参数CK
UINT lk_ck[32] = { 0x00070e15,0x1c232a31,0x383f464d,0x545b6269,0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,0x30373e45,0x4c535a61,0x686f767d,0x848b9299,0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,0x10171e25,0x2c333a41,0x484f565d,0x646b7279
};LK_VOID lk_sm4_init(LK_VOID *p_context, UCHAR *ekey)
{//进行密钥扩展,生成轮转密钥rkLK_SM4_INIT(p_context, ekey)
}LK_VOID lk_sm4_enc(LK_VOID *p_context)
{LK_SM4_ENC_F(p_context)
}LK_VOID lk_sm4_dec(LK_VOID *p_context)
{LK_SM4_DEC_F(p_context)
}

test.c是测试例程,具体测试数据参照了SM3分组加密算法中的例子程序。

测试代码:

#include <stdio.h>
#include "lk_sm4.h"LK_INT main(LK_VOID)
{LK_INT i;lk_sm4_context_t context;UCHAR ekey[16] = { 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10 };UCHAR data[16] = { 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10 };//初始化lk_sm4_init(&context, ekey);//待加密数据填充memcpy(context.buf, data, sizeof(data));//对数据进行加密lk_sm4_enc(&context);//打印密文值printf("enc data:");for (i = 0; i < LK_GCHAR_SIZE; i++)printf(" %02x ", context.ebuf[i]); printf("\n");//对数据进行解密memset(context.buf, 0x0, sizeof(context.buf));lk_sm4_dec(&context); //打印解密数据printf("dec data:");for (i = 0; i < LK_GCHAR_SIZE; i++)printf(" %02x ", context.buf[i]); printf("\n");return 0;
}

执行结果:

五、关于填充

在上面提到的测试例子中,对于分组加密的数据,如果最后的一组不足16字节,其实没有进行填充,也可以理解为填充了0,当然也可以按照自己的规则去随意填充,解密的时候只要能去掉自己的规则就可以。

在一些已有的SM4的实现库中,一般采用了PKCS#7填充方式:

1、最后一组待加密数据不足16字节,比如还差一个字节则填充0x1,还差5个字节则填充5个0x5,还差10个字节则填充10个0xA......填充值跟待填充字节数是一致的。

2、如果待加密数据的最后一个分组正好也是16字节,则补位16个字节的0xF。

SM4分组加密算法原理和c语言实现相关推荐

  1. 【数据结构与算法】程序员们常用的10个关键数据结构,包括它们的原理和C语言实现代码

    [数据结构与算法]程序员们常用的10个关键数据结构,包括它们的原理和C语言实现代码 文章目录 [数据结构与算法]程序员们常用的10个关键数据结构,包括它们的原理和C语言实现代码 1. 数组 (Arra ...

  2. (ECC)椭圆曲线加密算法原理和C++实现源码

    目录 (1)ECC加密原理: (2)编译生成LibTommath静态库 (3)ECC源码 今天介绍一下利用LibTommath数学库实现椭圆曲线加密算法的原理和源码. (1)ECC加密原理: 1.用户 ...

  3. SM3国密杂凑值算法的原理和c语言实现

    一.SM3算法介绍 杂凑值算法也可称为摘要算法或者哈希算法.通过对数据资料的填充.分组.扩展压缩等方式计算成特定长度的数值,来作为数据指纹或者数据特征使用.常见的MD5算法长度为128bit(16字节 ...

  4. RSA加密算法原理和java简单实现

    数学 RSA加密算法中,用到素数.互质数.指数运算.模运算等几个数学知识. 素数 素数又称质数,指在一个大于1的自然数中,除了1和此整数自身外,不能被其他自然数整除的数. 互质数 百度百科:公因数只有 ...

  5. r语言聚类分析_图说层次聚类分析原理和R语言实现

    1.引言 "物以类聚.人以群分".但我们面对一群人或者一堆物的时候,我们都希望将他们分分类,分类之后,我们才能更加有针对性地采取措施,从而提高工作效率. 如,我们将消费者分成若干类 ...

  6. MPEG音频编码 基本原理和C语言代码分析

    背景 MPEG(Moving Picture Experts Group)在汉语中译为活动图像专家组,特指活动影音压缩标准. MPEG 音频文件是MPEG1 标准中的声音部分,也叫MPEG 音频层,它 ...

  7. 【池化技术】内存池技术原理和C语言实现

    文章目录 一.基础概念 1.一个可执行程序占用的内存分为哪几个区?一个进程的虚拟内存区域有哪些? 2.静态内存分配和动态内存分配 二.malloc实现原理 malloc内存分配(下面算是正常一般的情况 ...

  8. 导入表注入原理和C语言实现

    一.导入表注入的原理 注入是把DLL加载到另一个进程的4GB地址空间中,实现方式有很多种,导入表注入是我学的第一种注入,是通过修改程序的导入表,把自己的DLL添加到导入表中,来实现这个目的. 导入表是 ...

  9. 北京工业大学微机原理和c语言,北京工业大学 微机原理 实验九

    实验报告 实验九数码转换 一.实验目的 1.掌握计算机常用数据编码之间的相互转换方法. 2.进一步熟悉DEBUG软件的使用方法. 二.实验内容 1. ACSII码转换为非压缩型BCD码 编写并调试正确 ...

最新文章

  1. kali win10双系统_kali 双系统+实体机(win10+kali)
  2. 阿里云提示WordPress“/wp-includes/http.php输入IP验证不当”的解决办法
  3. delphi程序设计之底层原理
  4. OSG使用更新回调来更改模型
  5. 18.04升级到19.10
  6. Vert.x入坑须知(2)
  7. 我的世界有宠物系统的服务器,我的世界1.6.4宠物战争服务器客户端
  8. Nacos发布0.5.0版本,轻松玩转动态 DNS 服务
  9. install cuda5 on ubuntu12.04
  10. 案例:监听域对象的生命周期
  11. 安卓手机ip修改器_亚马逊手机端?电脑端?谁是测评一哥
  12. 使用电脑开发的,连个黑屏休眠都不会设置?
  13. linux编译trinitycore,TC编译步骤之二代码安装
  14. 200多个电脑修复工具问你要不要?
  15. 深圳-上海-呼伦贝尔-漠河-哈尔滨环行手记
  16. 计算机flash听课记录范文,听课记录范文.doc
  17. 『天涯杂谈』十大古今名人语录经典(2007版)
  18. python基本常用语法函数数据结构
  19. 酷狗免费下载歌曲(网易云同样适用)
  20. sqlplus 为什么会自动断开_为什么设置的手机铃声会自动变为手机默认的,原因在这里...

热门文章

  1. linpus linux 换win7,联想 lenovoIdeaPad可以装windows7系统吗_联想 lenovoIdeaPad怎么安装win7系统-win7之家...
  2. 【docker】Docker容器的两种运行模式(Foreground、Detached)
  3. 电脑录屏怎么录制影视片段视频
  4. jsplumb拖线_基于 jsPlumb 的流程图编辑器的实现 (二,连接线的操作)
  5. Vue中禁止输入表情符号指令
  6. 云原生之docker详解
  7. MySQL卸载以及重装
  8. hdmi接口有什么用_hdmi是什么接口,hdmi接口有什么作用
  9. justify-content、align-content、align-items、align-self的区别
  10. 2020互联互通社区中秋国庆双节盛典