一、 mbedtls简介
MbedTLS是一个开源、可移植、易使用、可读性高的SSL库,实现了常所用的加解密算法、X.509证书操作以及TLS协议操作。MbedTLS各功能模块独立性高、耦合度低,可以通过配置宏定义进行功能裁剪,非常适合对空间和效率要求高的嵌入式系统。
二、RSA算法简介
1978年,由Ron Rivest、Adi Shamir和Reonard Adleman共同发表了公钥密码算法RSA,RSA目前是使用广泛的非对称加解密和签名验签算法。RSA密钥由公钥和私钥组成,基本特性如下:
1.公钥和私钥是成对出现的,一个公钥必然对应一个固定的私钥。同理,一个私钥也必然对应一个固定的公钥;
2.在加解密缓解,公钥用于加密,私钥用于解密;
3.在签名验签环节,私钥用于签名,公钥用于验签;
4.公钥通常是公开的,任何人都可以获取到,但私钥必须严格保密;
5.RSA按分组进行,若分组长度不足(例如1024bits或2048bits),则需要填充。填充方式分为PKCS#V1.5和PKCS#V2.1
6.非对称算法性能远低于对称算法
三、签名验签简介
签名验签机制被称为数字签名,通过该机制可实现完整性(Integrity)、认证性(Authenticity)、不和否认性(Non-Repudiation)的保护。通过使用私钥对原始数据进行签名,并同步将签名信息传递给接收者。接受者收到后,使用对应的公钥对签名进行验签,以确认接收到的原始数据是否有效。
原则为:私钥签名,公钥验签
详细请见RFC3447
总体流程如下图:
四、实现
demo代码:https://download.csdn.net/download/anjiyufei/88863376
4.1 移植MbedTLS代码
移植自mbedTLS 2.16版本
需移植的文件如下:
修改config.h文件
#ifndef MBEDTLS_CONFIG_H
#define MBEDTLS_CONFIG_H
#define MBEDTLS_ERROR_C
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_OID_C
#define MBEDTLS_RSA_C
#define MBEDTLS_AES_C
#define MBEDTLS_MD_C
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_GENPRIME
#define MBEDTLS_CTR_DRBG_C
#define MBEDTLS_PK_C
#define MBEDTLS_SHA256_C
//读pem证书
#define MBEDTLS_PEM_PARSE_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_BASE64_C
#define MBEDTLS_FS_IO
//填充方式,V15或V21两种模式必须二选一
//#define MBEDTLS_PKCS1_V21
#define MBEDTLS_PKCS1_V15
//不使用平台默认熵源,mbedtls在windows和linux下已实现熵源
#define MBEDTLS_NO_PLATFORM_ENTROPY
//#include "check_config.h"
#endif
4.2 引入头文件
引入相应的头文件
#include <stdio.h>
#include <time.h>
#include <string.h>
#include "../crypto/mbedtls/rsa.h"
#include "../crypto/mbedtls/ctr_drbg.h"
#include "../crypto/mbedtls/entropy.h"
#include "../crypto/mbedtls/entropy_poll.h"
#include "../crypto/mbedtls/sha256.h"
#include "../crypto/mbedtls/pk.h"
#include "../crypto/mbedtls/error.h"
4.3 填充方式
RSA加解密时存在两种填充方式:PKCS#V1.5、PKCS#V2.1,必须在config.h中指定填充方式。
mbedtls的RSA中默认使用PKCS#V1.5的填充方式,若在代码中使用PKCS#V2.1,代码中需额外设置,具体设置见后续代码备注。
两种填充方式的差异此处不展开介绍。
4.3.1 PKCS#V2.1说明
在使用PK库时,mbedtls已提示默认使用PKCS#V1.5,无接口去调用PKCS#V2.1的填充方式
但实际测试发现,可通过mbedtls_rsa_set_padding(private_rsa_context,MBEDTLS_RSA_PKCS_V21,MBEDTLS_MD_SHA256)
去实现PKCS#V2.1的填充
当添加如上代码后,跟踪到mbedtls_rsa_pkcs1_sign
函数,可发现ctx的padding为1
4.4 公私钥和熵源
详见之前博客《mbedtls移植之RSA加解密算法》的3.4章节,此处不额外说明
4.5 读取密钥
从文件中读取pem格式的公私钥
//获取公钥
int get_public_key_from_file( mbedtls_pk_context *public_pk_context,const char *public_pem_file){
char error[100];
int result = mbedtls_pk_parse_public_keyfile(public_pk_context,public_pem_file);
if(result != 0){
printf("failed to parse public key from file:");
mbedtls_strerror(result,error,sizeof(error));
printf("%s\n",error);
return -1;
}else{
printf("succeeded to parse public key from file!\n");
}
return 0;
}
//获取私钥
int get_private_key_from_file(mbedtls_pk_context *private_pk_context,const char *private_pem_file){
char error[100];
int result = mbedtls_pk_parse_keyfile(private_pk_context,private_pem_file,NULL);
if(result != 0){
printf("failed to parse private key from file:");
mbedtls_strerror(result,error,sizeof(error));
printf("%s\n",error);
return -1;
}else{
printf("succeeded to parse private key from file!\n");
}
return 0;
}
4.6 计算HASH
在对文件进行签名时,需先计算文件的HASH,再使用RSA私钥进行签名。使用SHA256计算HASH过程如下:
int get_hash_from_file_by_sha256(const char *sign_file,unsigned char *hash){
FILE *input_file = fopen(sign_file,"rb");
unsigned char input_buffer[1024];
size_t read_num = 0;
mbedtls_sha256_context sha256_context;
mbedtls_sha256_init(&sha256_context);
mbedtls_sha256_starts(&sha256_context,0);
while(1){
read_num = fread(input_buffer,sizeof(char ),1024,input_file);
if( read_num > 0){
if(read_num == 1024){
mbedtls_sha256_update(&sha256_context,input_buffer,read_num);
}else{
//文件尾
mbedtls_sha256_update(&sha256_context,input_buffer,read_num);
break;
}
}else{
printf("failed to read file\n");
mbedtls_sha256_free(&sha256_context);
fclose(input_file);
return -1;
}
}
mbedtls_sha256_finish(&sha256_context,hash);
/*
printf("sha256 info:\n");
for(int i=0;i<32;i++){
printf("%02x",hash[i]);
}
printf("\n");
*/
fclose(input_file);
mbedtls_sha256_free(&sha256_context);
return 0;
}
4.7 签名和验签
有两种方式可以实现签名验签
1.使用RSA库
2.使用PK库(PK库为对RSA库的封装,包括封装了ECC算法,本质上最终还是会调用到RSA库或ECC库)
推荐使用PK库
4.7.1 调用RSA库
1.读取私钥和公钥信息
2.对待签名文件使用SHA256算法得到文件的HASH值
3.签名:使用私钥对文件HASH值进行签名,得到签名值。签名使用函数mbedtls_rsa_pkcs1_sign
4.验签:使用对应的公钥对签名值进行验证,得到验签结果。验签使用函数mbedtls_rsa_pkcs1_verify
注意:
默认使用PKCS#V1.5填充算法,若需使用PKCS#V2.1填充算法,请参考代码中注释部分
int sign_verify_by_rsa(){
//个性化初始值:用于初始化伪随机数生成器,可设置为任意值
const char *personalization = "Fr789jj-ikrkjfjs@";
mbedtls_pk_context public_pk_context;
mbedtls_pk_context private_pk_context;
const char *public_pem_file = "D:\\tmp\\crypto\\rsa\\public.pem";
const char *private_pem_file = "D:\\tmp\\crypto\\rsa\\private.pem";
const char *sign_verify_file = "D:\\tmp\\crypto\\rsa\\music.mp3";
char error[100];
unsigned char signature_info[256];
unsigned char hash_buffer[32];
mbedtls_entropy_context entropy_context;
mbedtls_ctr_drbg_context ctr_drbg_context;
mbedtls_entropy_init(&entropy_context);
mbedtls_ctr_drbg_init(&ctr_drbg_context);
mbedtls_pk_init(&public_pk_context);
mbedtls_pk_init(&private_pk_context);
mbedtls_entropy_add_source(&entropy_context,get_clock_for_entropy,NULL,MBEDTLS_ENTROPY_MIN_PLATFORM,MBEDTLS_ENTROPY_SOURCE_STRONG);
int result = mbedtls_ctr_drbg_seed(&ctr_drbg_context, mbedtls_entropy_func, &entropy_context, personalization,strlen(personalization));
if(result){
printf("failed to get drbg seed\n");
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return -1;
}
result = get_private_key_from_file(&private_pk_context,private_pem_file);
if(result != 0){
printf("failed to get private key!\n");
return -2;
}
//对文件进行签名
//计算待加密文件HASH
result = get_hash_from_file_by_sha256(sign_verify_file,hash_buffer);
if(result != 0){
printf("failed to get hash from file\n");
}
mbedtls_rsa_context *private_rsa_context = mbedtls_pk_rsa(private_pk_context);
//因PK默认填充方式为V15,若需要使用V21填充方式,需添加如下代码。若使用V15,则无需添加。若使用V21填充方式,需同步在config.h中启用#define MBEDTLS_PKCS1_V21
//mbedtls_rsa_set_padding(private_rsa_context,MBEDTLS_RSA_PKCS_V21,MBEDTLS_MD_SHA256);
result = mbedtls_rsa_pkcs1_sign(private_rsa_context,mbedtls_ctr_drbg_random,&ctr_drbg_context,MBEDTLS_RSA_PRIVATE,MBEDTLS_MD_SHA256,sizeof(hash_buffer),hash_buffer,signature_info);
if(result != 0){
printf("failed to signature:");
mbedtls_strerror(result,error,sizeof(error));
printf("%s\n",error);
return -3;
}else{
printf("succeeded to signature!\n");
printf("signature info:\n");
for(int i=0;i<256;i++){
printf("%02x",signature_info[i]);
}
printf("\n");
//签名写入文件
FILE *sign_file = fopen("D:\\tmp\\crypto\\rsa\\mbedtls_sign-rsa.bin","wb");
int write_num = fwrite(signature_info,sizeof(char),256,sign_file);
if(write_num != 256){
printf("failed to write signature to file\n");
}
fclose(sign_file);
}
//修改签名信息,查看验签是否失败
//memset(signature_info+255,0xFF,1);
//验签
//从文件中读取公钥
result = get_public_key_from_file(&public_pk_context,public_pem_file);
if(result != 0){
printf("failed to get public key!\n");
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return -4;
}
//计算待加密文件HASH
result = get_hash_from_file_by_sha256(sign_verify_file,hash_buffer);
if(result != 0){
printf("failed to get hash from file\n");
}
mbedtls_rsa_context *public_rsa_context = mbedtls_pk_rsa(public_pk_context);
//因PK默认填充方式为V15,若需要使用V21填充方式,需添加如下代码。若使用V15,则无需添加。若使用V21填充方式,需同步在config.h中启用#define MBEDTLS_PKCS1_V21
//mbedtls_rsa_set_padding(public_rsa_context,MBEDTLS_RSA_PKCS_V21,MBEDTLS_MD_SHA256);
result = mbedtls_rsa_pkcs1_verify(public_rsa_context,mbedtls_ctr_drbg_random,&ctr_drbg_context,MBEDTLS_RSA_PUBLIC,MBEDTLS_MD_SHA256,sizeof(hash_buffer),hash_buffer,signature_info);
if(result == 0){
printf("succeeded to verify signature!\n");
}else{
printf("failed to verify signature:");
mbedtls_strerror(result,error,sizeof(error));
printf("%s\n",error);
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return -5;
}
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return 0;
}
4.7.1.1 运行效果
1.正常运行效果
2.篡改签名信息后验证
代码中启用注释掉的代码memset(signature_info+255,0xFF,1);
最终提示验签失败
3.使用openssl验签
上述代码中,成功后的签名信息会保存到D:\tmp\crypto\rsa\mbedtls_sign-rsa.bin
使用openssl命令进行验签,也可以验签通过(openssl使用PKCS#V1.5填充进行验签。若使用PKCS#V2.1签名,因填充方式不一致,会导致验签失败)
4.7.2 调用PK库
1.读取私钥和公钥信息
2.对待签名文件使用SHA256算法得到文件的HASH值
3.签名:使用私钥对文件HASH值进行签名,得到签名值。签名使用函数mbedtls_pk_sign
4.验签:使用对应的公钥对签名值进行验证,得到验签结果。验签使用函数mbedtls_pk_verify
注意:
默认使用PKCS#V1.5填充算法,若需使用PKCS#V2.1填充算法,请参考代码中注释部分
int sign_verify_by_pk(){
//个性化初始值:用于初始化伪随机数生成器,可设置为任意值
const char *personalization = "Fr789jj-ikrkjfjs@";
mbedtls_pk_context public_pk_context;
mbedtls_pk_context private_pk_context;
const char *public_pem_file = "D:\\tmp\\crypto\\rsa\\public.pem";
const char *private_pem_file = "D:\\tmp\\crypto\\rsa\\private.pem";
const char *sign_verify_file = "D:\\tmp\\crypto\\rsa\\music.mp3";
char error[100];
unsigned char signature_info[256];
unsigned char hash_buffer[32];
mbedtls_entropy_context entropy_context;
mbedtls_ctr_drbg_context ctr_drbg_context;
mbedtls_entropy_init(&entropy_context);
mbedtls_ctr_drbg_init(&ctr_drbg_context);
mbedtls_pk_init(&public_pk_context);
mbedtls_pk_init(&private_pk_context);
mbedtls_entropy_add_source(&entropy_context,get_clock_for_entropy,NULL,MBEDTLS_ENTROPY_MIN_PLATFORM,MBEDTLS_ENTROPY_SOURCE_STRONG);
int result = mbedtls_ctr_drbg_seed(&ctr_drbg_context, mbedtls_entropy_func, &entropy_context, personalization,strlen(personalization));
if(result){
printf("failed to get drbg seed\n");
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return -1;
}
//对文件进行签名
//从文件读取私钥
result = get_private_key_from_file(&private_pk_context,private_pem_file);
if(result != 0){
printf("failed to get private key!\n");
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return -2;
}
//计算待加密文件HASH
result = get_hash_from_file_by_sha256(sign_verify_file,hash_buffer);
if(result != 0){
printf("failed to get hash from file\n");
}
//因PK默认填充方式为V15,若需要使用V21填充方式,需添加如下代码。若使用V15,则无需添加。若使用V21填充方式,需同步在config.h中启用#define MBEDTLS_PKCS1_V21
//mbedtls_rsa_set_padding(mbedtls_pk_rsa(private_pk_context),MBEDTLS_RSA_PKCS_V21,MBEDTLS_MD_SHA256);
size_t signature_length;
result = mbedtls_pk_sign(&private_pk_context,MBEDTLS_MD_SHA256,hash_buffer,sizeof(hash_buffer),signature_info,&signature_length,mbedtls_ctr_drbg_random,&ctr_drbg_context);
if(result != 0){
printf("failed to signature:");
mbedtls_strerror(result,error,sizeof(error));
printf("%s\n",error);
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return -3;
}else{
printf("succeeded to signature!\n");
printf("signature info:\n");
for(int i=0;i<signature_length;i++){
printf("%02x",signature_info[i]);
}
printf("\n");
//签名写入文件
FILE *sign_file = fopen("D:\\tmp\\crypto\\rsa\\mbedtls_sign-pk.bin","wb");
int write_num = fwrite(signature_info,sizeof(char),signature_length,sign_file);
if(write_num != signature_length){
printf("failed to write signature to file\n");
}
fclose(sign_file);
}
//修改签名信息,查看验签是否失败
//memset(signature_info+255,0xFF,1);
//验签
//从文件中读取公钥
result = get_public_key_from_file(&public_pk_context,public_pem_file);
if(result != 0){
printf("failed to get public key!\n");
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return -4;
}
//计算待加密文件HASH
result = get_hash_from_file_by_sha256(sign_verify_file,hash_buffer);
if(result != 0){
printf("failed to get hash from file\n");
}
//因PK默认填充方式为V15,若需要使用V21填充方式,需添加如下代码。若使用V15,则无需添加。若使用V21填充方式,需同步在config.h中启用#define MBEDTLS_PKCS1_V21
//mbedtls_rsa_set_padding(mbedtls_pk_rsa(private_pk_context),MBEDTLS_RSA_PKCS_V21,MBEDTLS_MD_SHA256);
//最后一个参数为签名长度,RSA-2048为256Bytes(先HASH在签名)
result = mbedtls_pk_verify(&public_pk_context,MBEDTLS_MD_SHA256,hash_buffer,sizeof(hash_buffer),signature_info,256);
if(result == 0){
printf("succeeded to verify signature!\n");
}else{
printf("failed to verify signature:");
mbedtls_strerror(result,error,sizeof(error));
printf("%s\n",error);
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return -5;
}
free_context(&public_pk_context,&private_pk_context,&entropy_context,&ctr_drbg_context);
return 0;
}
4.7.2.1 运行效果
1.正常运行效果
2.篡改签名信息后验证
代码中启用注释掉的代码memset(signature_info+255,0xFF,1);
最终提示验签失败
3.使用openssl验签
上述代码中,成功后的签名信息会保存到D:\tmp\crypto\rsa\mbedtls_sign-pk.bin
使用openssl命令进行验签,也可以验签通过(openssl使用PKCS#V1.5填充进行验签。若使用PKCS#V2.1签名,因填充方式不一致,会导致验签失败)