3DES加密算法介绍
3DES数据加密算法是一种可逆的对称加密算法,也称三重数据加密算法。3DES块加密算法的设计用来提供一种相对简单的方法,即通过增加DES的密钥长度来避免类似的攻击,而不是设计一种全新的密码算法,目前3DES作为DES的过渡算法已经逐渐被更安全的AES代替。
DES的秘钥长度是8字节,而3DES的秘钥长度是24字节。3DES 是为了增加DES 的强度,将DES 重复计算3次所得到的一种密码算法。但3DES 并不是进行三次DES 加密(加密->加密->加密),而是加密->解密->加密的过程。这样做的目的是为了3DES 能够兼容普通的DES。
对于3DES算法秘钥来说,24个字节被分为三组,每组8个字节,分别用于三次DES计算的秘钥,所以当DES 中所有的密钥都是相同时,3DES 也就等同于普通的DES了。这就让3DES 具备了向下的兼容性。
加密模式
ECB模式(Electronic CodeBook mode):电子密码本模式
ECB (电子密码本)模式是最简单的块密码加密模式,加密前根据数据块大小分成若干块,之后将每块使用相同的密钥单独通过块加密器加密。这种加密模式的优点就是简单,不需要初始化向量(IV),每个数据块独立进行加/解密,利于并行计算,加/解密效率很高。但这种模式中,所有数据都采用相同密钥进行加/解密,也没有经过任何逻辑运算,相同明文得到相同的密文,所以可能导致“选择明文攻击”的发生。
CBC模式(Cipher Block Chaining mode):密码分组链模式
CBC (密码分组链接)模式是先将明文切分成若干小块,然后每个小块与初始块或者上一段的密文段进行逻辑异或运算后,再用密钥进行加密。第一个明文块与一个叫初始化向量的数据块进行逻辑异或运算。这样就有效的解决了ECB模式所暴露出来的问题,即使两个明文块相同,加密后得到的密文块也不相同。但是缺点也相当明显,如加密过程复杂,效率低等。
CFB模式(Cipher FeedBack mode):密码反馈模式
与ECB和CBC模式只能够加密块数据不同,CFB模式能够将密文转化成为流密文。这种加密模式中,由于加密流程和解密流程中被块加密器加密的数据是前块的密文,因此即使本块明文数据的长度不是数据块大小的整数倍也是不需要填充的,这保证了数据长度在加密前后是相同的。
OFB模式(Output FeedBack mode):输出反馈模式
不再直接加密明文块,其加密过程是先使用块加密器生成密钥流,然后再将密钥流和明文流进行逻辑异或运算得到密文流。
CTR模式(CounTeR mode):计时器模式
是一种通过将逐次累加的计算器进行加密来生成密钥流的流密码。每一个分组对应一个逐次累加的计数器,并通过计数器进行加密来生成密钥流。也就是说,最终的密文分组是通过将计数器加密得到的比特序列,与明文分组进行XOR而得到的。
图文详解可参考: 传送门
填充方式
由于块密码只能对确定长度的数据块进行处理,明文会被以64bit为一组划分为若干组进行加密,每一组使用DES算法由明文获得密文。可是待加密的明文并不能保证总是可以正好分成若干个64bit的组,最后一组正好满64bit的可能性往往是比较低的,那么为了加密方便,应该怎么办呢,Padding就是用来解决这个问题的。
3DES主要的填充方式有如下几种:
zeropadding
所有需要填充的地方都以0填充。
pkcs7padding
填充的内容是需要填充的字节数。如果最后一个数据块长度为len,每个块的长度为k,len恰好等于k时,则需要在后面再添加一个完整的padding块。例如是以每8byte为一块,最后一块有8byte,则需要填充8byte的0x08。
pkcs5padding
PKCS5 和 PKCS7 的唯一区别是PKCS5只能用来填充64bit(8bytes)的数据块,除此之外可以混用。
示例代码
mbedtls包含了3DES加解密模块,使用起来也十分方便,在高级语言中(java等),一般3DES加密后会以base64的形式输出,恰好mbedtls也集成了base64编解码模块,废话不多说,直接上代码:
#include <stdio.h>
#include "mbedtls/aes.h"
#include "mbedtls/des.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
int main(int argc, char *argv[])
{
unsigned char input[2048] = {"0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399"};
unsigned char output[2048] = {0};
unsigned char key[] = {"123456781234567812345678"};
UINT64 start_time = 0;
UINT64 des3_encrypt_time = 0;
UINT64 base64_encode_time = 0;
UINT64 base64_decode_time = 0;
UINT64 des3_decrypt_time = 0;
mbedtls_des3_context ctx;
int data_len = strlen(input); // 数据长度
printf("data_len = %d\r\n", data_len);
int len = (data_len / 8 + (data_len % 8 ? 1: 0)) * 8; // 长度补为8的整数倍
printf("padding len = %d\r\n", len);
printf("原始数据:\r\n%s\r\n", input);
start_time = LOS_CurrNanosec(); // 开始计时
mbedtls_des3_init(&ctx);
mbedtls_des3_set3key_enc(&ctx, key);
// 使能 pkcs5padding 填充方式
#if 1
if(data_len == len) {
len += 8;
}
for(int i = data_len; i < len; i++) {
input[i] = len - data_len;
}
#endif
for(int i=0; i<len; i+=8) {
mbedtls_des3_crypt_ecb(&ctx, &input[i], &output[i]);
}
des3_encrypt_time = LOS_CurrNanosec(); // 原始数据加密完成
size_t base64_len;
unsigned char *encode_buf = NULL;
mbedtls_base64_encode( NULL, 0, &base64_len, output, len);
encode_buf = (unsigned char *)calloc(1, base64_len);
mbedtls_base64_encode(encode_buf, base64_len, &base64_len, output, len);
base64_encode_time = LOS_CurrNanosec(); // base64编码完成
printf("加密后HEX: \r\n");
for(int i=0; i<len; i++) {
printf("%02x",output[i]);
}
printf("\r\n");
printf("加密后base64:\r\n%s\r\n",encode_buf);
printf("\r\n\r\n====================================\r\n");
printf("3DES加密耗时:\t\t%lldns\r\n", des3_encrypt_time - start_time);
printf("base64编码耗时:\t\t%lldns\r\n", base64_encode_time - des3_encrypt_time);
printf("加密总耗时:\t\t%lldns\r\n", base64_encode_time - start_time);
printf("====================================\r\n\r\n");
// --------------------------- 开始解密
start_time = LOS_CurrNanosec(); // 开始计时
size_t result_len;
memset(output, 0, sizeof(output)); // 清空output buffer,用于接收base64解码结果
mbedtls_base64_decode( output, sizeof(output), &result_len, encode_buf, base64_len);
base64_decode_time = LOS_CurrNanosec(); // 解码完成
if(result_len%8 != 0) {
printf("base64解码数据不符合3DES加密数据格式! result_len = %d\r\n", result_len);
goto DES_Exit;
}
printf("base64解码后HEX: \r\n");
for(int i=0; i<result_len; i++) {
printf("%02x",output[i]);
}
printf("\r\n");
mbedtls_des3_set3key_dec(&ctx, key);
memset(input, 0, sizeof(input)); // 清空输入buffer,用于接收3DES解密结果
for(int i=0; i<result_len; i+=8) {
mbedtls_des3_crypt_ecb(&ctx, &output[i], &input[i]);
}
unsigned char padding_size = input[result_len-1];
if(padding_size <= 0 || padding_size > 8){
printf("解析失败, padding错误! padding_size = %d\r\n", padding_size);
goto DES_Exit;
}
data_len = result_len - padding_size;
input[data_len] = '\0'; // 添加结束符
des3_decrypt_time = LOS_CurrNanosec(); // 3DES解密完成
printf("解密后原始数据为:\r\n%s\r\n",input);
printf("\r\n\r\n====================================\r\n");
printf("base64解码耗时:\t\t%lldns\r\n", base64_decode_time - start_time);
printf("3DES解密耗时:\t\t%lldns\r\n", des3_decrypt_time - base64_decode_time);
printf("解密总耗时:\t\t%lldns\r\n", des3_decrypt_time - start_time);
printf("====================================\r\n\r\n");
DES_Exit:
mbedtls_des3_free(&ctx);
}
程序运行
如下为GD32F470单片机上的运行结果,可供参考: