1、开发环境
使用STM32F103ZET6+Keil+mbedtls2.24.0
2、下载MbedTLS源码
git clone -b mbedtls-2.24.0 https://github.com/ARMmbed/mbedtls.git
所需要的相关文件夹有
include/:头文件library/:算法源码configs/:配置文件
3、算法实现
首先,这里简单介绍了加密算法的种类加密算法介绍
椭圆曲线数字签名算法(ECDSA)是基于椭圆曲线密码(ECC)模拟数字签名算法(DSA)。相比普通的离散对数问题(DLP)和大数分解问题(IFP),椭圆曲线密码的单位比特强度要高于其他公钥体制。算法库框架提供了多种椭圆曲线及摘要算法组合的椭圆曲线数字签名算法(ECDSA)能力
在嵌入式领域,每KB内存都弥足珍贵。传统RSA算法需要2048位密钥才能达到的安全级别,ECDSA仅需256位即可实现。这种差异源于两者截然不同的数学基础:
- RSA:基于大整数分解难题,密钥长度呈线性增长
- ECDSA:基于椭圆曲线离散对数问题,安全性与密钥长度呈指数关系
具体对比如下:
| 安全强度(bits) | RSA密钥长度(bits) | ECDSA密钥长度(bits) |
|---|---|---|
| 80 | 1024 | 160 |
| 112 | 2048 | 224 |
| 128 | 3072 | 256 |
4、代码实现
首先,我在程序运行时程序崩溃了,调试时发现是堆栈设置的太小了,所以一定要重新设置堆栈的大小,我使用RAM大小有64KB所以我尽量往大了设置了:
Stack_Size EQU 0x00005000
Heap_Size EQU 0x00002500
4.1、MBedTLS的配置文件
下载的mbedtls目录下configs存放着模块的配置文件
修改config-mini-tls1_1.h
#ifndef MBEDTLS_CONFIG_H
#define MBEDTLS_CONFIG_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_ECP_C
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
#define MBEDTLS_ECDSA_C
#define MBEDTLS_ASN1_WRITE_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_PK_C
#define MBEDTLS_OID_C
#include "mbedtls/check_config.h"
#endif /* _MBEDTLS_CONFIG_ECDSA_H_ */
在Keil的设置里C/C++下的Preprocesson Symbols添加定义:

在Include Paths下添加头文件路径

4.2、定义mbedtls_hardware_poll
这里配置好运行以后,会提示你mbedtls_hardware_poll没有被定义,但是这个函数你可以去mbedtls\configs里面找到,文件名new 1.txt
/*随机数函数*/
int mbedtls_hardware_poll( void *data,
unsigned char *output, size_t len, size_t *olen )
{
unsigned long randomValue = ((rand()*20) + 1000);//生成随机数
((void) data);
*olen = 0;
if( len < sizeof(unsigned long) ) return( 0 );
memcpy( output, &randomValue, sizeof(unsigned long) );
*olen = sizeof(unsigned long);
return 0;
}
/**
* @brief 时间函数(SSL底层会调用时间验证证书是否过期)
**/
//struct tm *lcTime;
//time_t startTime;
// lcTime = localtime (&startTime);
_ARMABI time_t time(time_t *t)
{
// time_t it;
if (t) {
return *t;
}
else
{
// startTime = 0;
// lcTime = localtime (&startTime);
// it = mktime(lcTime);
// return it ;
return 0;
}
}
4.3、官方例程
官方例程如果有报错,那么应该是你的配置文件或者选择的参数与例子的不同,比如例程的
if( ( ret = mbedtls_ecdsa_genkey( &ctx_sign, ECPARAMS,
mbedtls_ctr_drbg_random, &ctr_drbg ) ) != 0 )
ECPARAMS 我使用就会报错,我需要改成我的MBEDTLS_ECP_DP_SECP256R1
/*
* Example ECDSA program
*
* Copyright The Mbed TLS Contributors
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif
#if defined(MBEDTLS_PLATFORM_C)
#include "mbedtls/platform.h"
#else
#include <stdio.h>
#include <stdlib.h>
#define mbedtls_printf printf
#define mbedtls_exit exit
#define MBEDTLS_EXIT_SUCCESS EXIT_SUCCESS
#define MBEDTLS_EXIT_FAILURE EXIT_FAILURE
#endif /* MBEDTLS_PLATFORM_C */
#if defined(MBEDTLS_ECDSA_C) && \
defined(MBEDTLS_ENTROPY_C) && defined(MBEDTLS_CTR_DRBG_C)
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/sha256.h"
#include <string.h>
#endif
/*
* Uncomment to show key and signature details
*/
#define VERBOSE
/*
* Uncomment to force use of a specific curve
*/
#define ECPARAMS MBEDTLS_ECP_DP_SECP192R1
#if !defined(ECPARAMS)
#define ECPARAMS mbedtls_ecp_curve_list()->grp_id
#endif
#if !defined(MBEDTLS_ECDSA_C) || !defined(MBEDTLS_SHA256_C) || \
!defined(MBEDTLS_ENTROPY_C) || !defined(MBEDTLS_CTR_DRBG_C)
int main( void )
{
mbedtls_printf("MBEDTLS_ECDSA_C and/or MBEDTLS_SHA256_C and/or "
"MBEDTLS_ENTROPY_C and/or MBEDTLS_CTR_DRBG_C not defined\n");
mbedtls_exit( 0 );
}
#else
#if defined(VERBOSE)
static void dump_buf( const char *title, unsigned char *buf, size_t len )
{
size_t i;
mbedtls_printf( "%s", title );
for( i = 0; i < len; i++ )
mbedtls_printf("%c%c", "0123456789ABCDEF" [buf[i] / 16],
"0123456789ABCDEF" [buf[i] % 16] );
mbedtls_printf( "\n" );
}
static void dump_pubkey( const char *title, mbedtls_ecdsa_context *key )
{
unsigned char buf[300];
size_t len;
if( mbedtls_ecp_point_write_binary( &key->grp, &key->Q,
MBEDTLS_ECP_PF_UNCOMPRESSED, &len, buf, sizeof buf ) != 0 )
{
mbedtls_printf("internal error\n");
return;
}
dump_buf( title, buf, len );
}
#else
#define dump_buf( a, b, c )
#define dump_pubkey( a, b )
#endif
int main( int argc, char *argv[] )
{
int ret = 1;
int exit_code = MBEDTLS_EXIT_FAILURE;
mbedtls_ecdsa_context ctx_sign, ctx_verify;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
unsigned char message[100];
unsigned char hash[32];
unsigned char sig[MBEDTLS_ECDSA_MAX_LEN];
size_t sig_len;
const char *pers = "ecdsa";
((void) argv);
mbedtls_ecdsa_init( &ctx_sign );
mbedtls_ecdsa_init( &ctx_verify );
mbedtls_ctr_drbg_init( &ctr_drbg );
memset( sig, 0, sizeof( sig ) );
memset( message, 0x25, sizeof( message ) );
if( argc != 1 )
{
mbedtls_printf( "usage: ecdsa\n" );
#if defined(_WIN32)
mbedtls_printf( "\n" );
#endif
goto exit;
}
/*
* Generate a key pair for signing
*/
mbedtls_printf( "\n . Seeding the random number generator..." );
fflush( stdout );
mbedtls_entropy_init( &entropy );
if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char *) pers,
strlen( pers ) ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret );
goto exit;
}
mbedtls_printf( " ok\n . Generating key pair..." );
fflush( stdout );
if( ( ret = mbedtls_ecdsa_genkey( &ctx_sign, ECPARAMS,
mbedtls_ctr_drbg_random, &ctr_drbg ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_ecdsa_genkey returned %d\n", ret );
goto exit;
}
mbedtls_printf( " ok (key size: %d bits)\n", (int) ctx_sign.grp.pbits );
dump_pubkey( " + Public key: ", &ctx_sign );
/*
* Compute message hash
*/
mbedtls_printf( " . Computing message hash..." );
fflush( stdout );
if( ( ret = mbedtls_sha256_ret( message, sizeof( message ), hash, 0 ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_sha256_ret returned %d\n", ret );
goto exit;
}
mbedtls_printf( " ok\n" );
dump_buf( " + Hash: ", hash, sizeof( hash ) );
/*
* Sign message hash
*/
mbedtls_printf( " . Signing message hash..." );
fflush( stdout );
if( ( ret = mbedtls_ecdsa_write_signature( &ctx_sign, MBEDTLS_MD_SHA256,
hash, sizeof( hash ),
sig, &sig_len,
mbedtls_ctr_drbg_random, &ctr_drbg ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_ecdsa_write_signature returned %d\n", ret );
goto exit;
}
mbedtls_printf( " ok (signature length = %u)\n", (unsigned int) sig_len );
dump_buf( " + Signature: ", sig, sig_len );
/*
* Transfer public information to verifying context
*
* We could use the same context for verification and signatures, but we
* chose to use a new one in order to make it clear that the verifying
* context only needs the public key (Q), and not the private key (d).
*/
mbedtls_printf( " . Preparing verification context..." );
fflush( stdout );
if( ( ret = mbedtls_ecp_group_copy( &ctx_verify.grp, &ctx_sign.grp ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_ecp_group_copy returned %d\n", ret );
goto exit;
}
if( ( ret = mbedtls_ecp_copy( &ctx_verify.Q, &ctx_sign.Q ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_ecp_copy returned %d\n", ret );
goto exit;
}
/*
* Verify signature
*/
mbedtls_printf( " ok\n . Verifying signature..." );
fflush( stdout );
if( ( ret = mbedtls_ecdsa_read_signature( &ctx_verify,
hash, sizeof( hash ),
sig, sig_len ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_ecdsa_read_signature returned %d\n", ret );
goto exit;
}
mbedtls_printf( " ok\n" );
exit_code = MBEDTLS_EXIT_SUCCESS;
exit:
#if defined(_WIN32)
mbedtls_printf( " + Press Enter to exit this program.\n" );
fflush( stdout ); getchar();
#endif
mbedtls_ecdsa_free( &ctx_verify );
mbedtls_ecdsa_free( &ctx_sign );
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
mbedtls_exit( exit_code );
}
#endif /* MBEDTLS_ECDSA_C && MBEDTLS_ENTROPY_C && MBEDTLS_CTR_DRBG_C &&
ECPARAMS */
4.4、试验main文件
#include "delay.h"
#include "stm32f10x.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "stmflash.h"
#include "iap.h"
#include "flash_key_storage.h"
#define PORT_TXD0 (GPIOA)
#define PIN_TXD0 (GPIO_Pin_9)
#define PORT_RXD0 (GPIOA)
#define PIN_RXD0 (GPIO_Pin_10)
//添加熵源,stm32f103没有RNG,所以只能用软件模拟一下
int mbedtls_hardware_poll( void *data,
unsigned char *output, size_t len, size_t *olen )
{
unsigned long randomValue = ((rand()*20) + 1000);//生成随机数
((void) data);
*olen = 0;
if( len < sizeof(unsigned long) ) return( 0 );
memcpy( output, &randomValue, sizeof(unsigned long) );
*olen = sizeof(unsigned long);
return 0;
}
int mbedtls_ecdsa_test(void)
{
int ret;
size_t qlen, dlen;
size_t rlen, slen;
uint8_t hash[32];
const char *msg = "HelloWorld";
const char *pers = "ecdsa_test";
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_mpi r, s;
mbedtls_ecdsa_context ctx;
mbedtls_md_context_t md_ctx;
/* 1. init structure */
mbedtls_md_init(&md_ctx);
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_mpi_init(&r);
mbedtls_mpi_init(&s);
/* 2. update seed with we own interface ported */
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\r\n" );
/* 3. hash message */
printf( "\r\n . Hash message..." );
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), (uint8_t *)msg, strlen(msg), hash);
if(ret != 0) {
printf( " failed\n ! mbedtls_md returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show hash */
dump_buf(hash, sizeof(hash));
/* 4. generate keypair */
printf( "\n . Generate ecdsa keypair..." );
ret = mbedtls_ecdsa_genkey(&ctx, MBEDTLS_ECP_DP_SECP256R1, mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdsa_genkey returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show keypair */
mbedtls_ecp_point_write_binary(&ctx.grp, &ctx.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &qlen, buf, sizeof(buf));
dlen = mbedtls_mpi_size(&ctx.d);
mbedtls_mpi_write_binary(&ctx.d, buf + qlen, dlen);
dump_buf(buf, qlen + dlen);
/* 5. ecdsa sign */
printf( "\n . ECDSA sign..." );
ret = mbedtls_ecdsa_sign(&ctx.grp, &r, &s, &ctx.d, hash, sizeof(hash), mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdsa_sign returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show sign */
rlen = mbedtls_mpi_size(&r);
slen = mbedtls_mpi_size(&s);
mbedtls_mpi_write_binary(&r, buf, rlen);
mbedtls_mpi_write_binary(&s, buf + rlen, slen);
dump_buf(buf, rlen + slen);
/* 6. ecdsa verify */
printf( "\n . ECDSA verify..." );
ret = mbedtls_ecdsa_verify(&ctx.grp, hash, sizeof(hash), &ctx.Q, &r, &s);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdsa_verify returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
exit:
/* 7. release structure */
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
mbedtls_mpi_free(&r);
mbedtls_mpi_free(&s);
mbedtls_md_free(&md_ctx);
mbedtls_ecdsa_free(&ctx);
return ret;
}
其中:
- qlen 和 dlen用于存储公钥点的长度和私钥的长度。
- rlen 和 slen用于存储ECDSA签名中 r 和 s 分量的长度。
- hash用于存储SHA-256哈希值(SHA-256输出为32字节)。
- msg表示明文。
- "ecdsa_test" 的指针 pers于种子化随机数生成器,以增加随机性。
- mbedtls_entropy_context 类型的变量 entropy。这是mbed TLS库中用于管理熵源(随机数据源)的上下文结构体。
- mbedtls_ctr_drbg_context 类型的变量 ctr_drbg。这是mbed TLS库中用于管理CTR_DRBG(基于CTR模式的随机数生成器)的上下文结构体,用于生成加密安全的随机数。
- mbedtls_mpi 类型的变量 r 和 s。mbedtls_mpi 是mbed TLS库中用于大整数运算的结构体。在这里,它们用于存储ECDSA签名的两个组成部分 r 和 s。
- mbedtls_ecdsa_context 类型的变量 ctx。这是mbed TLS库中用于管理ECDSA密钥对和操作的上下文结构体。
- 声明一个 mbedtls_md_context_t 类型的变量 md_ctx。这是mbed TLS库中用于管理消息摘要(如SHA-256)操作的上下文结构体。
- 调用 mbedtls_ctr_drbg_seed 函数,使用 mbedtls_entropy_func 作为熵源函数、&entropy 作为熵源上下文、pers 作为个性化字符串来种子化 ctr_drbg 随机数生成器。ret 用于接收函数的返回值,以检查是否成功。
- 调用 mbedtls_md 函数,使用SHA-256算法对 msg 指向的消息进行哈希计算,并将结果存储在 hash 数组中。mbedtls_md_info_from_type(MBEDTLS_MD_SHA256) 获取SHA-256算法的信息。ret 用于接收函数的返回值。
- 调用 mbedtls_ecdsa_genkey 函数,使用SECP256R1椭圆曲线(即NIST P-256曲线)、mbedtls_ctr_drbg_random 作为随机数生成函数、&ctr_drbg 作为随机数生成器上下文,来生成一个ECDSA密钥对,并将密钥对存储在 ctx 结构体中。ret 用于接收函数的返回值。
- 调用 mbedtls_ecp_point_write_binary 函数,将公钥点 ctx.Q 以未压缩格式(MBEDTLS_ECP_PF_UNCOMPRESSED)写入到 buf 缓冲区中。qlen 用于接收写入的字节数。ctx.grp 是椭圆曲线组信息。
- 调用 mbedtls_mpi_size 函数,获取私钥 ctx.d 的大小(以字节为单位),并存储在 dlen 中。
- 调用 mbedtls_mpi_write_binary 函数,将私钥 ctx.d 的二进制表示写入到 buf 缓冲区中,从 buf + qlen 的位置开始写入,写入 dlen 字节。
- 调用 mbedtls_ecdsa_sign 函数,使用 ctx 中的私钥(&ctx.d)、hash 中的哈希值、mbedtls_ctr_drbg_random 作为随机数生成函数、&ctr_drbg 作为随机数生成器上下文,对消息进行签名。签名结果的 r 和 s 分量分别存储在 &r 和 &s 中。ret 用于接收函数的返回值。
- 调用 mbedtls_ecdsa_verify 函数,使用 ctx 中的公钥(&ctx.Q)、hash 中的哈希值、以及之前生成的签名分量 r 和 s,对签名进行验证。ret 用于接收函数的返回值。
4.5、已有现成的公钥如果对数据解析
4.5.1、第一步找到解析公钥函数
我的使用情景分单片机和上位机
上位机:
- 对发送的文件进行哈希值计算
- 对哈希值使用私钥加密生成数字签名
- 发送数字签名以及文件
单片机:
- 接收数字签名和文件
- 对文件进行哈希值计算
- 使用公钥对数字签名和生成的哈希值进行验签
计算哈希值这个没啥说的,数字签名我已经有了,公钥存在单片机里,所以我的流程是导入公钥,然后进行验签就行了,
直接在源目录下搜索公钥的开头-----BEGIN PUBLIC KEY-----搜索结果如下:

其中pkparse.c就是我们需要的,而pkwrite是生成pem格式使用的,所以我们直接去pkparse.文件查看得到mbedtls_pk_parse_public_key文件,调用格式:
/** \ingroup pk_module */
/**
* \brief Parse a public key in PEM or DER format
*
* \param ctx The PK context to fill. It must have been initialized
* but not set up.
* \param key Input buffer to parse.
* The buffer must contain the input exactly, with no
* extra trailing material. For PEM, the buffer must
* contain a null-terminated string.
* \param keylen Size of \b key in bytes.
* For PEM data, this includes the terminating null byte,
* so \p keylen must be equal to `strlen(key) + 1`.
*
* \note On entry, ctx must be empty, either freshly initialised
* with mbedtls_pk_init() or reset with mbedtls_pk_free(). If you need a
* specific key type, check the result with mbedtls_pk_can_do().
*
* \note The key is also checked for correctness.
*
* \return 0 if successful, or a specific PK or PEM error code
*/
int mbedtls_pk_parse_public_key( mbedtls_pk_context *ctx,
const unsigned char *key, size_t keylen );
有几个要点:
- pk必须初始化
- pem数据的长度要加1已空格符结尾
调用代码:
mbedtls_pk_context pk;
mbedtls_pk_init( &pk );
ret = mbedtls_pk_parse_public_key( &pk, public_key, strlen(public_key)+1 );
这里得到了一个mbedtls_pk_context结构体的数据,定义如下:
typedef struct mbedtls_pk_context
{
const mbedtls_pk_info_t * pk_info; /**< Public key information */
void * pk_ctx; /**< Underlying public key context */
} mbedtls_pk_context;
typedef struct mbedtls_pk_info_t mbedtls_pk_info_t;
struct mbedtls_pk_info_t
{
/** Public key type */
mbedtls_pk_type_t type;
/** Type name */
const char *name;
/** Get key size in bits */
size_t (*get_bitlen)( const void * );
/** Tell if the context implements this type (e.g. ECKEY can do ECDSA) */
int (*can_do)( mbedtls_pk_type_t type );
/** Verify signature */
int (*verify_func)( void *ctx, mbedtls_md_type_t md_alg,
const unsigned char *hash, size_t hash_len,
const unsigned char *sig, size_t sig_len );
/** Make signature */
int (*sign_func)( void *ctx, mbedtls_md_type_t md_alg,
const unsigned char *hash, size_t hash_len,
unsigned char *sig, size_t *sig_len,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng );
#if defined(MBEDTLS_ECDSA_C) && defined(MBEDTLS_ECP_RESTARTABLE)
/** Verify signature (restartable) */
int (*verify_rs_func)( void *ctx, mbedtls_md_type_t md_alg,
const unsigned char *hash, size_t hash_len,
const unsigned char *sig, size_t sig_len,
void *rs_ctx );
/** Make signature (restartable) */
int (*sign_rs_func)( void *ctx, mbedtls_md_type_t md_alg,
const unsigned char *hash, size_t hash_len,
unsigned char *sig, size_t *sig_len,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng, void *rs_ctx );
#endif /* MBEDTLS_ECDSA_C && MBEDTLS_ECP_RESTARTABLE */
/** Decrypt message */
int (*decrypt_func)( void *ctx, const unsigned char *input, size_t ilen,
unsigned char *output, size_t *olen, size_t osize,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng );
/** Encrypt message */
int (*encrypt_func)( void *ctx, const unsigned char *input, size_t ilen,
unsigned char *output, size_t *olen, size_t osize,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng );
/** Check public-private key pair */
int (*check_pair_func)( const void *pub, const void *prv );
/** Allocate a new context */
void * (*ctx_alloc_func)( void );
/** Free the given context */
void (*ctx_free_func)( void *ctx );
#if defined(MBEDTLS_ECDSA_C) && defined(MBEDTLS_ECP_RESTARTABLE)
/** Allocate the restart context */
void * (*rs_alloc_func)( void );
/** Free the restart context */
void (*rs_free_func)( void *rs_ctx );
#endif /* MBEDTLS_ECDSA_C && MBEDTLS_ECP_RESTARTABLE */
/** Interface with the debug module */
void (*debug_func)( const void *ctx, mbedtls_pk_debug_item *items );
};
这里可以看到,mbedtls_pk_context该结构体包含两个主要成员:
-
**
pk_info**:指向公钥信息结构体的指针,包含公钥类型的相关信息和操作函数指针。这个成员用于标识密钥类型(如 RSA、EC 等)以及提供相应的操作函数。 -
**
pk_ctx** :指向底层公钥上下文的指针,实际存储了具体密钥算法的上下文信息。例如,对于 RSA 密钥,这里会指向mbedtls_rsa_context结构体;对于 ECC 密钥,会指向mbedtls_ecp_keypair结构体等。
4.5.2、得到mbedtls_ecp_keypair
通过这个结构体,Mbed TLS 能够以统一的方式处理不同类型的公钥算法,同时保持各算法实现的独立性。在使用公钥进行加密、解密、签名、验证等操作时,通常会传入 mbedtls_pk_context 类型的参数,由底层实现根据 pk_info 成员确定具体的操作方式。在pk.h中也看到了它的类型强制转换
#if defined(MBEDTLS_RSA_C)
/**
* Quick access to an RSA context inside a PK context.
*
* \warning You must make sure the PK context actually holds an RSA context
* before using this function!
*/
static inline mbedtls_rsa_context *mbedtls_pk_rsa( const mbedtls_pk_context pk )
{
return( (mbedtls_rsa_context *) (pk).pk_ctx );
}
#endif /* MBEDTLS_RSA_C */
#if defined(MBEDTLS_ECP_C)
/**
* Quick access to an EC context inside a PK context.
*
* \warning You must make sure the PK context actually holds an EC context
* before using this function!
*/
static inline mbedtls_ecp_keypair *mbedtls_pk_ec( const mbedtls_pk_context pk )
{
return( (mbedtls_ecp_keypair *) (pk).pk_ctx );
}
所以下一步如果我们使用什么类型的解密,就将其转换为不同的类型即可,我使用的ecp
const mbedtls_ecp_keypair *ecp;
ecp = mbedtls_pk_ec(pk);
4.5.3、找到验签函数
到这一步又不知道干嘛了,怎么执行下一步,首先我们的需求很明确,导入自己的公钥和签名,进行验签,那么我们直接去找验签函数,看看验签函数需要什么,前面模板已经知道了,验签函数可以是mbedtls_ecdsa_verify也可以是mbedtls_ecdsa_read_signature。但是代码逻辑上mbedtls_ecdsa_read_signature内部会调用mbedtls_ecdsa_verify,所以看mbedtls_ecdsa_read_signature
/**
* \brief This function reads and verifies an ECDSA signature.
*
* \note If the bitlength of the message hash is larger than the
* bitlength of the group order, then the hash is truncated as
* defined in <em>Standards for Efficient Cryptography Group
* (SECG): SEC1 Elliptic Curve Cryptography</em>, section
* 4.1.4, step 3.
*
* \see ecp.h
*
* \param ctx The ECDSA context to use. This must be initialized
* and have a group and public key bound to it.
* \param hash The message hash that was signed. This must be a readable
* buffer of length \p size Bytes.
* \param hlen The size of the hash \p hash.
* \param sig The signature to read and verify. This must be a readable
* buffer of length \p slen Bytes.
* \param slen The size of \p sig in Bytes.
*
* \return \c 0 on success.
* \return #MBEDTLS_ERR_ECP_BAD_INPUT_DATA if signature is invalid.
* \return #MBEDTLS_ERR_ECP_SIG_LEN_MISMATCH if there is a valid
* signature in \p sig, but its length is less than \p siglen.
* \return An \c MBEDTLS_ERR_ECP_XXX or \c MBEDTLS_ERR_MPI_XXX
* error code on failure for any other reason.
*/
int mbedtls_ecdsa_read_signature( mbedtls_ecdsa_context *ctx,
const unsigned char *hash, size_t hlen,
const unsigned char *sig, size_t slen );
这里可以看到我们需要一个mbedtls_ecdsa_context 的结构体ctx,一看定义
typedef mbedtls_ecp_keypair mbedtls_ecdsa_context;
嘿,这不就对应起来了吗,我前面已经得到了这个结构体了,结构体定义
/**
* \brief The ECP key-pair structure.
*
* A generic key-pair that may be used for ECDSA and fixed ECDH, for example.
*
* \note Members are deliberately in the same order as in the
* ::mbedtls_ecdsa_context structure.
*/
typedef struct mbedtls_ecp_keypair
{
mbedtls_ecp_group grp; /*!< Elliptic curve and base point */
mbedtls_mpi d; /*!< our secret value */
mbedtls_ecp_point Q; /*!< our public value */
}
mbedtls_ecp_keypair;
4.5、验签
完整代码如下:
#include "delay.h"
#include "stm32f10x.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "stmflash.h"
#include "iap.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/sha256.h"
#include "mbedtls/pk.h"
#include <time.h>
const uint8_t public_key[] = {
"-----BEGIN PUBLIC KEY-----\n"
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvaJpE+eaJ4iVlurAR2xc1gZRDBsY\n"
"zRttienho/a5qGiPe54eip4MzJh5G80dh5PrTlRpHq6nRdmbElBZ9H0lEQ==\n"
"-----END PUBLIC KEY-----\n"};
const uint8_t hash_text[32] = {
0x3b,0xb6,0xd6,0xb3,0xe4,0x49,0x01,0x91,0xb1,0xa5,
0x4b,0x7e,0xbd,0x09,0xce,0xb1,0xdb,0xcb,0xf1,0x0d,
0xdf,0x84,0x3d,0xe1,0x99,0x64,0xa7,0x43,0x5c,0xe1,
0x95,0xc2
};
const uint8_t signature[71] = {
0x30,0x45,0x02,0x20,0x14,0x27,0xFD,0xBE,0x03,0xDC,0xBB,0x23,0x36,0xF8,0x00,0xEE,
0x93,0x61,0x4B,0x72,0x47,0x7B,0x9A,0x41,0x16,0x14,0x8D,0x46,0x16,0x6D,0x9C,0x68,
0x83,0x6C,0xFD,0xCD,0x02,0x21,0x00,0xDE,0xF2,0xF0,0x8B,0x9D,0x3A,0x85,0x09,0xB5,
0x36,0x26,0x59,0x69,0x15,0x31,0xE6,0x39,0xEF,0x87,0xBA,0x26,0x6D,0x39,0x61,0x5A,
0xAC,0x09,0xC3,0x8B,0x26,0x2F,0x41
};
//添加熵源
int mbedtls_hardware_poll( void *data,unsigned char *output, size_t len, size_t *olen )
{
unsigned long randomValue = ((rand()*20) + 1000);//生成随机数
((void) data);
*olen = 0;
if( len < sizeof(unsigned long) ) return( 0 );
memcpy( output, &randomValue, sizeof(unsigned long) );
*olen = sizeof(unsigned long);
return 0;
}
/**
* @brief 时间函数(SSL底层会调用时间验证证书是否过期)
**/
//struct tm *lcTime;
//time_t startTime;
// lcTime = localtime (&startTime);
_ARMABI time_t time(time_t *t)
{
// time_t it;
if (t) {
return *t;
}
else
{
// startTime = 0;
// lcTime = localtime (&startTime);
// it = mktime(lcTime);
// return it ;
return 0;
}
}
int main( )
{
int ret;
mbedtls_ecp_keypair *ecp;
mbedtls_pk_context pk;
mbedtls_pk_init( &pk );
ret = mbedtls_pk_parse_public_key(&pk,
(const unsigned char*)public_key,
strlen((const char*)public_key)+1 );
if (ret != 0) {
goto cleanup;
}
ecp = mbedtls_pk_ec(pk);
ret = mbedtls_ecdsa_read_signature((mbedtls_ecdsa_context *) ecp,
hash_text, strlen( (const char*)hash_text )+1,
signature, 71 );
if( ret != 0 ){
goto cleanup;
}
printf("ok");
cleanup:
mbedtls_pk_free(&pk);
return 0;
}
5、碰到的问题
5.1、公钥解析问题
如果公钥解析出现问题,那么第一步你就应该怀疑之际的格式是否正确,比如说我用的pem格式的,调试查看pem转为der格式的数据是否正确,因为pem格式会转换为der然后存入结构体中,那么首先我要知道我公钥解析正确否,调试看到解析生成的buf数据,比如说我的转为der的数据存放在0x20000B98里,然后我windows用openssl进行转换,数据可以看到是对应的,那么公钥解析应该是没问题

0x20000b98数组地址存放着公钥的解析数据,而我电脑直接对pem文件进行DER格式的转换

5.2、数字签名问题
注意:mebdtls生成私钥要选择secp256r1而不是secp256k1,否则会报错,因为配置文件选的就是r1
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
不用格式的生成签名算法需要用不同的函数验签,比如说我最早使用的是RSA生成的密钥对以及数字签名,可是我后来发觉ecdsa算法更好,换了以后以为密钥对和数字签名是通用的,其实不然,比如说ecdsad 数字签名的第一个数据必须为0x30,不然就报错,windows使用openssl生成ecdsa密钥对流程:
//这里`secp256k1`是椭圆曲线参数的名称,你也可以选择其他如`prime256v1`等。
//生成的私钥文件是`private_key.pem`。
openssl ecparam -genkey -name secp256r1 -out private_key.pem
//使用私钥生成公钥:
openssl ec -in private_key.pem -pubout -out public_key.pem
//计算哈希值,要计算一个文件的SHA-256哈希值,可以使用:
openssl dgst -sha256 -out hash.txt your_file.txt
//使用私钥和消息(或其哈希值)生成数字签名:
openssl dgst -sha256 -sign private_key.pem -out signature.sign your_file.txt
//验证数字签名
openssl dgst -sha256 -verify public_key.pem -signature signature.sign your_file.txt
5.3、公钥编码问题
有的时候编译器就是不知道怎么编码就让你校验通不过,所以可以转换为数组存储
-----BEGIN PUBLIC KEY-----
.................................
-----END PUBLIC KEY-----
const uint8_t public_key[] = {
0x45,0x45,0x45,0x45,0x45,0x66,......
};
5.4、验签速度
超级慢,主频频率或者没有硬件加速的使用时间挺长的
6、其它使用过的函数
6.1、导出公钥到数组
unsigned char pub_key_buf[65];
mbedtls_ecp_keypair * ecp = mbedtls_pk_ec(pk);
ret = mbedtls_ecp_point_write_binary(&ecp->grp, &ecp->Q,
MBEDTLS_ECP_PF_UNCOMPRESSED,
&olen, pub_key_buf, sizeof(pub_key_buf));
6.2、PK验签
ret = mbedtls_pk_verify(&pk,MBEDTLS_MD_SHA256,
hash_text,strlen( (const char*)hash_text+1),
signature,71);