AES加解密之C++实现

一、AES介绍

AES,高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。对称加密算法也就是加密和解密用相同的密钥,具体的加密流程如下图:AES加解密之C++实现_第1张图片
下面简单介绍下各个部分的作用与意义:

明文P

没有经过加密的数据。

密钥K

用来加密明文的密码,在对称加密算法中,加密与解密的密钥是相同的。密钥为接收方与发送方协商产生,但不可以直接在网络上传输,否则会导致密钥泄漏,通常是通过非对称加密算法加密密钥,然后再通过网络传输给对方,或者直接面对面商量密钥。密钥是绝对不可以泄漏的,否则会被攻击者还原密文,窃取机密数据。

AES加密函数

设AES加密函数为E,则 C = E(K, P),其中P为明文,K为密钥,C为密文。也就是说,把明文P和密钥K作为加密函数的参数输入,则加密函数E会输出密文C。

密文C

经加密函数处理后的数据

AES解密函数

设AES解密函数为D,则 P = D(K, C),其中C为密文,K为密钥,P为明文。也就是说,把密文C和密钥K作为解密函数的参数输入,则解密函数会输出明文P。

在这里简单介绍下对称加密算法与非对称加密算法的区别。

对称加密算法

加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合。缺点是密钥的传输比较麻烦。

非对称加密算法

加密和解密用的密钥是不同的,这种加密方式是用数学上的难解问题构造的,通常加密解密的速度比较慢,适合偶尔发送数据的场合。优点是密钥传输方便。常见的非对称加密算法为RSA、ECC和EIGamal。

实际中,一般是通过RSA加密AES的密钥,传输到接收方,接收方解密得到AES密钥,然后发送方和接收方用AES密钥来通信。

二、AES结构

AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同,如下表所示:
|AES 密钥长度(32位比特字) |分组长度(32位比特字) |加密轮数
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

这里实现的是AES-128,也就是密钥的长度为128位,加密轮数为10轮。
上面说到,AES的加密公式为C = E(K,P),在加密函数E中,会执行一个轮函数,并且执行10次这个轮函数,这个轮函数的前9次执行的操作是一样的,只有第10次有所不同。也就是说,一个明文分组会被加密10轮。AES的核心就是实现一轮中的所有操作。

AES的处理单位是字节,128位的输入明文分组P和输入密钥K都被分成16个字节,分别记为P = P0 P1 … P15 和 K = K0 K1 … K15。如,明文分组为P = abcdefghijklmnop,其中的字符a对应P0,p对应P15。一般地,明文分组用字节为单位的正方形矩阵描述,称为状态矩阵。在算法的每一轮中,状态矩阵的内容不断发生变化,最后的结果作为密文输出。该矩阵中字节的排列顺序为从上到下、从左至右依次排列

解密过程仍为10轮,每一轮的操作是加密操作的逆操作。由于AES的4个轮操作都是可逆的,因此,解密操作的一轮就是顺序执行逆行移位、逆字节代换、轮密钥加和逆列混合。同加密操作类似,最后一轮不执行逆列混合,在第1轮解密之前,要执行1次密钥加操作。

AES的S和逆S盒

AES的字节代换其实就是一个简单的查表操作。AES定义了一个S盒和一个逆S盒,S盒如下:

行/列	0	1	2	3	4	5	6	7	8	9	A	B	C	D	E	F
0	0x63	0x7c	0x77	0x7b	0xf2	0x6b	0x6f	0xc5	0x30	0x01	0x67	0x2b	0xfe	0xd7	0xab	0x76
1	0xca	0x82	0xc9	0x7d	0xfa	0x59	0x47	0xf0	0xad	0xd4	0xa2	0xaf	0x9c	0xa4	0x72	0xc0
2	0xb7	0xfd	0x93	0x26	0x36	0x3f	0xf7	0xcc	0x34	0xa5	0xe5	0xf1	0x71	0xd8	0x31	0x15
3	0x04	0xc7	0x23	0xc3	0x18	0x96	0x05	0x9a	0x07	0x12	0x80	0xe2	0xeb	0x27	0xb2	0x75
4	0x09	0x83	0x2c	0x1a	0x1b	0x6e	0x5a	0xa0	0x52	0x3b	0xd6	0xb3	0x29	0xe3	0x2f	0x84
5	0x53	0xd1	0x00	0xed	0x20	0xfc	0xb1	0x5b	0x6a	0xcb	0xbe	0x39	0x4a	0x4c	0x58	0xcf
6	0xd0	0xef	0xaa	0xfb	0x43	0x4d	0x33	0x85	0x45	0xf9	0x02	0x7f	0x50	0x3c	0x9f	0xa8
7	0x51	0xa3	0x40	0x8f	0x92	0x9d	0x38	0xf5	0xbc	0xb6	0xda	0x21	0x10	0xff	0xf3	0xd2
8	0xcd	0x0c	0x13	0xec	0x5f	0x97	0x44	0x17	0xc4	0xa7	0x7e	0x3d	0x64	0x5d	0x19	0x73
9	0x60	0x81	0x4f	0xdc	0x22	0x2a	0x90	0x88	0x46	0xee	0xb8	0x14	0xde	0x5e	0x0b	0xdb
A	0xe0	0x32	0x3a	0x0a	0x49	0x06	0x24	0x5c	0xc2	0xd3	0xac	0x62	0x91	0x95	0xe4	0x79
B	0xe7	0xc8	0x37	0x6d	0x8d	0xd5	0x4e	0xa9	0x6c	0x56	0xf4	0xea	0x65	0x7a	0xae	0x08
C	0xba	0x78	0x25	0x2e	0x1c	0xa6	0xb4	0xc6	0xe8	0xdd	0x74	0x1f	0x4b	0xbd	0x8b	0x8a
D	0x70	0x3e	0xb5	0x66	0x48	0x03	0xf6	0x0e	0x61	0x35	0x57	0xb9	0x86	0xc1	0x1d	0x9e
E	0xe1	0xf8	0x98	0x11	0x69	0xd9	0x8e	0x94	0x9b	0x1e	0x87	0xe9	0xce	0x55	0x28	0xdf
F	0x8c	0xa1	0x89	0x0d	0xbf	0xe6	0x42	0x68	0x41	0x99	0x2d	0x0f	0xb0	0x54	0xbb	0x16

状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出。

逆字节代换也就是查逆S盒来变换,逆S盒如下:

行/列	0	1	2	3	4	5	6	7	8	9	A	B	C	D	E	F
0	0x52	0x09	0x6a	0xd5	0x30	0x36	0xa5	0x38	0xbf	0x40	0xa3	0x9e	0x81	0xf3	0xd7	0xfb
1	0x7c	0xe3	0x39	0x82	0x9b	0x2f	0xff	0x87	0x34	0x8e	0x43	0x44	0xc4	0xde	0xe9	0xcb
2	0x54	0x7b	0x94	0x32	0xa6	0xc2	0x23	0x3d	0xee	0x4c	0x95	0x0b	0x42	0xfa	0xc3	0x4e
3	0x08	0x2e	0xa1	0x66	0x28	0xd9	0x24	0xb2	0x76	0x5b	0xa2	0x49	0x6d	0x8b	0xd1	0x25
4	0x72	0xf8	0xf6	0x64	0x86	0x68	0x98	0x16	0xd4	0xa4	0x5c	0xcc	0x5d	0x65	0xb6	0x92
5	0x6c	0x70	0x48	0x50	0xfd	0xed	0xb9	0xda	0x5e	0x15	0x46	0x57	0xa7	0x8d	0x9d	0x84
6	0x90	0xd8	0xab	0x00	0x8c	0xbc	0xd3	0x0a	0xf7	0xe4	0x58	0x05	0xb8	0xb3	0x45	0x06
7	0xd0	0x2c	0x1e	0x8f	0xca	0x3f	0x0f	0x02	0xc1	0xaf	0xbd	0x03	0x01	0x13	0x8a	0x6b
8	0x3a	0x91	0x11	0x41	0x4f	0x67	0xdc	0xea	0x97	0xf2	0xcf	0xce	0xf0	0xb4	0xe6	0x73
9	0x96	0xac	0x74	0x22	0xe7	0xad	0x35	0x85	0xe2	0xf9	0x37	0xe8	0x1c	0x75	0xdf	0x6e
A	0x47	0xf1	0x1a	0x71	0x1d	0x29	0xc5	0x89	0x6f	0xb7	0x62	0x0e	0xaa	0x18	0xbe	0x1b
B	0xfc	0x56	0x3e	0x4b	0xc6	0xd2	0x79	0x20	0x9a	0xdb	0xc0	0xfe	0x78	0xcd	0x5a	0xf4
C	0x1f	0xdd	0xa8	0x33	0x88	0x07	0xc7	0x31	0xb1	0x12	0x10	0x59	0x27	0x80	0xec	0x5f
D	0x60	0x51	0x7f	0xa9	0x19	0xb5	0x4a	0x0d	0x2d	0xe5	0x7a	0x9f	0x93	0xc9	0x9c	0xef
E	0xa0	0xe0	0x3b	0x4d	0xae	0x2a	0xf5	0xb0	0xc8	0xeb	0xbb	0x3c	0x83	0x53	0x99	0x61
F	0x17	0x2b	0x04	0x7e	0xba	0x77	0xd6	0x26	0xe1	0x69	0x14	0x63	0x55	0x21	0x0c	0x7d

三、实现

说那么多没用,只需要了解对应原理就行,最终还是落在代码的实现上,为了可复用性、模块化和组件化,我依然封装为dll库,提供了如下三个接口:

  1. 秘钥初始化接口
    因为AES是对称加密,加解密用的同一个密码,这里我提供了一个秘钥初始化接口,初始化后加密和解密则使用该秘钥进行加解密。
// ------------------------------------------------------------------------
// Function:
//		Initial Encode or Decoder private key
// Remark:
//      This interface must be invoked before Encode_Data or Decoder_Data
// Parameter:
//      pPrivateKey[in]		:  private key
//		nKeyByteLen[in]		:  private key len
// Return:
//		return 0 if initial success else return -1
// ------------------------------------------------------------------------
AESENCRYPT_API int AES_InitlizeKey(char* pPrivateKey, AESKey nKeyByteLen);

该接口提供了两个参数,指定秘钥信息以及秘钥长度。

  1. 数据加密接口
// ------------------------------------------------------------------------
// Function:
//		Encode source data
// Remark:
//      AES_InitlizeKey must be invoked before invoke AES_Encode_Data
// Parameter:
//      pSrcData[in]		:  not encrypted source data
//		pDestBuf[in]		:  buffer that stored encrypted data
//		nSrcDataLenOrBufLen[in-out]	:
//							   input source data len or the result len,
//							   AES algorithmic does not change the len
//							   of input data len,so the input length equal
//							   result length
// Return:
//		return 0 if initial success else return -1
// ------------------------------------------------------------------------
AESENCRYPT_API long AES_Encode_Data(char* pSrcData, 
									char* pDestBuf, 
									unsigned long  nSrcDataLenOrBufLen);

该接口提供了三个参数,需要加密的文本数据,加密后存放的数据缓存以及缓存长度。

  1. 解密数据接口
// ------------------------------------------------------------------------
// Function:
//		Decode source data
// Remark:
//      AES_InitlizeKey must be invoked before invoke AES_Decode_Data
// Parameter:
//      pSrcEncryptData[in]		:  encrypted source data
//		pDestBuf[in]			:  buffer that stored decoded data
//		nSrcEncryptDataLenOrBufLen[in-out]	:
//							   input encrypted data len or the result len,
//							   AES algorithmic does not change the len
//							   of input data len,so the input length equal
//							   result length
// Return:
//		return 0 if initial success else return -1
// ------------------------------------------------------------------------
AESENCRYPT_API long AES_Decode_Data(char* pSrcEncryptData, 
									char* pDestBuf, 
									unsigned long  nSrcEncryptDataLenOrBufLen);

该接口提供数据解密,第一个参数为需要解密的数据,第二个为解密之后的数据缓存以及数据缓存长度。

加密核心实现

AES是分段多轮加密过程,其核心实现代码如下:

void Cipher(unsigned char* input, unsigned char* output)
{
	int i = 0;
	memset(&State[0][0],0,16);

	// 明文[128bit=16字节=4*4状态举证]状态数据初始化
	for(i=0;i<4*g_Nb;i++)                             
	{
		State[i%4][i/4]=input[i];					
	}

	// 轮密钥加[初始轮1加上Nr轮=Nr+1轮=Nr+1轮秘钥数组]
	AddRoundKey(0);									

	// 进行Nr-1轮加密,最后一轮不需要列混淆
	for (int round = 1; round <= g_Nr; round++)  
	{
		SubBytes();									//字节代换
		ShiftRows();								//行移位
		if(round != g_Nr)
			MixColumns();							//列混淆
		AddRoundKey(round);							//轮密钥加
	}  

	// 输出加密后的状态数据[加密前明文与加密后明文长度未变化]
	// 因为它是针对状态数据进行各项复杂操作的
	for (i = 0; i < (4 * g_Nb); i++)
	{
		output[i] =  State[i % 4][ i / 4];
	}

}

解密核心实现

解密其实就是一个逆向过程,实现核心代码如下:

void InvCipher(unsigned char* input,unsigned char* output)
{
	memset(&State[0][0],0,16);
	for (int i = 0; i < (4 * g_Nb); i++)
	{
		State[i % 4][ i / 4] = input[i];
	}

	AddRoundKey(g_Nr);

	for (int round = g_Nr-1; round >= 1; round--)  // main round loop
	{
		InvShiftRows();
		InvSubBytes();
		AddRoundKey(round);
		InvMixColumns();
	}  // end main round loop for InvCipher

	InvShiftRows();
	InvSubBytes();
	AddRoundKey(0);

	// output = state
	for (int i = 0; i < (4 * g_Nb); i++)
	{
		output[i] =  State[i % 4][ i / 4];
	}
}

相关函数实现

// 轮密钥加
void AddRoundKey(int round)
{
	// 注: 秘钥块大小为Nk行一共Nr+1轮秘钥Nk为4-6-8大于等于明文矩阵行4
	//     故而多出秘钥块后面[所有行最后多出的不是每个秘钥块多出的]不参与
	//     秘钥加运行,Nk的增加也就是秘钥长度的增加只是起了增加轮Nr的作用
	//     所以每轮的作用行只有4行

	//							  b0 b4 b8 b12      k0 k1 k2 k3 
	//							  b0 b4 b8 b12  ^   k4 k5 k6 k7
	//							  b0 b4 b8 b12      k8 k9 k10k11
	//							  b0 b4 b8 b12      k12k13k14k15	
	int i,j;  //i行 j列           //因为密钥w是一列一列排列的,即 k0 k4 k8 k12
	for(j=0;j<4;j++)			  //							  k1 k5 k9 k13
	{							  //							  k2 k6 k10k14
		for(i=0;i<4;i++)		  //							  k3 k7 k11k15
		{						  // 所以i行j列的下标是4*((round*4)+j)+i即16*round+4*j+i
			State[i][j]=(unsigned char)((int)State[i][j]^(int)w[4*((round*4)+j)+i]);  
		}
	}
}

// 字节代换函数
void SubBytes()                              
{
	int i,j;
	for(j=0;j<4;j++)
	{
		for(i=0;i<4;i++)
		{
			State[i][j]=AesSbox[State[i][j]];
			//因为 16*(State[i][j]>>4)+State[i][j]&0x0f=State[i][j]
		}
	}
}

// 逆字节代换函数
void InvSubBytes()
{
	int i,j;
	for(j=0;j<4;j++)
	{
		for(i=0;i<4;i++)
		{
			State[i][j]=AesiSbox[State[i][j]]; //因为 16*(State[i][j]>>4)+State[i][j]&0x0f=State[i][j]
		}
	}
}

// 行移位
void ShiftRows()
{
	unsigned char temp[4*4];                                        //Page105
	int i,j;
	for(j=0;j<4;j++)
	{
		for(i=0;i<4;i++)
		{
			temp[4*i+j]=State[i][j];
		}
	}
	for(i=1;i<4;i++)
	{
		for(j=0;j<4;j++)
		{
			if(i==1)State[i][j]=temp[4*i+(j+1)%4];					//第一行左移1位
			else if(i==2)State[i][j]=temp[4*i+(j+2)%4];				//第二行左移2位
			else if(i==3)State[i][j]=temp[4*i+(j+3)%4];				//第三行左移3位
		}
	}

}

// 逆行移位
void InvShiftRows()
{
	unsigned char temp[4*4];
	int i,j;
	for(j=0;j<4;j++)
	{
		for(i=0;i<4;i++)
		{
			temp[4*i+j]=State[i][j];
		}
	}
	for(i=1;i<4;i++)
	{
		for(j=0;j<4;j++)
		{
			//if(i==1)State[i][j]=temp[4*i+(j-1)%4];    在此犯了一个错误 -1%4=-1 而不是3,所以采用了下面再加一个4的做法
			if(i==1)State[i][j]=temp[4*i+(j+3)%4];			//第一行右移1位 j-1+4=j+3
			else if(i==2)State[i][j]=temp[4*i+(j+2)%4];		//第二行右移2位 j-2+4=j+2
			else if(i==3)State[i][j]=temp[4*i+(j+1)%4];		//第三行右移3位 j-3+4=j+2
		}
	}

}

四、测试

一下提供AES测试代码:

int _tmain(int argc, _TCHAR* argv[])
{
	// 32byte秘钥(256bit)
	char* pKey = "12345678123456781234567812345678";
	AES_InitlizeKey(pKey,AESKey_32);

	// 需要加密的数据
	char* pSrc = "dw123456";
	int nLen = strlen(pSrc);

	// 加密数据
	char szBuf[1024] = {0};
	int nOutLen = AES_Encode_Data(pSrc,szBuf,nLen);

	cout << "Encrypt source:" << pSrc << endl;
	cout << "Encrypt Result:" << szBuf << endl;

	// 将秘钥base分装--传输
	string strBase64 = Base64Helper::encode((const BYTE*)szBuf, nOutLen);
	int x = strBase64.length();

	// 解密秘闻
	char szUncrypt[1024] = {0};
	int nUnLen = strlen(szBuf);
	nOutLen = AES_Decode_Data(szBuf,szUncrypt,nUnLen);

	cout << "UnEncrypt Result:" << szUncrypt << endl;

	system("pause");
}

加密和解密测试如下:
AES加解密之C++实现_第2张图片
可以看到明文dw123456经过AES加密之后变成了密文,然后又被解密为dw123456

五、文件加解密

加密测试

通过以上封装之后,我们就可以实现对文件的加密和解密了,这里我封装了一个demon,需要加密的原文如下(问题列表):
AES加解密之C++实现_第3张图片
我们将使用如下demon进行加密,密码为24字节(192bit)-123456781234567812345678:
AES加解密之C++实现_第4张图片
加密完成后生成文件“问题_Encrypt.txt”,密文如下:
AES加解密之C++实现_第5张图片

解密测试

通过加密完成之后,文件是基本无法阅读的,所以我们需要用我们的密码解密,我们重新选择输入文件为加密后的密文文件,再次解密:
AES加解密之C++实现_第6张图片
解密之后生成文件“问题_Encrypt_Dcrypt.txt”,文件内容如下:
AES加解密之C++实现_第7张图片
通过文件内容,我们可以看到密文确实被解密了,核心加密代码如下:

BOOL CFileEncoderDlg::Encrypt(CString strInputFile, CString strKey,CString strDirectory)
{
	AESKey eKeyType = AESKey_16;
	if(24 == strKey.GetLength())
	{
		eKeyType = AESKey_24;
	} 
	else if (32 == strKey.GetLength())
	{
		eKeyType = AESKey_32;
	}
	AES_InitlizeKey((LPTSTR)(LPCTSTR)strKey, eKeyType);

	TCHAR szTitle[MAX_PATH] = {_T("")};
	CFileUtil::GetFileTitleEx(strInputFile, szTitle, MAX_PATH);
	CString strFileName(szTitle);
	TCHAR szExt[MAX_PATH] = {_T("")};
	CFileUtil::GetFileExtName(strInputFile, szExt, MAX_PATH);
	strFileName += _T("_Encrypt.");
	strFileName += szExt;

	CString strOutputPath = strDirectory + _T("/");
	strOutputPath += strFileName;

	FILE* pFile = fopen((LPTSTR)(LPCTSTR)strInputFile, "rb");
	if(NULL == pFile)
	{
		return FALSE;
	}

	FILE* pOutputFile = fopen((LPTSTR)(LPCTSTR)strOutputPath, "wb");
	if(NULL == pOutputFile)
	{
		fclose(pFile);
		return FALSE;
	}

	while (0 == feof(pFile))
	{
		char szBuffer[1024] = {0};
		int nLen = fread(szBuffer, 1, 1024, pFile);
		if (nLen > 0)
		{
			char szEncrypt[1024+16+16] = {0};	// 可能多出16字节加密长度
			int nEncryptLen = AES_Encode_Data(szBuffer, szEncrypt, nLen);
			if (nEncryptLen > 0)
			{
				fwrite(szEncrypt, 1, nEncryptLen, pOutputFile);
			}
		}
	}

	fclose(pFile);
	fclose(pOutputFile);
	return TRUE;
}

核心解码代码如下:

BOOL CFileEncoderDlg::Decrypt(CString strInputFile, CString strKey, CString strDirectory)
{
	AESKey eKeyType = AESKey_16;
	if(24 == strKey.GetLength())
	{
		eKeyType = AESKey_24;
	} 
	else if (32 == strKey.GetLength())
	{
		eKeyType = AESKey_32;
	}
	AES_InitlizeKey((LPTSTR)(LPCTSTR)strKey, eKeyType);

	TCHAR szTitle[MAX_PATH] = {_T("")};
	CFileUtil::GetFileTitleEx(strInputFile, szTitle, MAX_PATH);
	CString strFileName(szTitle);
	TCHAR szExt[MAX_PATH] = {_T("")};
	CFileUtil::GetFileExtName(strInputFile, szExt, MAX_PATH);
	strFileName += _T("_Dcrypt.");
	strFileName += szExt;

	CString strOutputPath = strDirectory + _T("/");
	strOutputPath += strFileName;

	FILE* pFile = fopen((LPTSTR)(LPCTSTR)strInputFile, "rb");
	if(NULL == pFile)
	{
		return FALSE;
	}

	FILE* pOutputFile = fopen((LPTSTR)(LPCTSTR)strOutputPath, "wb");
	if(NULL == pOutputFile)
	{
		fclose(pFile);
		return FALSE;
	}

	while (0 == feof(pFile))
	{
		char szBuffer[1024+16] = {0};
		int nLen = fread(szBuffer, 1, 1024+16, pFile);
		if (nLen > 0)
		{
			char szDcrypt[2048] = {0};
			int nDcryptLen = AES_Decode_Data(szBuffer, szDcrypt, nLen);
			if (nDcryptLen > 0)
			{
				fwrite(szDcrypt, 1, nDcryptLen, pOutputFile);
			}
		}
	}

	fclose(pFile);
	fclose(pOutputFile);
	return TRUE;
}

源码获取、合作、技术交流请获取如下联系方式:
QQ交流群:961179337
在这里插入图片描述

微信账号:lixiang6153
公众号:IT技术快餐
电子邮箱:[email protected]

你可能感兴趣的:(C++,VC开发,加密解密)