你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

AES对称加密使用方式与浅析

2021/11/24 13:21:09

首先,我这里使用OpenSSL库来实现的加密,使用时需要导入这些库和头文件,已经上传到GitHub,先从这里拉下来。

一、介绍AES加密:

AES是一种对称加密的算法,可以自己指定密钥key参与明文的加密,加解密使用同一个密钥key。共有五种加密模式,

  1. 电码本模式(Electronic Codebook Book (ECB)):这种模式是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
    2.密码分组链接模式(Cipher Block Chaining (CBC)):这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
    3.计算器模式(Counter (CTR)):计算器模式不常见,在CTR模式中, 有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是 在计算器不能维持很长的情况下,密钥只能使用一次 。
    4.密码反馈模式(Cipher FeedBack (CFB)):
    5.输出反馈模式(Output FeedBack (OFB))

由于第4和第5种很复杂,用示意图来展示
4.密码反馈模式(Cipher FeedBack (CFB)):
在这里插入图片描述

5、输出反馈模式(Output FeedBack (OFB))
在这里插入图片描述

1.1下面用一个图片来整体说明下AES对称加密的流程:

在这里插入图片描述

1.2分组密码的填充


这种是PKCS#5填充方式,如果明文刚好是16字节不需要填充的时候,就再加16字节的16,后边看代码可以一目了然。这里我是使用cbc的加密模式,我这里是使用c++来实现的,Java已经基础了api直接调用即可,c++代码也可以写成jni方式去调用,跨平台是一个优势,c++代码的性能优越也是一个优势,下面看代码实现:

//测试入口写在了main函数中
int main(int argc, char** argv)  
{
  //记录开始加密时间,用来评估加密耗时
  struct timeval tv; 
  gettimeofday(&tv, NULL);
  int64_t start = tv.tv_sec * 1000 + tv.tv_usec / 1000;

  //手动写一串明文用来加密,读者可以将这段明文替换成字节需要加密的明文字节
  std::string data = "abcdefghijklmnopqrstovwxyz";
  int16_t dateLen = data.length();
  /**一般AES是用128bit作为加密块长度,也就是16字节,因此密钥长度一般也用16个字节的长度去做加密*/
		int AES_block_num = dateLen / AES_BLOCK_SIZE + 1;
		int AES_encrypt_data_len = AES_block_num * AES_BLOCK_SIZE;
		unsigned char *encryptBuffer = (unsigned char *)malloc(AES_encrypt_data_len);
		unsigned char *ciphertext= (unsigned char *)malloc(AES_encrypt_data_len);
		if( encryptBuffer == NULL || out == NULL)
		{
		  ALOGE("Error,malloc fail:encryptBuffer or out");
		  return;
		}
		memset(encryptBuffer,0,AES_encrypt_data_len);
		memset(ciphertext,0,AES_encrypt_data_len);
		/**填充的原则是:
		   1、如果源字节长度不是16的整数倍,需要补满16个字节的整数倍,在不够的地方补(16-len)个16-len(值),
			  例如:原始字节是0x10 11 01 02 05 04,总共6个字节,就需要补16-6个字节,每个字节填充16-6 = 10(十六进制是0x0a)
			  填充后就变成:0x10 11 01 02 05 04 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a
		   2、如果源字节长度正好是16的整数倍,则需要再补16个字节的十进制的16*/
		
		int fillDataAndNum = 0;
		if( dateLen % AES_BLOCK_SIZE >0 )
		{
		  fillDataAndNum = AES_encrypt_data_len - dateLen;
		}
		else
		{
		  fillDataAndNum = AES_BLOCK_SIZE;
		}
		memset(encryptBuffer,fillDataAndNum,AES_encrypt_data_len);
		memcpy(encryptBuffer,date, dateLen);
        AES_encryptOrEncrypt(encryptBuffer,AES_encrypt_data_len,TRUE,ciphertext);
        if(ciphertext)
        {
            //这里可以把ciphertext中的值打印出来,加密后的密文就在ciphertext里
        }
        else
        {
            ALOGE("[AES_encrypt]:faild!\n");
            break;
        }
		struct timeval tv1; 
		gettimeofday(&tv1, NULL);
		int64_t end = tv1.tv_sec * 1000 + tv1.tv_usec / 1000;
		ALOGE("11111ddddd:AES_encryptDataTime: %d",end - start);
		ALOGE("11111aaa:encryptData len = %d",AES_encrypt_data_len );

//decrypt
		int AES_block_num1 = AES_encrypt_data_len  / AES_BLOCK_SIZE + 1;
		int AES_decrypt_data_len = AES_block_num1 * AES_BLOCK_SIZE;
		unsigned char *decryptBuffer = (unsigned char *)malloc(AES_decrypt_data_len );
		unsigned char *out1 = (unsigned char *)malloc(AES_decrypt_data_len );
		if( decryptBuffer == NULL || out1 == NULL)
		{
			ALOGE("Error,malloc fail:encryptBuffer or out");
			return;
		}
		memset(decryptBuffer ,AES_decrypt_data_len );
		memset(out1,0,AES_decrypt_data_len );
		/**填充的原则是:
		1、如果源字节长度不是16的整数倍,需要补满16个字节的整数倍,在不够的地方补(16-len)个16-len(值),
		例如:原始字节是0x10 11 01 02 05 04,总共6个字节,就需要补16-6个字节,每个字节填充16-6 = 10(十六进制是0x0a)
		填充后就变成:0x10 11 01 02 05 04 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a
		2、如果源字节长度正好是16的整数倍,则需要再补16个字节的十进制的16*/
				 
				 int fillDataAndNum1 = 0;
				 if(AES_decrypt_data_len  % AES_BLOCK_SIZE >0 )
				 {
				   fillDataAndNum1 = AES_decrypt_data_len - AES_encrypt_data_len  ;
				 }
				 else
				 {
				   fillDataAndNum1 = AES_BLOCK_SIZE;
				 }
				 memset(decryptBuffer,fillDataAndNum1,AES_decrypt_data_len );
				 memcpy(decryptBuffer,msg_info.msg, AES_encrypt_data_len  );

		 AES_encryptOrEncrypt(decryptBuffer,AES_decrypt_data_len,FALSE,out1);
		ALOGE("11111bbb:decryptData");
       if(out1)
        {
            //这里可以把out1中的值打印出来,解密后的明文就在out1里
        }
        else
        {
            ALOGE("[AES_encrypt]:faild!\n");
            break;
        }
       return 0;  
} 

void GBDataHandle::AES_encryptOrEncrypt(uint8_t *data,uint16_t len,bool isEncrypt,unsigned char* out)
{
  AES_KEY aes;
  char ivValue[] = "0000000000000000";
  unsigned char key[17] = {0};
  memset(key, '0', 16);//赋值默认的加密key,只用于测试
    if(isEncrypt)
    {
        //设置加密密钥,16字节
        int keyLen = strlen((char*)(key)) * 8;
        ALOGD(" key =  %s,   keyLen = %d", key, keyLen);
        if (AES_set_encrypt_key((unsigned char*)key, keyLen, &aes) < 0)
        {
            ALOGD("Unable to set encryption key in AES");
            return;
        }
    }
    else
    {
        //设置解密密钥,16字节
        int keyLen = strlen((char*)(key)) * 8;
        ALOGD(" key =  %s,   keyLen = %d", key, keyLen);
        if (AES_set_decrypt_key((unsigned char*)key, keyLen, &aes) < 0)
        {
            ALOGD("Unable to set encryption key in AES");
            return;
        }
    }

  //std::string data_bak = (char*)pcInput;
  AES_cbc_encrypt((const unsigned char*)pcInput, (unsigned char*)out, nLen,
                   &aes, (unsigned char*)ivValue, isEncrypt ? AES_ENCRYPT : AES_DECRYPT);
}

这样使用,加解密即可完成,因为是demo使用时可以注意封装与优化,总体思路是没错的