前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >极简加解密库--使数据传输更加安全

极简加解密库--使数据传输更加安全

作者头像
Rice加饭
修改2023-03-24 09:15:32
5000
修改2023-03-24 09:15:32
举报
文章被收录于专栏:Rice嵌入式Rice嵌入式

概述

  • 互联网时代,安全成为了一个沉重的话题。文件传输、电子邮件等的安全性尤为重要。我们为了保证安全性,必须对其内容加密,
  • 加密的作用就是防止有用或私有化信息在传输链路上被拦截和窃取。提高数据传输的可靠性。
  • 在嵌入式开发中,我们会涉及到数据的传输,文件的传输。很多人都没有考虑其数据的安全性问题,往往都是明文的方式传输,最多增加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加密方法:
  1. AES为分组加密,分组加密也就是把明文分成一组一组的,每组的长度相等,每次加密一组数据,直到加密完整个明文。

AES

密钥长度(32bit)

分组长度(32bit)

加密轮数

AES-128

4

4

10

AES-192

6

4

12

AES-256

8

4

14

  1. 在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. 加密:加密第1轮到第9轮的轮函数一样,最后一轮迭代不执行列混合,另外,在第一轮迭代之前,先将明文和原始密钥进行一次异或加密操作。
  2. 解密:解密过程仍为10轮,每一轮的操作是加密操作的逆操作。由于AES的4个轮操作都是可逆的,因此,解密操作的一轮就是顺序执行逆行移位、逆字节代换、轮密钥加和逆列混合。同加密操作类似,最后一轮不执行逆列混合,在第1轮解密之前,要执行1次密钥加操作。

tinycrypt的AES使用

  1. tinycrypt这个软件包精简使用起来很方便,其包含了源码和测试用例(AES和MD5),tinycrypt软件包的目录结构如下:
代码语言:javascript
复制
.
├── 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 头文件
  1. 在Ubuntu环境下,搭建tinycrypt的环境,测试加解密文件,目录结构如下:
代码语言:javascript
复制
.
├── 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文件内容:
代码语言:javascript
复制
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)。
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <fcntl.h>
#include <unistd.h>

#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\r\n", __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\r\n", __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\r\n", __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\r\n", __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\r\n", __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\r\n", __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;
}
  1. 验证时发现一个问题,加密内容的长度需要16字节的整数倍,所以加密完的文件大小也为16字节的整数倍。值得注意的是,解密的最后一包要根据明文文件的大小算出来的,然后写进解密文件中,因为加密文件是16字节对齐的,所以要去除16字节对齐。
  2. 编译及运行:
代码语言:javascript
复制
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$ 
  1. 演示结果:

结论:

  1. 通过加密方式,提高数据的安全性,避免了数据的泄露
  2. AES加密方式,加密内容的长度需要16字节的整数倍,加密之后长度比实际明文可能要长。所以明文的长度需要告知解密方。
  3. AES是对称加密,所以加密方和解密方的密钥需要一直。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rice 嵌入式开发技术分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • AES加密算法
    • AES加密流程说明:
      • AES加密方法:
      • tinycrypt的AES使用
      • 结论:
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档