一、非对称加密算法——RSA

1. 对称加密算法和非对称加密算法

对称加密算法,比如AES算法,在发送端(加密)和接收端(解密)使用相同的一份秘钥,称为共享秘钥。

非对称加密算法,比如RSA算法,在加密使用公钥,而在解密时使用私钥。

2. RSA算法

RSA算法是一种常用的非对称加密算法。

RSA算法主要包括三个过程:

生成秘钥对

使用公钥加密明文

使用私钥解密密文

3. RSA加速技术

RSA算法在计算过程中存在较多的取模运算和幂运算,计算速度比对称加密算法要慢,所以不适合对大量数据进行加密和解密,在实际中常用于加密或解密小数据片段。

RSA公钥加密过程可使用短公开指数进行快速计算,而RSA私钥解密过程可使用中国剩余定理(CRT)进行加速执行。

4. RSA填充方法

RSA加密结果是确定的。当公钥确定的时候,一段明文总是会加密的到对应的密文,这样未免存在安全隐患。

实际使用RSA算法中需要包含填充方案,在计算之前会对明文进行随机注入,这样在公钥和明文相同的情况下,也不会生成相同的秘文。

常用的RSA填充方案有两种:RSAES-OAEP(推荐使用)和RSAES-PKCS1V1_5(较早、不推荐使用)。



二、RSA功能模块的配置与使用

1. 配置宏

使用RSA功能需要提前开启伪随机数生成器(依赖AES算法、SHA256算法、MD通用接口),其中伪随机数生成器的硬件适配移植实现已经讲述,请参考对应的第二篇博客,不再赘述。

综合上述,本实验中需要开启的宏如下表:

宏定义 功能

MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES 不使用默认熵源(如果已有、要屏蔽该宏)

MBEDTLS_NO_PLATFORM_ENTROPY 不使用系统内置熵源

MBEDTLS_AES_C 使用AES算法

MBEDTLS_ENTROPY_C 使能熵源模块

MBEDTLS_CTR_DRBG_C 使能随机数模块

MBEDTLS_SHA256_C 使能SHA256算法

MBEDTLS_MD_C 开启MD通用接口

MBEDTLS_AES_ROM_TABLES 使能预定义S盒(节约内存空间)

MBEDTLS_BIGNUM_C 开启大数运算

MBEDTLS_GENPRIME 开启生成素数

MBEDTLS_OID_C 开启OID数据结构模块

MBEDTLS_RSA_C 开启RSA算法

MBEDTLS_PKCS1_V21 开启PKCS#1 v2.1填充方案(OAEP)

下面补充几个第一次出现宏的定义。

① MBEDTLS_BIGNUM_C

/**
 * \def MBEDTLS_BIGNUM_C
 *
 * Enable the multi-precision integer library.
 *
 * Module:  library/bignum.c
 * Caller:  library/dhm.c
 *          library/ecp.c
 *          library/ecdsa.c
 *          library/rsa.c
 *          library/rsa_internal.c
 *          library/ssl_tls.c
 *
 * This module is required for RSA, DHM and ECC (ECDH, ECDSA) support.
 */
#define MBEDTLS_BIGNUM_C

② MBEDTLS_GENPRIME

/**
 * \def MBEDTLS_GENPRIME
 *
 * Enable the prime-number generation code.
 *
 * Requires: MBEDTLS_BIGNUM_C
 */
#define MBEDTLS_GENPRIME

③ MBEDTLS_OID_C

/**
 * \def MBEDTLS_OID_C
 *
 * Enable the OID database.
 *
 * Module:  library/oid.c
 * Caller:  library/asn1write.c
 *          library/pkcs5.c
 *          library/pkparse.c
 *          library/pkwrite.c
 *          library/rsa.c
 *          library/x509.c
 *          library/x509_create.c
 *          library/x509_crl.c
 *          library/x509_crt.c
 *          library/x509_csr.c
 *          library/x509write_crt.c
 *          library/x509write_csr.c
 *
 * This modules translates between OIDs and internal values.
 */
#define MBEDTLS_OID_C

④ MBEDTLS_RSA_C

/**
 * \def MBEDTLS_RSA_C
 *
 * Enable the RSA public-key cryptosystem.
 *
 * Module:  library/rsa.c
 *          library/rsa_internal.c
 * Caller:  library/ssl_cli.c
 *          library/ssl_srv.c
 *          library/ssl_tls.c
 *          library/x509.c
 *
 * This module is used by the following key exchanges:
 *      RSA, DHE-RSA, ECDHE-RSA, RSA-PSK
 *
 * Requires: MBEDTLS_BIGNUM_C, MBEDTLS_OID_C
 */
#define MBEDTLS_RSA_C

⑤ MBEDTLS_PKCS1_V21

/**
 * \def MBEDTLS_PKCS1_V21
 *
 * Enable support for PKCS#1 v2.1 encoding.
 *
 * Requires: MBEDTLS_MD_C, MBEDTLS_RSA_C
 *
 * This enables support for RSAES-OAEP and RSASSA-PSS operations.
 */
#define MBEDTLS_PKCS1_V21

编辑针对本实验的配置文件mbedtls_config_rsa.h:

/**
 * @brief   Minimal configuration for RSA Function
 * @author  mculover666
 * @date    2020/09/27
*/

#ifndef _MBEDTLS_CONFIG_RSA_H_
#define _MBEDTLS_CONFIG_RSA_H_

/* System support */
#define MBEDTLS_HAVE_ASM
//#define MBEDTLS_HAVE_TIME

/* mbed feature support */
#define MBEDTLS_ENTROPY_HARDWARE_ALT
//#define MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
#define MBEDTLS_NO_PLATFORM_ENTROPY

/* mbed modules */
#define MBEDTLS_AES_C
#define MBEDTLS_AES_ROM_TABLES
#define MBEDTLS_CTR_DRBG_C
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_SHA256_C
#define MBEDTLS_MD_C
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_GENPRIME
#define MBEDTLS_OID_C
#define MBEDTLS_RSA_C
#define MBEDTLS_PKCS1_V21

#include "mbedtls/check_config.h"

#endif /* _MBEDTLS_CONFIG_RSA_H_ */

在MDK配置mbedtls使用该配置文件:

51c嵌入式~mbedtls~rsa_嵌入式硬件

2. RSA功能模块API说明

使用该功能模块需要包含头文件:

#include "mbedtls/rsa.h"

① 初始化RSA结构体:

/**
 * \brief          This function initializes an RSA context.
 *
 * \note           Set padding to #MBEDTLS_RSA_PKCS_V21 for the RSAES-OAEP
 *                 encryption scheme and the RSASSA-PSS signature scheme.
 *
 * \note           The \p hash_id parameter is ignored when using
 *                 #MBEDTLS_RSA_PKCS_V15 padding.
 *
 * \note           The choice of padding mode is strictly enforced for private key
 *                 operations, since there might be security concerns in
 *                 mixing padding modes. For public key operations it is
 *                 a default value, which can be overridden by calling specific
 *                 \c rsa_rsaes_xxx or \c rsa_rsassa_xxx functions.
 *
 * \note           The hash selected in \p hash_id is always used for OEAP
 *                 encryption. For PSS signatures, it is always used for
 *                 making signatures, but can be overridden for verifying them.
 *                 If set to #MBEDTLS_MD_NONE, it is always overridden.
 *
 * \param ctx      The RSA context to initialize. This must not be \c NULL.
 * \param padding  The padding mode to use. This must be either
 *                 #MBEDTLS_RSA_PKCS_V15 or #MBEDTLS_RSA_PKCS_V21.
 * \param hash_id  The hash identifier of ::mbedtls_md_type_t type, if
 *                 \p padding is #MBEDTLS_RSA_PKCS_V21. It is unused
 *                 otherwise.
 */
void mbedtls_rsa_init( mbedtls_rsa_context *ctx,
                       int padding,
                       int hash_id );

② RSA生成秘钥对:

/**
 * \brief          This function generates an RSA keypair.
 *
 * \note           mbedtls_rsa_init() must be called before this function,
 *                 to set up the RSA context.
 *
 * \param ctx      The initialized RSA context used to hold the key.
 * \param f_rng    The RNG function to be used for key generation.
 *                 This must not be \c NULL.
 * \param p_rng    The RNG context to be passed to \p f_rng.
 *                 This may be \c NULL if \p f_rng doesn't need a context.
 * \param nbits    The size of the public key in bits.
 * \param exponent The public exponent to use. For example, \c 65537.
 *                 This must be odd and greater than \c 1.
 *
 * \return         \c 0 on success.
 * \return         An \c MBEDTLS_ERR_RSA_XXX error code on failure.
 */
int mbedtls_rsa_gen_key( mbedtls_rsa_context *ctx,
                         int (*f_rng)(void *, unsigned char *, size_t),
                         void *p_rng,
                         unsigned int nbits, int exponent );

③ RSA加密操作,通过参数指定公钥加密:

/**
 * \brief          This function adds the message padding, then performs an RSA
 *                 operation.
 *
 *                 It is the generic wrapper for performing a PKCS#1 encryption
 *                 operation using the \p mode from the context.
 *
 * \deprecated     It is deprecated and discouraged to call this function
 *                 in #MBEDTLS_RSA_PRIVATE mode. Future versions of the library
 *                 are likely to remove the \p mode argument and have it
 *                 implicitly set to #MBEDTLS_RSA_PUBLIC.
 *
 * \note           Alternative implementations of RSA need not support
 *                 mode being set to #MBEDTLS_RSA_PRIVATE and might instead
 *                 return #MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED.
 *
 * \param ctx      The initialized RSA context to use.
 * \param f_rng    The RNG to use. It is mandatory for PKCS#1 v2.1 padding
 *                 encoding, and for PKCS#1 v1.5 padding encoding when used
 *                 with \p mode set to #MBEDTLS_RSA_PUBLIC. For PKCS#1 v1.5
 *                 padding encoding and \p mode set to #MBEDTLS_RSA_PRIVATE,
 *                 it is used for blinding and should be provided in this
 *                 case; see mbedtls_rsa_private() for more.
 * \param p_rng    The RNG context to be passed to \p f_rng. May be
 *                 \c NULL if \p f_rng is \c NULL or if \p f_rng doesn't
 *                 need a context argument.
 * \param mode     The mode of operation. This must be either
 *                 #MBEDTLS_RSA_PUBLIC or #MBEDTLS_RSA_PRIVATE (deprecated).
 * \param ilen     The length of the plaintext in Bytes.
 * \param input    The input data to encrypt. This must be a readable
 *                 buffer of size \p ilen Bytes. It may be \c NULL if
 *                 `ilen == 0`.
 * \param output   The output buffer. This must be a writable buffer
 *                 of length \c ctx->len Bytes. For example, \c 256 Bytes
 *                 for an 2048-bit RSA modulus.
 *
 * \return         \c 0 on success.
 * \return         An \c MBEDTLS_ERR_RSA_XXX error code on failure.
 */
int mbedtls_rsa_pkcs1_encrypt( mbedtls_rsa_context *ctx,
                       int (*f_rng)(void *, unsigned char *, size_t),
                       void *p_rng,
                       int mode, size_t ilen,
                       const unsigned char *input,
                       unsigned char *output );

④ RSA解密操作,通过指定私钥解密:

/**
 * \brief          This function performs an RSA operation, then removes the
 *                 message padding.
 *
 *                 It is the generic wrapper for performing a PKCS#1 decryption
 *                 operation using the \p mode from the context.
 *
 * \note           The output buffer length \c output_max_len should be
 *                 as large as the size \p ctx->len of \p ctx->N (for example,
 *                 128 Bytes if RSA-1024 is used) to be able to hold an
 *                 arbitrary decrypted message. If it is not large enough to
 *                 hold the decryption of the particular ciphertext provided,
 *                 the function returns \c MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE.
 *
 * \deprecated     It is deprecated and discouraged to call this function
 *                 in #MBEDTLS_RSA_PUBLIC mode. Future versions of the library
 *                 are likely to remove the \p mode argument and have it
 *                 implicitly set to #MBEDTLS_RSA_PRIVATE.
 *
 * \note           Alternative implementations of RSA need not support
 *                 mode being set to #MBEDTLS_RSA_PUBLIC and might instead
 *                 return #MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED.
 *
 * \param ctx      The initialized RSA context to use.
 * \param f_rng    The RNG function. If \p mode is #MBEDTLS_RSA_PRIVATE,
 *                 this is used for blinding and should be provided; see
 *                 mbedtls_rsa_private() for more. If \p mode is
 *                 #MBEDTLS_RSA_PUBLIC, it is ignored.
 * \param p_rng    The RNG context to be passed to \p f_rng. This may be
 *                 \c NULL if \p f_rng is \c NULL or doesn't need a context.
 * \param mode     The mode of operation. This must be either
 *                 #MBEDTLS_RSA_PRIVATE or #MBEDTLS_RSA_PUBLIC (deprecated).
 * \param olen     The address at which to store the length of
 *                 the plaintext. This must not be \c NULL.
 * \param input    The ciphertext buffer. This must be a readable buffer
 *                 of length \c ctx->len Bytes. For example, \c 256 Bytes
 *                 for an 2048-bit RSA modulus.
 * \param output   The buffer used to hold the plaintext. This must
 *                 be a writable buffer of length \p output_max_len Bytes.
 * \param output_max_len The length in Bytes of the output buffer \p output.
 *
 * \return         \c 0 on success.
 * \return         An \c MBEDTLS_ERR_RSA_XXX error code on failure.
 */
int mbedtls_rsa_pkcs1_decrypt( mbedtls_rsa_context *ctx,
                       int (*f_rng)(void *, unsigned char *, size_t),
                       void *p_rng,
                       int mode, size_t *olen,
                       const unsigned char *input,
                       unsigned char *output,
                       size_t output_max_len );

⑤ 释放RSA结构体:

/**
 * \brief          This function frees the components of an RSA key.
 *
 * \param ctx      The RSA context to free. May be \c NULL, in which case
 *                 this function is a no-op. If it is not \c NULL, it must
 *                 point to an initialized RSA context.
 */
void mbedtls_rsa_free( mbedtls_rsa_context *ctx );

以上都是源码部分

3. 编写测试函数

新建文件mbedtls_rsa_test.c,编写以下测试内容:

这一块贴走使用

/**
 * @brief   RSA Function demo
 * @author  mculover666
 * @date    2020/09/27
*/

#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif

#if defined(MBEDTLS_RSA_C)

#include <stdio.h>
#include "string.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/rsa.h"

char buf[516];

static void dump_rsa_key(mbedtls_rsa_context *ctx)
{
    size_t olen;
    
    printf("\n  +++++++++++++++++ rsa keypair +++++++++++++++++\n\n");
    mbedtls_mpi_write_string(&ctx->N , 16, buf, sizeof(buf), &olen);
    printf("N: %s\n", buf); 

    mbedtls_mpi_write_string(&ctx->E , 16, buf, sizeof(buf), &olen);
    printf("E: %s\n", buf);

    mbedtls_mpi_write_string(&ctx->D , 16, buf, sizeof(buf), &olen);
    printf("D: %s\n", buf);

    mbedtls_mpi_write_string(&ctx->P , 16, buf, sizeof(buf), &olen);
    printf("P: %s\n", buf);

    mbedtls_mpi_write_string(&ctx->Q , 16, buf, sizeof(buf), &olen);
    printf("Q: %s\n", buf);

    mbedtls_mpi_write_string(&ctx->DP, 16, buf, sizeof(buf), &olen);
    printf("DP: %s\n", buf);

    mbedtls_mpi_write_string(&ctx->DQ, 16, buf, sizeof(buf), &olen);
    printf("DQ: %s\n", buf);

    mbedtls_mpi_write_string(&ctx->QP, 16, buf, sizeof(buf), &olen);
    printf("QP: %s\n", buf);
    printf("\n  +++++++++++++++++ rsa keypair +++++++++++++++++\n\n");
}

static void dump_buf(uint8_t *buf, uint32_t len)
{
    int i;
    
    for (i = 0; i < len; i++) {
        printf("%s%02X%s", i % 16 == 0 ? "\r\n\t" : " ", 
                           buf[i], 
                           i == len - 1 ? "\r\n" : "");
    }
}

uint8_t output_buf[2048/8];

int mbedtls_rsa_test(void)
{
    int ret;
    size_t olen;
    const char* msg = "HelloWorld";
    uint8_t decrypt_buf[20];
   
    const char *pers = "rsa_test";
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    mbedtls_rsa_context ctx;

    /* 1. init structure */
    mbedtls_entropy_init(&entropy);
    mbedtls_ctr_drbg_init(&ctr_drbg);
    mbedtls_rsa_init(&ctx, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
    
    /* 2. update seed with we own interface ported */
    printf( "\n  . Seeding the random number generator..." );
    
    ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy,
                               (const unsigned char *) pers,
                               strlen(pers));
    if(ret != 0) {
        printf( " failed\n  ! mbedtls_ctr_drbg_seed returned %d(-0x%04x)\n", ret, -ret);
        goto exit;
    }
    printf( " ok\n" );

    /* 3. generate an RSA keypair */
    printf( "\n  . Generate RSA keypair..." );
    
    ret = mbedtls_rsa_gen_key(&ctx, mbedtls_ctr_drbg_random, &ctr_drbg, 2048, 65537);
    if(ret != 0) {
        printf( " failed\n  ! mbedtls_rsa_gen_key returned %d(-0x%04x)\n", ret, -ret);
        goto exit;
    }
    printf( " ok\n" );
    
    /* shwo RSA keypair */
    dump_rsa_key(&ctx);
    
    /* 4. encrypt */
    printf( "\n  . RSA pkcs1 encrypt..." );
    
    ret = mbedtls_rsa_pkcs1_encrypt(&ctx, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, 
                                    strlen(msg), (uint8_t *)msg, output_buf);
    if(ret != 0) {
        printf( " failed\n  ! mbedtls_rsa_pkcs1_encrypt returned %d(-0x%04x)\n", ret, -ret);
        goto exit;
    }
    printf( " ok\n" );
    
    /* show encrypt result */
    dump_buf(output_buf, sizeof(output_buf));
    
    /* 5. decrypt */
    printf( "\n  . RSA pkcs1 decrypt..." );
    
    ret = mbedtls_rsa_pkcs1_decrypt(&ctx, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, 
                                    &olen, output_buf, decrypt_buf, sizeof(decrypt_buf));
    if(ret != 0) {
        printf( " failed\n  ! mbedtls_rsa_pkcs1_decrypt returned %d(-0x%04x)\n", ret, -ret);
        goto exit;
    }
    printf( " ok\n" );
    
    /* show decrypt result */
    decrypt_buf[olen] = '\0';
    printf("decrypt result:[%s]\r\n", decrypt_buf);
    
    exit:
    
    /* 5. release structure */
    mbedtls_ctr_drbg_free(&ctr_drbg);
    mbedtls_entropy_free(&entropy);
    mbedtls_rsa_free(&ctx);
    
    return ret;
}

#endif /* MBEDTLS_RSA_C */

4. 测试结果

在 main.c 中声明该测试函数:

extern int mbedtls_rsa_test(void);

然后在main函数中调用:

/* 5. rsa test */
mbedtls_rsa_test();

5. 堆内存空间的设置

特别注意:

mbedtls RSA算法中的生成秘钥对比较占用空间,再加上RSA算法计算过程涉及到大数运算,所以RSA算法对内存的消耗比较大。

mbedtls使用的是动态内存,从堆空间中分配,所以要在STM32启动文件中将堆空间设置的大一点:

51c嵌入式~mbedtls~rsa_mbed_02

6. 测试结果

编译、下载到开发板中,可以看到:

① 秘钥对生成结果(花费大概5min左右,主控芯片STM32L431主频为80Mhz):

51c嵌入式~mbedtls~rsa_嵌入式硬件_03

② 使用公钥加密结果(因为RSA算法中的特性,所以同一份程序,同一份明文,每次重新运行生成的密文也不同):

51c嵌入式~mbedtls~rsa_嵌入式硬件_04

③ 使用私钥解密结果:

51c嵌入式~mbedtls~rsa_mbed_05




三、各MCU使用状况

1.STM32

2.AT32

3.GD32

4.HC32

5.WC32