mbedtls移植之AES算法

一、 mbedtls简介

MbedTLS是一个开源、可移植、易使用、可读性高的SSL库,实现了常所用的加解密算法、X.509证书操作以及TLS协议操作。MbedTLS各功能模块独立性高、耦合度低,可以通过配置宏定义进行功能裁剪,非常适合对空间和效率要求高的嵌入式系统。

二、AES算法简介

AES全称为Advanced Encryption Standard,是一种对称分组算法。AES算法是NIST组织公开竞选产生,最终Rijndael力压群雄,在2000年被NIST选择为AES标准算法。通过AES算法可以加密或解密一段数据,其中加密为将明文转换为密文,解密是将密文转换为明文。AES算法的密钥长度有三种:128bits、192bits、256bits,但加密和解密的分组长度必须为128bits。
AES为一个分组密码,密码算法总体可分为分组密码(block cipher)和流密码(stream cipher)两类。其中分组密码是每次只能处理特定长度的一块数据,而流密码是对数据流进行连续处理,无需按照一段分组后再处理。
分组密码算法一次只能处理固定长度的分组,当我们需要加解密的数据超过固定分组长度时,需要不断迭代分组密码算法,以保证对全部数据进行加解密,迭代的过程就成为分组密码的模式。对于AES,存在多种模式,常见有:

  • ECB:Electronic CodeBook mode
  • CBC:Cipher Block Chaining mode
  • CFB:Cipher FeedBack mode
  • OFB:Output FeedBack mode
  • CTR:CounTeR mode
    通常使用CBC模式最多,本文以CBC为示例。关于AES算法,具体可阅读NIST FIPS-197文档。

三、实现

3.1 移植MbedTLS代码

移植自mbedTLS 2.16版本
需移植的文件如下:
在这里插入图片描述

修改config.h文件

#ifndef MBEDTLS_CONFIG_H
#define MBEDTLS_CONFIG_H

#define MBEDTLS_ERROR_C

/* AES-256-CBC */
#define MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_AES_C
#define MBEDTLS_AES_ROM_TABLES  //Use precomputed AES tables stored in ROM

#endif /* MBEDTLS_CONFIG_H */

3.2 引入头文件

引入相应的头文件

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "../crypto/mbedtls/aes.h"
#include "../crypto/mbedtls/error.h"

3.3 填充说明

因AES单个分组长度为16个字节,当分组不足16个字节时,必须填充到16个字节,否则AES无法进行加解密。常用的分组填充方式为PKCS#7,PKCS#7的总体填充思想为,缺少N个字节,则填充N个字节的0x0N值,例如待加密数据长度为10字节,则缺少6个字节,故填充内容为6个字节的0x06。但须额外注意,当最后一个分组长度即便正好为16字节,仍需要填充16个字节的0x0F。具体请阅读相关标准

3.4 一次处理所有数据

一次性加密或解密所有数据,需要加解密的数据都一次作为输入,通常用于数据量不大的情况下。

3.4.1 加密

详细内容见代码中的注释。
注意以下点:

  1. CBC模式是需要一个初始化向量的IV,此初始化向量IV值,加解密时必须保持一致,否则会导致解密出的数据和原明文不一致。
  2. 采用PKCS#7的填充方式
  3. 因进行了填充,故加密后的密文长度必然比明文长度要长,密文长度计算见代码头部注释,需传入足够长度的密文存储空间,否则会导致溢出

/*
 * 一次输入所有待加密数据,并使用AES CBC模式进行加密
 *
 * 输入:
 *      input:待加密数据
 *      input_length:待加密数据的长度
 *      encrypted_data:加密后的密文存放位置,注意此指针指向的区域有效长度计算如下:使用PKCS#7填充的output长度计算应为 decrypted_data_length = ((input_length/16)+1)*16
 * 返回值:加密后密文的长度,若为负值,则存在错误。
 *
 * */
int encrypt_data_by_aes_cbc(unsigned char *input,int input_length,unsigned char *encrypted_data){
    mbedtls_aes_context aes_context;
    int result = 0;
    int padding = 0;
    int real_input_length = 0;
    unsigned char padding_code;
    unsigned char *input_tmp;
    //AES密钥
    const unsigned char key[16] = "1234567890ABCDEF";
    //当使用CBC/CFB/OFB等模式时,需要一个初始化向量IV
    unsigned char iv[16] = "0099887766554433";
    unsigned char error[64];


    /*
    AES为分组加密算法,一个分组长度必须为16字节,所有待加密数据必须为16字节的整倍数。
    若待加密数据不为16字节的整倍数,则必须先填充到16字节整倍数。常用填充方式有PKCS5和PKCS7,通常使用PKCS7填充
    */
    //PKCS7填充到16字节整倍数,若待加密数据正好为16字节整倍数,仍需填充16个字节
    //计算当前长度距离16整倍数的差值,此值即为填充值
    padding = 16 - (input_length%16);
    padding_code = (char)padding;
    //real_input_length即为经过填充后待加密数据的长度,值必然为16整倍数,同时此长度也为加密后的密文长度
    real_input_length = input_length + padding;
    if(real_input_length%16 != 0){
        //填充后必然为16字节整倍数,若不为16字节整倍数,则填充存在问题
        printf("failed to padding\n");
        return -1;
    }
    input_tmp = calloc(1,real_input_length);
    if(input_tmp == NULL){
        //分配内存错误
        return -2;
    }
    //进行填充
    memcpy(input_tmp,input,input_length);
    memset(input_tmp + input_length,padding_code,padding);

    mbedtls_aes_init(&aes_context);
    //若使用AES-128,则必要长度为128bits。若使用AES-256,则密钥长度为256bits
    result = mbedtls_aes_setkey_enc(&aes_context, key, 128);
    if(result != 0){
        printf("failed to set key:");
        mbedtls_strerror(result,error,sizeof(error));
        printf("%s\n",error);
        free(input_tmp);
        mbedtls_aes_free(&aes_context);
        return -3;
    }

    result = mbedtls_aes_crypt_cbc(&aes_context, MBEDTLS_AES_ENCRYPT, real_input_length,iv,input_tmp,encrypted_data);
    if(result != 0){
        printf("failed to encrypt data:");
        mbedtls_strerror(result,error,sizeof(error));
        printf("%s\n",error);
        free(input_tmp);
        mbedtls_aes_free(&aes_context);
        return -4;
    }

    free(input_tmp);
    mbedtls_aes_free(&aes_context);
    //返回密文长度
    return real_input_length;

}

3.4.2 解密

详细内容见代码中的注释。
注意以下点:

  1. CBC模式是需要一个初始化向量的IV,此初始化向量IV值,加解密时必须保持一致,否则会导致解密出的数据和原明文不一致。
  2. 因加密采用PKCS#7的填充方式,故解密后需去填充,详见代码注释
  3. 因进行了填充,故解密后的明文长度比密文长度必然比要短
/*
 * 一次输入所有待加密数据,并使用AES CBC模式进行加密
 * 使用PKCS#7填充的output长度计算应为 output_length = ((input_length/16)+1)*16
 * 输入:
 *  encrypted_data:待解密的密文数据
 *  encrypted_length:待解密的密文数据的有效长度
 *  decrypted_data:解密的明文数据
 *返回值:解密后的明文数据的有效长度,即decrypted_data中数据的有效长度。若为负值,则存在错误。
 *
 * */
int decrypt_data_by_aes_cbc(unsigned char *encrypted_data,int encrypted_length,unsigned char *decrypted_data){
    mbedtls_aes_context aes_context;
    int result = 0;
    int padding = 0;
    unsigned char padding_code;
    //AES密钥
    const unsigned char key[16] = "1234567890ABCDEF";
    //当使用CBC/CFB/OFB等模式时,需要一个初始化向量IV
    unsigned char iv[16] = "0099887766554433";
    unsigned char error[64];

    mbedtls_aes_init(&aes_context);
    //若使用AES-128,则必要长度为128bits。若使用AES-256,则密钥长度为256bits
    result = mbedtls_aes_setkey_dec(&aes_context, key, 128);
    if(result != 0){
        printf("failed to set key:");
        mbedtls_strerror(result,error,sizeof(error));
        printf("%s\n",error);
        mbedtls_aes_free(&aes_context);
        return -1;
    }

    //解密后包含填充值的明文,后续需去除填充值
    unsigned char decrypted_data_include_padding[encrypted_length];
    result = mbedtls_aes_crypt_cbc(&aes_context, MBEDTLS_AES_DECRYPT, encrypted_length,iv,encrypted_data,decrypted_data_include_padding);
    if(result != 0){
        printf("failed to decrypt data:");
        mbedtls_strerror(result,error,sizeof(error));
        printf("%s\n",error);
        mbedtls_aes_free(&aes_context);
        return -2;
    }

    //去除PKCS#7的填充值
    //读取最后一个值,此值即为填充值的长度
    padding_code = decrypted_data_include_padding[encrypted_length-1];
    padding = (int)padding_code;
    if(padding < 1 || padding > 16){
        printf("padding code is illegal!\n");
        return -3;
    }
    int real_decrypted_data_length = encrypted_length - padding;

    memcpy(decrypted_data,decrypted_data_include_padding,real_decrypted_data_length);

    mbedtls_aes_free(&aes_context);
    return real_decrypted_data_length;
}

3.4.3 运行效果

	//待加密数据
    unsigned char input[11] = "ABCDEF12345";
    //PKCS#7填充的密文长度计算:decrypted_data_length = ((input_length/16)+1)*16,decrypted_data_length = ((11/16)+1)*16 = 16
    unsigned char encrypted_data[16];
    unsigned char decrypted_data[16];


    printf("plain data:\n");
    for(int i=0;i<11;i++){
        printf("%02x",input[i]);
    }
    printf("\n");

    //加密
    int encrypted_data_length = encrypt_data_by_aes_cbc(input,11,encrypted_data);
    if(encrypted_data_length < 0){
        printf("failed to encrypt data!\n");
        return -1;
    }
    if(encrypted_data_length != 16){
        printf("failed to encrypt data!\n");
        return -2;
    }else{
        printf("encrypted data:\n");
        for(int i=0;i<encrypted_data_length;i++){
            printf("%02x",encrypted_data[i]);
        }
        printf("\n");
    }

    //解密
    int decrypted_data_length = decrypt_data_by_aes_cbc(encrypted_data,encrypted_data_length,decrypted_data);
    if(decrypted_data_length < 0){
        printf("failed to decrypt data!\n");
        return -3;
    }
    printf("decrypted data:\n");
    for(int i=0;i<decrypted_data_length;i++){
        printf("%02x",decrypted_data[i]);
    }
    printf("\n");

在这里插入图片描述

使用openssl验证,openssl默认会进行加盐操作,需添加-nosalt参数
在这里插入图片描述

待加密明文保存在00.txt,密文保存在01.txt,可看到密文相同
在这里插入图片描述

3.5 基于文件的加解密

当加解密数据较长时,或对文件中内容进行加解密时,需基于文件的加解密操作。总体加解密相同,但需要添加文件读取的控制,尤其是解密时需要去填充的环节,当检测到文件读取完成后,方可进行去填充操作。

每次操作512个字节,每次操作的字节数建议为16字节的整倍数
#define FILE_BUFFER_SIZE 512

3.5.1 加密

/*
 * 从文件中读取明文数据,并使用AES CBC模式进行加密,加密后的密文同样输出到文件中
 *
 * 输入:
 *      plain_file_path:待加密文件路径
 *      encrypted_file_path:加密后文件保存路径
 * 返回值:0:成功,其他值:失败。
 *
 * */
int encrypt_data_from_file_by_aes_cbc(const char *plain_file_path,const char *encrypted_file_path){

    mbedtls_aes_context aes_context;
    int result = 0;
    int padding = 0;
    int real_input_length = 0;
    unsigned char padding_code;
    int eof_flag = 0;
    //AES密钥
    const unsigned char key[16] = "1234567890ABCDEF";
    //当使用CBC/CFB/OFB等模式时,需要一个初始化向量IV
    unsigned char iv[16] = "0099887766554433";
    unsigned char error[64];

    //读写文件的buffer,长度应为16整倍数
    unsigned char input_buffer[FILE_BUFFER_SIZE];
    unsigned char output_buffer[FILE_BUFFER_SIZE];

    FILE *plain_file = fopen(plain_file_path,"rb");
    FILE *encrypted_file = fopen(encrypted_file_path,"wb");

    mbedtls_aes_init(&aes_context);
    //若使用AES-128,则必要长度为128bits。若使用AES-256,则密钥长度为256bits
    result = mbedtls_aes_setkey_enc(&aes_context, key, 128);
    if(result != 0){
        printf("failed to set key:");
        mbedtls_strerror(result,error,sizeof(error));
        printf("%s\n",error);
        mbedtls_aes_free(&aes_context);
        return -1;
    }

    while(1){
        result = fread(input_buffer,1,FILE_BUFFER_SIZE,plain_file);
        real_input_length = result;
        if(result != FILE_BUFFER_SIZE){
            if(ferror(plain_file) != 0){
                printf("failed to read plain file\n");
                fclose(plain_file);
                fclose(encrypted_file);
                mbedtls_aes_free(&aes_context);
                return -2;
            }
            if(feof(plain_file)){
                //到达文件尾部,需进行PKCS#7填充
                /*
                AES为分组加密算法,一个分组长度必须为16字节,所有待加密数据必须为16字节的整倍数。
                若待加密数据不为16字节的整倍数,则必须先填充到16字节整倍数。常用填充方式有PKCS5和PKCS7,通常使用PKCS7填充
                */
                //PKCS7填充到16字节整倍数,若待加密数据正好为16字节整倍数,仍需填充16个字节
                //计算当前长度距离16整倍数的差值,此值即为填充值
                padding = 16 - (result%16);
                padding_code = (char)padding;
                //real_input_length即为经过填充后待加密数据的长度,值必然为16整倍数,同时此长度也为加密后的密文长度
                real_input_length = result + padding;
                if(real_input_length%16 != 0){
                    //填充后必然为16字节整倍数,若不为16字节整倍数,则填充存在问题
                    printf("failed to padding\n");
                    return -3;
                }
                //进行填充
                memset(input_buffer + result,padding_code,padding);
                //设置达到文件尾部标志
                eof_flag = 1;
            }
        }
        //进行加密
        result = mbedtls_aes_crypt_cbc(&aes_context, MBEDTLS_AES_ENCRYPT, real_input_length,iv,input_buffer,output_buffer);
        if(result != 0){
            printf("failed to encrypt data:");
            mbedtls_strerror(result,error,sizeof(error));
            printf("%s\n",error);
            fclose(plain_file);
            fclose(encrypted_file);
            mbedtls_aes_free(&aes_context);
            return -4;
        }
        result = fwrite(output_buffer,1,real_input_length,encrypted_file);
        if(result != real_input_length){
            printf("failed to write encrypted data to file\n");
            fclose(plain_file);
            fclose(encrypted_file);
            mbedtls_aes_free(&aes_context);
            return -5;
        }
        if(eof_flag == 1){
            break;
        }
    }

    mbedtls_aes_free(&aes_context);
    fclose(plain_file);
    fclose(encrypted_file);
    return 0;

}

3.5.2 解密

因解密时,依靠feof判断是否读取到文件末尾,从而确定何时进行去填充的操作,此时需注意一种特殊情况:当加密后的文件正好为FILE_BUFFER_SIZE的整倍数时,在某一轮的读取过程中,正好整个文件读取完成,此时feof无法判断出文件已结束导致无法去除填充,为避免此种情况,需再往后读取一个字节,确保下一轮可读取到内容。

/*
 * 从文件中读取密文数据,并使用AES CBC模式进行解密,解密后的明文同样输出到文件中
 *
 * 输入:
 *      encrypted_file_path:待解密的密文文件路径
 *      decrypted_file_path:解密后的明文文件保存路径
 * 返回值:0:成功,其他值:失败。
 *
 * */
int decrypt_data_from_file_by_aes_cbc(const char *encrypted_file_path,const char *decrypted_file_path){
    mbedtls_aes_context aes_context;
    int result = 0;
    int padding = 0;
    unsigned char padding_code;
    int eof_flag = 0;
    int input_length = 0;
    //AES密钥
    const unsigned char key[16] = "1234567890ABCDEF";
    //当使用CBC/CFB/OFB等模式时,需要一个初始化向量IV
    unsigned char iv[16] = "0099887766554433";
    unsigned char error[64];

    //读写文件的buffer,长度应为16整倍数
    unsigned char input_buffer[FILE_BUFFER_SIZE];
    unsigned char output_buffer[FILE_BUFFER_SIZE];

    FILE *encrypted_file = fopen(encrypted_file_path,"rb");
    FILE *decrypted_file = fopen(decrypted_file_path,"wb");

    mbedtls_aes_init(&aes_context);
    //若使用AES-128,则必要长度为128bits。若使用AES-256,则密钥长度为256bits
    result = mbedtls_aes_setkey_dec(&aes_context, key, 128);
    if(result != 0){
        printf("failed to set key:");
        mbedtls_strerror(result,error,sizeof(error));
        printf("%s\n",error);
        mbedtls_aes_free(&aes_context);
        return -1;
    }

    /**
     * 当加密后的文件正好为FILE_BUFFER_SIZE的整倍数时,在某一轮的读取过程中,正好整个文件读取完成,此时feof无法判断出文件已结束
     * 导致无法去除填充,为避免此种情况,需再往后读取一个字节,确保下一轮可读取到内容。
     * */
    while(1){
        result = fread(input_buffer,1,FILE_BUFFER_SIZE,encrypted_file);
        input_length = result;
        if(result != FILE_BUFFER_SIZE){
            if(ferror(encrypted_file)){
                printf("failed to read encrypted file\n");
                fclose(encrypted_file);
                fclose(decrypted_file);
                mbedtls_aes_free(&aes_context);
                return -2;
            }
        }
        //进行解密
        if(input_length > 0){
            //若读取的字节数为0,则表明上一轮正好读取完
            result = mbedtls_aes_crypt_cbc(&aes_context, MBEDTLS_AES_DECRYPT, input_length,iv,input_buffer,output_buffer);
            if(result != 0){
                printf("failed to decrypt data:");
                mbedtls_strerror(result,error,sizeof(error));
                printf("%s\n",error);
                fclose(encrypted_file);
                fclose(decrypted_file);
                mbedtls_aes_free(&aes_context);
                return -3;
            }
        }

        //判断是否到达文件尾部
        if(feof(encrypted_file)){
            //到达文件尾部
            if(input_length%16 != 0){
                printf("encrypted file is illegal!\n");
                return -4;
            }
            //设置达到文件尾部标志
            eof_flag = 1;
            //去除填充
            //读取最后一个值,此值即为填充值的长度
            padding_code = output_buffer[input_length-1];
            padding = (int)padding_code;
            if(padding < 1 || padding > 16){
                printf("padding code is illegal!\n");
                fclose(encrypted_file);
                fclose(decrypted_file);
                mbedtls_aes_free(&aes_context);
                return -6;
            }
            input_length = input_length - padding;
        }else{
            //预读取一个字节,查看本次读取是否正好到文件末尾
            fgetc(encrypted_file);
            if( ferror(encrypted_file)){
                printf("failed to read encrypted file\n");
                fclose(encrypted_file);
                fclose(decrypted_file);
                mbedtls_aes_free(&aes_context);
                return -2;
            }
            if(feof(encrypted_file)){
                //本次正好读取到文件末尾,需去填充
                //设置达到文件尾部标志
                eof_flag = 1;
                //去除填充
                //读取最后一个值,此值即为填充值的长度
                padding_code = output_buffer[input_length-1];
                padding = (int)padding_code;
                if(padding < 1 || padding > 16){
                    printf("padding code is illegal!\n");
                    fclose(encrypted_file);
                    fclose(decrypted_file);
                    mbedtls_aes_free(&aes_context);
                    return -6;
                }
                input_length = input_length - padding;
            }else{
                //后面还有内容,还原本次预读取的位置
                if(fseek(encrypted_file,-1L,SEEK_CUR)){
                    printf("failed to move file point!\n");
                    fclose(encrypted_file);
                    fclose(decrypted_file);
                    mbedtls_aes_free(&aes_context);
                    return -5;
                }
            }
        }

        result = fwrite(output_buffer,1,input_length,decrypted_file);
        if(result != input_length){
            printf("failed to write decrypted data to file\n");
            fclose(decrypted_file);
            fclose(encrypted_file);
            mbedtls_aes_free(&aes_context);
            return -7;
        }
        if(eof_flag == 1){
            break;
        }

    }
    fclose(decrypted_file);
    fclose(encrypted_file);
    mbedtls_aes_free(&aes_context);
    return 0;

}

3.5.3 运行效果

为测试运行效果,特意准备了三个文件

  • 1.txt:文件长度和每次读取的长度一致,即512字节
    在这里插入图片描述

  • 2.txt:长度比每次读取的长度短2个字节,即510字节,实现加密后的密文长度为512字节
    在这里插入图片描述

  • 3.txt:长度大于512字节
    在这里插入图片描述

int result = encrypt_data_from_file_by_aes_cbc("D:\\tmp\\crypto\\aes\\1.txt","D:\\tmp\\crypto\\aes\\encrypted-1.txt");
    if(result == 0){
        printf("succeed to encrypt file\n");
    }else{
        printf("failed to encrypt file\n");
        return -4;
    }
    result = decrypt_data_from_file_by_aes_cbc("D:\\tmp\\crypto\\aes\\encrypted-1.txt","D:\\tmp\\crypto\\aes\\decrypted-1.txt");
    if(result == 0){
        printf("succeed to decrypt file\n");
    }else{
        printf("failed to decrypt file\n");
        return -5;
    }
    
    result = encrypt_data_from_file_by_aes_cbc("D:\\tmp\\crypto\\aes\\2.txt","D:\\tmp\\crypto\\aes\\encrypted-2.txt");
    if(result == 0){
        printf("succeed to encrypt file\n");
    }else{
        printf("failed to encrypt file\n");
        return -4;
    }
    result = decrypt_data_from_file_by_aes_cbc("D:\\tmp\\crypto\\aes\\encrypted-2.txt","D:\\tmp\\crypto\\aes\\decrypted-2.txt");
    if(result == 0){
        printf("succeed to decrypt file\n");
    }else{
        printf("failed to decrypt file\n");
        return -5;
    }
    
    result = encrypt_data_from_file_by_aes_cbc("D:\\tmp\\crypto\\aes\\3.txt","D:\\tmp\\crypto\\aes\\encrypted-3.txt");
    if(result == 0){
        printf("succeed to encrypt file\n");
    }else{
        printf("failed to encrypt file\n");
        return -4;
    }
    result = decrypt_data_from_file_by_aes_cbc("D:\\tmp\\crypto\\aes\\encrypted-3.txt","D:\\tmp\\crypto\\aes\\decrypted-3.txt");
    if(result == 0){
        printf("succeed to decrypt file\n");
    }else{
        printf("failed to decrypt file\n");
        return -5;
    }

经过对比,解密后的三个文件和原文一致
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

同样使用openssl验证下,查看加密后的密文是否与openssl加密后的密文一致
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 37
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值