您的位置:首页 > 编程语言 > C语言/C++

C语言实现数据保护算法(2)——AES加解密

2017-09-02 10:59 756 查看
上一篇文章我简单的学习了des相关的加解密http://blog.csdn.net/u011502387/article/details/77570078;这篇文章来学习下DES的大兄弟AES加解密算法,和DES算法一样,两者均采用c语言实现,在VC6.0编译通过(文章末尾附带VC6.0
源码)。

初始AES:

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。(百度)

AES 是一个新的可以用于保护电子数据的加密算法。明确地说,AES 是一个迭代的、对称密钥分组的密码,它可以使用128、192 和 256 位密钥,并且用 128 位(16字节)分组加密和解密数据。与公共密钥密码使用密钥对不同,对称密钥密码使用相同的密钥加密和解密数据。通过分组密码返回的加密数据 的位数与输入数据相同。迭代加密使用一个循环结构,在该循环中重复置换(permutations )和替换(substitutions)输入数据。下图为网上引用的AES的结构图。



对于三种秘钥长度不同的AES算法的参数如下:



AES运算过程中主要包括以下四个过程:

轮密钥加(AddRoundKey):当前分组和扩展密钥的一部分进行按位XOR(异或)。
字节代替(SubBytes): 用一个S盒完成分组的字节到字节的代替。
行移位(ShiftRows):一个简单的置换。
列混淆(MixColumns):利用域GF(28)上的算术特性的一个代替

下面是128bit的加解密流程图:



具体的流程理解可以参照国外AES的演示动画:AES动画演示,下面详细介绍下各个环节。

轮密钥加:

在正向轮密钥加中,128位的state按位与128位的轮密钥XOR。 逆向轮密钥加变换是和正向轮密钥加变换一样的,因为异或操作是其本身的逆。(A⊕B⊕B = A);实现代码如下:

/****************************************************************************
Description:           数组异或(add_round_key) 轮密钥加
Input parameters:
uint8_t state:需要求异或的数组1
const uint8_t key : 需要求异或的数组2
Output parameters:
uint8_t state:求异或后的结果

Returned value:
NULL
Created by:
崔龙龙 (2017-09-01)
Modified by:
NULL
****************************************************************************/
static void add_round_key(uint8_t state[BLOCK_SIZE], const uint8_t key[BLOCK_SIZE])
{
int i;

assert(state != NULL);//对参数校验
assert(key != NULL);

for (i = 0; i < BLOCK_SIZE; ++i)
state[i] ^= key[i];
}
读了上面两句话很多人就疑惑什么是轮密钥啊,下面就是看下轮密钥是怎么生成的:
1) 将初始密钥以列为主,转化为4个32 bits的字,分别记为k[0…3];
2) 按照如下方式,依次求解k[j],其中j是整数并且属于[4,43]
3) 若j%4=0,则w[j]=w[j-4]⊕f(w[j-1]),否则w[j]=w[j-4]⊕w[j-1];

函数f的流程说明:
1) 将k循环左移一个字节;
2) 分别对每个字节按S盒进行映射;
3) 与32 bits的常量(RCON/4],0,0,0)进行异或,RCON个一维数组,其值如下。

(RCON = {00, 01, 02, 04, 08, 10, 20, 40, 80, 1B, 36},具体如下流程图:









密钥扩展具体代码如下:

/****************************************************************************
Description:           处理密钥(key_expansion)
Input parameters:
aes_context *ctx :  处理所需的参数(秘钥长度)
const uint8_t *key:密钥
Output parameters:
aes_context *ctx :  储存转换数据的结构体

Returned value:
NULL
Created by:
崔龙龙 (2017-09-01)
Modified by:
NULL
****************************************************************************/
static void key_expansion(aes_context *ctx, const uint8_t *key)
{
uint32_t Nk = ctx->nr - 6;
uint32_t Ek = (ctx->nr+1)<<2;
uint32_t *RK = ctx->rk;
uint32_t i = 0;

assert(ctx != NULL);
assert(key != NULL);//参数校验

for(i = 0;i<68;i++)
RK[i] = 0;

i = 0;
do
{
GET_UINT32(RK[i], key, i<<2);//把4位uchar数据移位成一位uint数据
} while(++i < Nk);
do
{
uint32_t t = RK[i-1];
if
4000
((i % Nk) == 0)
t = SUB_WORD(ROTL8(t))^RCON[i/Nk -1];//先替换再异或
else if (Nk == 8 && (i % Nk) == 4)
t = SUB_WORD(t);//直接s盒替换
RK[i] = RK[i-Nk]^t;
} while(++i < Ek);
}


字节代替:

该操作有正向和逆向变换。该步骤简单说就是一个简单的查表操作。把该字节的高4位作为行值,低4位作为列值,以这些行列值作为索引从S盒中对应位置取出元素作为输出。例如,十六进制数{95}所对应的S盒的行值是9,列值是5,S盒中在此位置的值是{2A},相应的{95}被映射为{2A}。下图分别为s盒和逆s盒。





具体的实现代码如下:

/****************************************************************************
Description:          S盒字节替换(_sub_bytes)
Input parameters:
uint8_t state:需要替换的数组
cuint8_t *BOX : s盒数组
Output parameters:
uint8_t state:替换后的数组

Returned value:
NULL
Created by:
崔龙龙 (2017-09-01)
Modified by:
NULL
****************************************************************************/
static void _sub_bytes(uint8_t state[BLOCK_SIZE], const uint8_t *BOX)
{
int i;

assert(state != NULL);//对参数校验
assert(BOX != NULL);

for (i = 0; i < BLOCK_SIZE; ++i)
state[i] = BOX[state[i]];
}


行移位:

行移位的功能是实现一个4x4矩阵内部字节之间的置换。
正向移位的实际操作:第一行保存不变,第二行循环左移1个字节,第三行循环左移2个字节,第四行循环左移3个字节。假设矩阵的名字为state,用公式表示如下:state’[i][j] = state[i][(j+i)%4];其中i、j属于[0,3]。具体的如下图:



逆向行移位即是相反的操作,用公式表示如下:state’[i][j] = state[i][(4+j-i)%4];其中i、j属于[0,3]

具体的代码实现:

/****************************************************************************
Description:          行移位处理函数(transport)
Input parameters:
uint8_t state:需要行移位的数组
Output parameters:
uint8_t state:行移位的结果

Returned value:
NULL
Created by:
崔龙龙 (2017-09-01)
Modified by:
NULL
****************************************************************************/
static void transport(uint8_t state[BLOCK_SIZE])
{
uint8_t _state[4][4];
int r,c;

assert(state != NULL);//对参数校验

for (r = 0; r < 4; ++r)
for (c = 0; c < 4; ++c)
_state[r][c] = state[(c<<2)+r];
memcpy(state, _state, sizeof(_state));
}


列混淆:

该操作是利用GF(28)域上算术特性的一个代替。通俗点说就是用一个常矩阵乘以上述步骤变换后的矩阵,以达到矩阵中每一个元素都是该元素原所在列所有元素的加权和。正向列混淆的原理图如下:



逆向列混淆的原理图如下:



由于上面两个常矩阵互逆,经过一次逆向列混淆后即可恢复原文。

具体的代码实现:
/****************************************************************************
Description:          4*4矩阵的乘法(_mix_columns)
Input parameters:
uint8_t state:矩阵1
uint8_t matrix: 矩阵2
Output parameters:
uint8_t state:结果

Returned value:
NULL
Created by:
崔龙龙 (2017-09-01)
Modified by:
NULL
****************************************************************************/
static void _mix_columns(uint8_t state[BLOCK_SIZE], const uint8_t matrix[][4])
{
uint8_t _state[BLOCK_SIZE] = {0};
int r,c,i;

assert(state != NULL);//对参数校验
assert(matrix != NULL);

for (r = 0; r < 4; ++r)
for (c = 0; c < 4; ++c)
for (i = 0; i < 4; ++i)
_state[(c<<2)+r] ^= GF_256_multiply(matrix[r][i], state[(c<<2)+i]);
memcpy(state, _state, sizeof(_state));
}
到此整个AES的操作流程就结束了,下面介绍我在VC 6.0环境下测试的源码。
/****************************************************************************
Description:
主函数,测试AES(main)
Created by:
崔龙龙 (2017-09-01)
Modified by:
NULL
****************************************************************************/
int main()
{
int i;
aes_context ctx;
uint8_t ret_text[16] = {0};
uint8_t cipher_text[16] = {0};
uint32_t key_bit[3] = {128, 192, 256};
uint8_t text[16] = {//明文
0x0f,0x1e,0x2d,0x3c,
0x4b,0x5a,0x69,0x78,
0x87,0x96,0xa5,0xb4,
0xc3,0xd2,0xe1,0xf0
};
uint8_t key[32] = {//测试密钥
0x01,0x23,0x45,0x67,
0x89,0xab,0xcd,0xef,
0xef,0xcd,0xab,0x89,
0x67,0x45,0x23,0x01,
0x00,0x11,0x22,0x33,
0x44,0x55,0x66,0x77,
0x88,0x99,0xaa,0xbb,
0xcc,0xdd,0xee,0xff
};

for (i = 0; i < sizeof(key_bit)/sizeof(key_bit[0]); ++i)
{
if (aes_set_key(&ctx, key, key_bit[i]) != SUCCESS)//初始化AES结构体ctx,包括密钥处理
{
printf("aes_set_key error.");
return -1;
}
if(aes_encrypt_block(&ctx, cipher_text, text) != SUCCESS)//调用AES加密接口
{
printf("aes_encrypt_block error.");
return -1;
}
if(aes_decrypt_block(&ctx, ret_text, cipher_text) != SUCCESS)//调用AES解密接口
{
printf("aes_decrypt_block error.");
return -1;
}
printf("key_bit %d: \n", key_bit[i]);
print("\tkey    :  ", key , 16+8*i);
print("\tinput  :  ", text ,16);
print("\tencrypt:  ", cipher_text , 16);
print("\tdecrypt:  ", ret_text,16);

}
return 0;
}
主函数中预制了16位明文和32位的密钥(密钥根据参数选取使用的长短),接着调用加密函数得到密文,然后使用得到的密文调用解密函数得到解密的明文,最后打印出相关的数据。
代码源码下载地址:CSDN源码(如不可下载可以留下邮箱,作者第一时间回复,也可去下面地址下载)
Github下载地址:GitHub源码(如果感觉可以请给个satr,多谢)

注明:该文章为作者根据网上收集的资料(图片和部分代码)整理,由于浏览太多未标明出处请原作者见谅,如有原作者追究请联系作者删除,谢谢。另本文作者辛苦手打,转载请注明地址,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息