最近有一个实现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的操作进行查看。

明文长度,对于不同的填充方式来说是不一样的:

  1. RSA_PKCS1_PADDING 填充模式,最常用的模式,也是我这里使用的模式。这种模式下,所用的明文长度必须比密钥长度少11字节,也就是说上限为117字节。如果输入的明文过长,必须切割,然后填充。
  2. RSA_PKCS1_OAEP_PADDING,是PKCS#1推出的新的填充方式,安全性是最高的,和前面RSA_PKCS1_PADDING的区别就是加密前的编码方式不一样。这种填充方式要求明文长度比密钥长度少41字节(也有42字节的说法,看具体函数的要求)。
  3. RSA_NO_PADDING,不填充。如果你的明文不够128字节,加密的时候会在你的明文前面,前向的填充零。这种填充模式下,明文长度可以和RSA密钥长度相等,如果输入的明文过长,必须切割,然后填充。

密文长度,三种方式下均为128字节。

问题分析

为了实现在ESP32上进行RSA加密,我们需要解决两个问题:

  1. 文件系统
  2. 密钥格式

由于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 - 博客园

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐