概述
互联网时代,安全成为了一个沉重的话题。文件传输、电子邮件等的安全性尤为重要。我们为了保证安全性,必须对其内容加密,
加密的作用就是防止有用或私有化信息在传输链路上被拦截和窃取。提高数据传输的可靠性。
在嵌入式开发中,我们会涉及到数据的传输,文件的传输。很多人都没有考虑其数据的安全性问题,往往都是明文的方式传输,最多增加CRC进行数据的完整性校验。这明显没有考虑数据的安全性问题。
最近项目遇到安全性问题,所以也开始折磨一下加密相关知识,发现RT-THREAD有个比较好软件包--tinycrypt(一个简小并且可配置的加解密软件包,包含算法:aes,base64,md5,sha1,sha2)。其中:AES刚好符合我的项目需求。
AES加密算法
AES加密标准又称为高级加密标准Rijndael加密法,是美国国家标准技术研究所NIST旨在取代DES的21世纪的加密标准。AES的基本要求是,采用对称分组密码体制,密钥长度可以为128、192或256位,分组长度128位。AES算法是最为常见的额对称加密算法之一。
AES加密流程说明:
AES的加解密流程图如下:
加解密流程图部件说明:
明文P:没有经过加密的数据或文件。
密钥K:用来加密明文P的密钥,在对称加密算法中,加密与解密的密钥是相同的。密钥为接收方与发送方协商产生,但不可以直接在网络上传输,否则会导致密钥泄漏,通常是通过非对称加密算法加密密钥,然后再通过网络传输给对方,或者直接面对面商量密钥。密钥是绝对不可以泄漏的,否则会被攻击者还原密文,窃取机密数据。
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。
AES加密方法:
AES为分组加密,分组加密也就是把明文分成一组一组的,每组的长度相等,每次加密一组数据,直到加密完整个明文。
AES | 密钥长度(32bit) | 分组长度(32bit) | 加密轮数 |
---|---|---|---|
AES-128 | 4 | 4 | 10 |
AES-192 | 6 | 4 | 12 |
AES-256 | 8 | 4 | 14 |
在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节,每个字节8位,密钥的长度可以使用128位、192位或者258位。密钥的长度不同,推荐加密轮数也不同,比如AES-128也就是密钥的长度为128位,加密轮数为10轮,AES-192为12轮,AES-256为14轮。以AES-128为例,加密中一轮的4个操作:
字节代换:AES的字符代换其实就是一个简单的查表操作,AES定义了一个S盒和一个逆S盒。
行位移:就是一个简单的左循环移位操作。
列混合:是通过矩阵相乘来实现的,经过移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵。
轮密钥加:是将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作。
加密:加密第1轮到第9轮的轮函数一样,最后一轮迭代不执行列混合,另外,在第一轮迭代之前,先将明文和原始密钥进行一次异或加密操作。
解密:解密过程仍为10轮,每一轮的操作是加密操作的逆操作。由于AES的4个轮操作都是可逆的,因此,解密操作的一轮就是顺序执行逆行移位、逆字节代换、轮密钥加和逆列混合。同加密操作类似,最后一轮不执行逆列混合,在第1轮解密之前,要执行1次密钥加操作。
tinycrypt的AES使用
tinycrypt这个软件包精简使用起来很方便,其包含了源码和测试用例(AES和MD5),tinycrypt软件包的目录结构如下:
├── include │ ├── tiny_aes.h // aes 头文件 │ ├── tiny_base64.h // base64 头文件 │ ├── tinycrypt_config.h // tinycrypt 配置头文件 │ ├── tinycrypt.h // tinycrypt 头文件 │ ├── tiny_md5.h // md5 头文件 │ ├── tiny_sha1.h // sha1 头文件 │ └── tiny_sha2.h // sha2 头文件 ├── LICENSE ├── README.md ├── samples │ ├── aes_sample.c // aes 测试用例头文件 │ └── md5_sample.c // md5 测试用例头文件 └── src ├── tiny_aes.c // aes 源文件 ├── tiny_base64.c // base64 源文件 ├── tiny_md5.c // md5 头文件 ├── tiny_sha1.c // sha1 头文件 └── tiny_sha2.c // sha2 头文件
在Ubuntu环境下,搭建tinycrypt的环境,测试加解密文件,目录结构如下:
├── main.c ├── makefile └── tinycrypt ├── include │ ├── tiny_aes.h │ ├── tiny_base64.h │ ├── tinycrypt_config.h │ ├── tinycrypt.h │ ├── tiny_md5.h │ ├── tiny_sha1.h │ └── tiny_sha2.h ├── LICENSE ├── README.md ├── samples │ ├── aes_sample.c │ └── md5_sample.c └── src ├── tiny_aes.c ├── tiny_base64.c ├── tiny_md5.c ├── tiny_sha1.c └── tiny_sha2.c
在目录中增加makefile文件和main.c,其中makefile用于构建工程,main.c用于编写我们的测试代码,其中:
makefile文件内容:
VERSION = 1.0.0 # 版本号 SOURCE = main.c # 源文件 SOURCE += $(wildcard ./tinycrypt/src/*.c) # 通过wildcard函数获取tinycrypt源文件 OBJECT = $(patsubst %.c, %.o, $(SOURCE)) # 通过patsubst函数替换源文件为目标文件 INCLUDE = -I ./tinycrypt/include # 包含头文件路径 TARGET = tinycrypt # 目标文件名字 CC = gcc # 工具链 CFLAGS = -Wall -g # 编译选项 OUTPUT = output # 输出目录 $(TARGET): $(OBJECT) # 构建执行命令 @mkdir -p $(OUTPUT) # 创建输出目录 $(CC) $^ $(CFLAGS) -o $(OUTPUT)/$(TARGET)_$(VERSION) # 编译 %.o: %.c # 将源文件编译成目标文件 $(CC) $(INCLUDE) $(CFLAGS) -c $< -o $@ .PHONY:clean # 执行make clean之后,清除过程文件 clean: @rm -rf $(OBJECT) $(OUTPUT)
main.c文件内容,测试历程说明:将一个文件(index.js)通过AES加密,然后生成加密文件(encrypt_index.js),再将加密文件(encrypt_index.js)通过AES解密,生成解密文件(decrypt_index.js)。
#include#include #include #include #include #include #include #include #include "tiny_aes.h" #define ENCRYPT_TEST_FILE "./index.js" #define ENCRYPT_TEST_ENCRYPT_FILE "./encrypt_index.js" #define ENCRYPT_TEST_DECRYPT_FILE "./decrypt_index.js" #define ENCRYPT_SINGLE_SIZE 1024 // 加密单包长度 #define ENCRYPT_AES_IV_LEN 16 // AES向量长度 #define ENCRYPT_AES_KEY_LEN 256 // AES密钥长度 #define ENCRYPT_AES_IV "0123456789ABCDEF" // AES向量 #define ENCRYPT_AES_KEY "0123456789ABCDEF0123456789ABCDEF" // AES密钥 // AES参数结构体定义 typedef struct { uint8_t aesIv[ENCRYPT_AES_IV_LEN]; uint8_t aesKey[ENCRYPT_AES_KEY_LEN]; tiny_aes_context aes_ctx; } EncryptInfo; static EncryptInfo g_info = {0}; // aes 参数初始化 void aes_init(const char *iv, const char *key) { memset(g_info.aesIv, 0, ENCRYPT_AES_IV_LEN); memset(g_info.aesKey, 0, ENCRYPT_AES_KEY_LEN); if (iv == NULL) { memcpy(g_info.aesIv, ENCRYPT_AES_IV, strlen(ENCRYPT_AES_IV)); } else { memcpy(g_info.aesIv, iv, strlen(iv)); } if(key == NULL) { memcpy(g_info.aesKey, ENCRYPT_AES_KEY, strlen(ENCRYPT_AES_KEY)); } else { memcpy(g_info.aesKey, key, strlen(key)); } } // aes加密函数的封装 void aes_encrypt(uint8_t *dest_buff, const uint8_t *src_buff, uint32_t len) { tiny_aes_crypt_cbc(&g_info.aes_ctx, AES_ENCRYPT, len, g_info.aesIv, (uint8_t *)src_buff, dest_buff); } // aes解密函数的封装 void aes_decrypt(uint8_t *dest_buff, const uint8_t *src_buff, uint32_t len) { tiny_aes_crypt_cbc(&g_info.aes_ctx, AES_DECRYPT, len, g_info.aesIv, (uint8_t *)src_buff, dest_buff); } int main(int argc, char *argv[]) { int raw_size = 0; int src_fd = -1; int encrypt_fd = -1; int decrypt_fd = -1; int src_file_size = 0; int srv_file_offset = 0; uint8_t src_buff[ENCRYPT_SINGLE_SIZE]; uint8_t encrypt_buff[ENCRYPT_SINGLE_SIZE]; uint8_t decrypt_buff[ENCRYPT_SINGLE_SIZE]; // 加密文件,将明文文件(index.js)通过AES加密为加密文件(encrypt_index.js) { aes_init(NULL, NULL); // aes 初始化 tiny_aes_setkey_enc(&g_info.aes_ctx, (uint8_t *)g_info.aesKey, ENCRYPT_AES_KEY_LEN); // 设置密钥 src_fd = open(ENCRYPT_TEST_FILE, O_RDONLY, 0777); // 打开明文文件 if (src_fd == -1) { printf("%d-ERR: open file(%s) failed ", __LINE__, ENCRYPT_TEST_FILE); return -1; } encrypt_fd = open(ENCRYPT_TEST_ENCRYPT_FILE, O_RDWR | O_CREAT | O_TRUNC, 0777); // 打开加密文件 if (encrypt_fd == -1) { printf("%d-ERR: open file(%s) failed ", __LINE__, ENCRYPT_TEST_ENCRYPT_FILE); return -1; } raw_size = lseek(src_fd, 0, SEEK_END); // 获取明文文件的大小 srv_file_offset = 0; lseek(src_fd, 0, SEEK_SET); printf("%d-INFO: source file size: %d ", __LINE__, raw_size); while (srv_file_offset < raw_size) { memset(src_buff, 0, ENCRYPT_SINGLE_SIZE); memset(encrypt_buff, 0, ENCRYPT_SINGLE_SIZE); read(src_fd, src_buff, ENCRYPT_SINGLE_SIZE); // 读取明文文件内容 aes_encrypt(encrypt_buff, src_buff, ENCRYPT_SINGLE_SIZE); // 明文文件内容加密 write(encrypt_fd, encrypt_buff, ENCRYPT_SINGLE_SIZE); // 将加密内容写入加密文件中 srv_file_offset += ENCRYPT_SINGLE_SIZE; } close(src_fd); src_fd = -1; close(encrypt_fd); encrypt_fd = -1; } // 解密文件,将加密文件(encrypt_index.js)通过AES解密为解密文件(decrypt_index.js) { aes_init(NULL, NULL); // aes 初始化 tiny_aes_setkey_dec(&g_info.aes_ctx, (uint8_t *)g_info.aesKey, ENCRYPT_AES_KEY_LEN); // 设置密钥 src_fd = open(ENCRYPT_TEST_ENCRYPT_FILE, O_RDONLY, 0777); // 打开密文文件 if (src_fd == -1) { printf("%d-ERR: open file(%s) failed ", __LINE__, ENCRYPT_TEST_ENCRYPT_FILE); return -1; } decrypt_fd = open(ENCRYPT_TEST_DECRYPT_FILE, O_RDWR | O_CREAT | O_TRUNC, 0777); // 打开解密文件 if (decrypt_fd == -1) { printf("%d-ERR: open file(%s) failed ", __LINE__, ENCRYPT_TEST_DECRYPT_FILE); return -1; } src_file_size = raw_size; // 设置明文文件的大小 srv_file_offset = 0; lseek(src_fd, 0, SEEK_SET); printf("%d-INFO: encrypt file size: %d ", __LINE__, src_file_size); while (srv_file_offset < src_file_size) { memset(src_buff, 0, ENCRYPT_SINGLE_SIZE); memset(decrypt_buff, 0, ENCRYPT_SINGLE_SIZE); if((srv_file_offset + ENCRYPT_SINGLE_SIZE) < src_file_size) { read(src_fd, src_buff, ENCRYPT_SINGLE_SIZE); // 读取密文文件内容 aes_decrypt(decrypt_buff, src_buff, ENCRYPT_SINGLE_SIZE); // 密文文件解密 write(decrypt_fd, decrypt_buff, ENCRYPT_SINGLE_SIZE); // 将解密内容写入解密文件中 srv_file_offset += ENCRYPT_SINGLE_SIZE; } else { read(src_fd, src_buff, ENCRYPT_SINGLE_SIZE); // 读取最后一包密文文件内容 aes_decrypt(decrypt_buff, src_buff, (ENCRYPT_SINGLE_SIZE)); // 密文文件解密 write(decrypt_fd, decrypt_buff, (src_file_size - srv_file_offset)); // 将最后一包解密内容写入解密文件中 break; } } close(src_fd); src_fd = -1; close(decrypt_fd); decrypt_fd = -1; } return 0; }
验证时发现一个问题,加密内容的长度需要16字节的整数倍,所以加密完的文件大小也为16字节的整数倍。值得注意的是,解密的最后一包要根据明文文件的大小算出来的,然后写进解密文件中,因为加密文件是16字节对齐的,所以要去除16字节对齐。
编译及运行:
rice@rice:~/project/encrypt$ make gcc -I ./tinycrypt/include -Wall -g -c main.c -o main.o gcc -I ./tinycrypt/include -Wall -g -c tinycrypt/src/tiny_sha1.c -o tinycrypt/src/tiny_sha1.o gcc -I ./tinycrypt/include -Wall -g -c tinycrypt/src/tiny_aes.c -o tinycrypt/src/tiny_aes.o gcc -I ./tinycrypt/include -Wall -g -c tinycrypt/src/tiny_md5.c -o tinycrypt/src/tiny_md5.o gcc -I ./tinycrypt/include -Wall -g -c tinycrypt/src/tiny_sha2.c -o tinycrypt/src/tiny_sha2.o gcc -I ./tinycrypt/include -Wall -g -c tinycrypt/src/tiny_base64.c -o tinycrypt/src/tiny_base64.o gcc main.o tinycrypt/src/tiny_sha1.o tinycrypt/src/tiny_aes.o tinycrypt/src/tiny_md5.o tinycrypt/src/tiny_sha2.o tinycrypt/src/tiny_base64.o -Wall -g -o output/tinycrypt_1.0.0 rice@rice:~/ohos/project/encrypt$ ./output/tinycrypt_1.0.0 136-INFO: source file size: 445 171-INFO: encrypt file size: 445 rice@rice:~/ohos/project/encrypt$
演示结果:
结论:
通过加密方式,提高数据的安全性,避免了数据的泄露
AES加密方式,加密内容的长度需要16字节的整数倍,加密之后长度比实际明文可能要长。所以明文的长度需要告知解密方。
AES是对称加密,所以加密方和解密方的密钥需要一直。
编辑:黄飞
评论
查看更多