最近有一个实现RSA加密的任务,要在ESP32上面做,首先我尝试了openssl的方案,结果做完了才发现Arduino不支持动态链接C语言库…在网上找了好久的资料,终于找到一个叫做mbedtls的库,奈何相关的文章实在是太少太少,好多都是注水的文章。为了造福挣扎于嵌入式苦海的小伙伴们,下面我将介绍如何使用Mbed TLS实现在ESP32上的RSA加密。
开发环境
上位机:Mac Pro 64位
下位机:ESP32 DevKitC V4
IDE:VSCode
编译工具:Arduino CLI(不会用的可以参考一下我之前的这篇文章)
用Arduino IDE应该也是能做的,有兴趣的可以自己尝试一下。
Mbed TLS简介
ARM mbedtls使开发人员可以非常轻松地在嵌入式产品中加入加密和 SSL/TLS 功能。它提供了具有直观的 API 和可读源代码的 SSL 库。该工具即开即用,可以在大部分系统上直接构建它,也可以手动选择和配置各项功能。
mbedtls 库提供了一组可单独使用和编译的加密组件,还可以使用单个配置头文件加入或排除这些组件。 从功能角度来看,该mbedtls分为三个主要部分:
- SSL/TLS 协议实施。
- 一个加密库。
- 一个 X.509 证书处理库。
这是它的老版官网:
SSL Library mbed TLS / PolarSSL
最新的地址是:
Mbed TLS
github项目主页:
https://github.com/ARMmbed/mbedtls
RSA加密特性介绍
密钥长度、明文长度、密文长度
使用1024bit密钥进行加密时,密钥长度为1024/8 = 128 bytes,虽然公钥文件和私钥文件的长度看起来不一样,其实解析之后都是128字节,可以自己在程序代码中添加打印rsa.len 的操作进行查看。
明文长度,对于不同的填充方式来说是不一样的:
- RSA_PKCS1_PADDING 填充模式,最常用的模式,也是我这里使用的模式。这种模式下,所用的明文长度必须比密钥长度少11字节,也就是说上限为117字节。如果输入的明文过长,必须切割,然后填充。
- RSA_PKCS1_OAEP_PADDING,是PKCS#1推出的新的填充方式,安全性是最高的,和前面RSA_PKCS1_PADDING的区别就是加密前的编码方式不一样。这种填充方式要求明文长度比密钥长度少41字节(也有42字节的说法,看具体函数的要求)。
- RSA_NO_PADDING,不填充。如果你的明文不够128字节,加密的时候会在你的明文前面,前向的填充零。这种填充模式下,明文长度可以和RSA密钥长度相等,如果输入的明文过长,必须切割,然后填充。
密文长度,三种方式下均为128字节。
问题分析
为了实现在ESP32上进行RSA加密,我们需要解决两个问题:
- 文件系统
- 密钥格式
由于ESP32没有自带的文件系统(关于spiffs是否可行,我没有进行测试),所以这里我采用了将密钥储存在字符串中的方式。
因为我之前用mbedtls_pk_encrypt 进行加密时无法选择填充模式,所以我改用了 mbedtls_rsa_pkcs1_encrypt (两种方式的区别,点这里),去网上找了个demo是用mbedtls_mpi_read_file 去读取密钥信息的,我搜了一下这个函数,有个类似的函数叫mbedtls_mpi_read_string 。但如果你用mbedtls_mpi_read_string 函数去读取pem格式的公钥的话,会失败,因为mbedtls的RSA加密默认支持的格式不是pem格式(很奇怪,如果用mbedtls_pk_parse_key 解析却是可以成功的)。
后来,我找到一个大佬实现的RSA使用pem格式公钥的代码,帮了我大忙:
mbedtls rsa使用pem文件_RIGOU精电科技的博客-CSDN博客_mbedtls pem文件
不过这里还是使用读写文件来实现的,我在此基础上进行了一些修改,最终达到了我的预期目标,代码贴在后面了。
准备工作
Arduino库下载地址:https://johanneskinzig.de/index.php/files/26/Arduino-mbedtls/9/gettingstartedmbedtlsarduino.7z
这个文件里面包含了Arduino的mbedtls库和一些示例代码。
下载完成后,将src/lib_mbedtls 文件夹拷贝到libraries中,并重命名为mbedtls (不然你可能会遇到一些很蛋疼的冲突问题):
开始编程
整个程序的代码如下所示(密钥的部分我已经隐去,复制自己的密钥进去就行):
#include "mbedtls/rsa.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/pk.h"
#include "mbedtls/platform.h"
#define mbedtls_printf printf
#define mbedtls_exit exit
const char* my_public_key = "-----BEGIN PUBLIC KEY-----\n"
"-----END PUBLIC KEY-----";
const char* my_private_key = "-----BEGIN RSA PRIVATE KEY-----\n"
"-----END RSA PRIVATE KEY-----";
int rsa_encrypt(unsigned char* plaintext, unsigned char* ciphertext, int msg_length)
{
clock_t start, finish;
double duration;
start = clock();
mbedtls_printf("\nEncryption Begins.\n");
printf("\nplaintext = %s\n", plaintext);
long size;
size_t n;
unsigned char* pub_key = NULL;
mbedtls_pk_context ctx_pk;
/*********************************************/
int ret = 1;
size_t i;
mbedtls_rsa_context rsa;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// unsigned char plaintext[1024];
unsigned char buf[128];
const char* pers = "rsa_encrypt";
mbedtls_mpi N, E; //定义一个大数,也就是公钥
/*****************************************************/
mbedtls_pk_init(&ctx_pk);
int length = strlen(my_public_key);
pub_key = (unsigned char *)(malloc(length+1));
strcpy((char*)pub_key, my_public_key);
// if( ( ret = mbedtls_pk_parse_public_key( &pk, pub_key, length+1) ) != 0 )
if (0 != mbedtls_pk_parse_public_key(&ctx_pk, pub_key, length + 1))
{
mbedtls_printf("\n . Can't import public key");
}
else
{
mbedtls_printf("\n . Import public key successfully");
}
free(pub_key);
pub_key = NULL;
/*****************************************************************/
mbedtls_printf("\n . Seeding the random number generator ...");
// memset(plaintext, 0, sizeof(plaintext));
memset(ciphertext, 0, sizeof(ciphertext));
fflush(stdout);
mbedtls_mpi_init(&N);
mbedtls_mpi_init(&E);
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V15, 0);
mbedtls_ctr_drbg_init(&ctr_drbg); //初始化ctr drbg结构体,用于随机数的生成
mbedtls_entropy_init(&entropy); //初始化熵源
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char*)pers, strlen(pers)); //生成随机数
if (ret != 0)
{
mbedtls_printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret);
goto exit;
}
/*导入pem内的公钥*/
rsa = *(mbedtls_rsa_context*)ctx_pk.pk_ctx;
/*
* Calculate the RSA encryption of the hash.
*/
mbedtls_printf("\n . Generating the RSA encrypted value ...");
fflush(stdout);
/*加密操作,利用公钥加密*/
ret = mbedtls_rsa_pkcs1_encrypt(&rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, msg_length, plaintext, ciphertext);
if (ret != 0)
{
mbedtls_printf(" failed\n ! mbedtls_rsa_pkcs1_encrypt returned %d\n\n", ret);
goto exit;
}
mbedtls_printf("\n\nEncryption Done.\n");
exit:
/*释放资源*/
mbedtls_mpi_free(&N);
mbedtls_mpi_free(&E);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
mbedtls_rsa_free(&rsa);
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "Encryption costs %f seconds\n\n", duration );
return 0;
}
int rsa_decrypt(unsigned char* ciphertext, unsigned char* plaintext)
{
clock_t start, finish;
double duration;
start = clock();
mbedtls_printf("Decryption Begins.\n");
printf("\nciphertext = %s\n", ciphertext);
FILE *f = NULL;
char *str = NULL;
long size;
size_t n;
unsigned char* priv_key = NULL;
mbedtls_pk_context ctx_pk;
/*******************************/
int ret = 1;
int c;
size_t i;
mbedtls_rsa_context rsa;
mbedtls_mpi N, P, Q, D, E, DP, DQ, QP; //定义大数
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// unsigned char result[1024];
// unsigned char buf[128];
const char* pers = "rsa_decrypt";
// memset(result, 0, sizeof(result));
/*********************************/
memset(plaintext, 0, sizeof(plaintext));
mbedtls_pk_init(&ctx_pk);
int length = strlen(my_private_key);
priv_key = (unsigned char *)(malloc(length+1));
strcpy((char*)priv_key, my_private_key);
// if( ( ret = mbedtls_pk_parse_public_key( &pk, pub_key, length+1) ) != 0 )
if (0 != mbedtls_pk_parse_key(&ctx_pk, priv_key, length + 1, NULL, 0))
{
mbedtls_printf("\n . Can't import private key");
}
else
{
mbedtls_printf("\n . Import private key successfully");
}
free(priv_key);
priv_key = NULL;
mbedtls_printf("\n . Seeding the random number generator ...");
fflush(stdout);
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V15, 0);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
mbedtls_mpi_init(&N);
mbedtls_mpi_init(&P);
mbedtls_mpi_init(&Q);
mbedtls_mpi_init(&D);
mbedtls_mpi_init(&E);
mbedtls_mpi_init(&DP);
mbedtls_mpi_init(&DQ);
mbedtls_mpi_init(&QP);
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
&entropy, (const unsigned char*)pers,
strlen(pers));
if (ret != 0)
{
mbedtls_printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n",
ret);
goto exit;
}
/*导入pem内的私钥*/
rsa = *(mbedtls_rsa_context*)ctx_pk.pk_ctx;
if ((ret = mbedtls_rsa_complete(&rsa)) != 0)
{
mbedtls_printf(" failed\n ! mbedtls_rsa_complete returned %d\n\n", ret);
goto exit;
}
/*
* Decrypt the encrypted RSA data and print the plaintext.
*/
mbedtls_printf("\n . Decrypting the encrypted data ...");
fflush(stdout);
ret = mbedtls_rsa_pkcs1_decrypt(&rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, &i, ciphertext, plaintext, 1024);
if (ret != 0)
{
mbedtls_printf(" failed\n ! mbedtls_rsa_pkcs1_decrypt returned %d\n\n", ret);
goto exit;
}
mbedtls_printf("\n\nThe decrypted result is: '%s'\n", plaintext);
mbedtls_printf("\nDecryption Done.\n");
exit:
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
mbedtls_rsa_free(&rsa);
mbedtls_mpi_free(&N);
mbedtls_mpi_free(&P);
mbedtls_mpi_free(&Q);
mbedtls_mpi_free(&D);
mbedtls_mpi_free(&E);
mbedtls_mpi_free(&DP);
mbedtls_mpi_free(&DQ);
mbedtls_mpi_free(&QP);
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "Decryption costs %f seconds\n\n", duration );
return 0;
}
void setup() {
printf("Welcome to test the RSA demo!\n");
}
void loop() {
char *msg = "I hate coding!";
unsigned char plaintext[117];
unsigned char ciphertext[128];
memcpy(plaintext, msg, strlen(msg));
plaintext[strlen(msg)] = '\0';
// printf("msg length = %d\n", strlen(msg)+1);
int msg_length = strlen(msg) + 1;
if(msg_length > 117)
{
printf("msg length must be less than 116 bytes!\n");
}
rsa_encrypt(plaintext, ciphertext, strlen(msg) + 1);
rsa_decrypt(ciphertext, plaintext);
delay(1000);
}
我一开始只改了读写文件相关的代码,但是后来发现加密的结果不对,输出一长串字符,最后的几位就是我的明文,那不等于没加密嘛!后来我把老哥代码里面的input和output长度从1024和512改成了117和128,就能跑通了。
编译,烧录,查看串口输出:
在ESP32上加密一条消息只花了0.02秒,这个速度是令人相当满意的。
参考文章
Getting started with Mbedtls on ARM-based Arduinos
RSA SHA-256 Encrypt String on ESP32
How to encrypt and decrypt with RSA
RSA的1024位是指公钥及私钥分别是1024bit,也就是1024/8=128 Bytes_任家_新浪博客
RSA非对称加解密算法填充方式(Padding)_浴血重生-学习空间-CSDN博客_rsa填充模式
rsa加密–选择padding模式需要注意的问题。。。
使用Mbedtls做文件签名及校验 - microsun - 博客园
|