【发布时间】:2021-04-13 16:47:35
【问题描述】:
我正在尝试使用以下函数在 C 中生成 RSA 密钥对:
int generate_key(const int bits, char* public_key_name, char* private_key_name){
int ret = 0;
RSA *r = NULL;
BIGNUM *bne = NULL;
FILE *bp_public = NULL;
FILE* bp_private = NULL;
unsigned long e = RSA_F4;
// 1. generate rsa key
bne = BN_secure_new();
ret = BN_set_word(bne,e);
if(ret != 1){
goto free_all;
}
r = RSA_new();
ret = RSA_generate_key_ex(r, bits, bne, NULL);
if(ret != 1){
goto free_all;
}
// 2. save public key
bp_public = fopen(public_key_name, "w+");
ret = PEM_write_RSAPublicKey(bp_public, r);
fclose(bp_public);
if(ret != 1){
goto free_all;
}
// 3. save private key
bp_private = fopen(private_key_name, "w+");
ret = PEM_write_RSAPrivateKey(bp_private, r, NULL, NULL, 0, NULL, NULL);
fclose(bp_private);
// 4. free
free_all:
RSA_free(r);
BN_clear_free(bne);
return ret;
}
它工作正常,除了当我使用公钥验证签名消息时,由于EVP_VerifyFinal 而弹出错误(error:0909006C:PEM routines:get_name:no)。请注意,奇怪的是,EVP_VerifyFinal 的返回值不是0,而是-1:如果是0,那么这无疑是签名验证失败,但事实并非如此,它发生了一些其他的事情,我想不通。在下面我过去了用于签名和验证的代码。
void sign(const unsigned char* const restrict clear_buf, const unsigned long clear_size, unsigned char* restrict* const restrict sgnt_buf, unsigned long* const restrict sgnt_size, const char* const restrict prvkey_file_name){
const EVP_MD* md;
EVP_MD_CTX* md_ctx;
int ret;
FILE* prvkey_file;
EVP_PKEY* prvkey;
unsigned expected_sgn_size;
unsigned tmp;
if(clear_size > INT_MAX){
fprintf(stderr, "Buffer to sign too big\n");
exit(1);
}
if((prvkey_file = fopen(prvkey_file_name, "r")) == NULL){
fprintf(stderr, "Error: cannot open file '%s' (missing?)\n", prvkey_file_name);
exit(1);
}
prvkey = PEM_read_PrivateKey(prvkey_file, NULL, NULL, NULL);
fclose(prvkey_file);
expected_sgn_size = (unsigned) EVP_PKEY_size(prvkey);
if((*sgnt_buf = (unsigned char*)malloc((size_t)expected_sgn_size)) == NULL){
fprintf(stderr, "Error in allocating memory for signature. Error: %s\n", strerror(errno));
exit(1);
}
// create the signature context:
md = EVP_sha256();
md_ctx = EVP_MD_CTX_new();
if(!md_ctx){
fprintf(stderr, "Error: EVP_MD_CTX_new returned NULL\n");
exit(1);
}
if(EVP_SignInit(md_ctx, md) == 0){
fprintf(stderr, "Error: EVP_SignInit returned %d\n", ret);
exit(1);
}
if(EVP_SignUpdate(md_ctx, clear_buf, (unsigned)clear_size) == 0){
fprintf(stderr, "Error: EVP_SignUpdate returned %d\n", ret);
exit(1);
}
if(EVP_SignFinal(md_ctx, *sgnt_buf, &tmp, prvkey) == 0){
fprintf(stderr, "Error: EVP_SignFinal returned %d\n", ret);
exit(1);
}
if(tmp < expected_sgn_size){
fprintf(stderr, "Error in signing, signature size does not match expected size\n");
exit(1);
}
*sgnt_size = (unsigned long)tmp;
// delete the digest and the private key from memory:
EVP_MD_CTX_free(md_ctx);
EVP_PKEY_free(prvkey);
}
void verify(const unsigned char* const restrict file_buf, unsigned long* const restrict file_size, const char* const restrict pubkey_file_name){
// declare some useful variables:
int ret;
FILE* pubkey_file;
EVP_PKEY* pubkey;
unsigned char* sgnt_buf;
unsigned sgnt_size;
pubkey_file = fopen(pubkey_file_name, "r");
if(!pubkey_file){
fprintf(stderr, "Error: cannot open file '%s' (missing?)\n", pubkey_file_name);
exit(1);
}
pubkey = PEM_read_PUBKEY(pubkey_file, NULL, NULL, NULL);
sgnt_size = (unsigned) EVP_PKEY_size(pubkey);
fclose(pubkey_file);
const EVP_MD* md = EVP_sha256();
EVP_MD_CTX* md_ctx;
*file_size -= (unsigned long)sgnt_size;
if((sgnt_buf = (unsigned char*)malloc((size_t)sgnt_size)) == NULL){
fprintf(stderr, "Error in allocating memory for signature. Error: %s\n", strerror(errno));
exit(1);
}
memcpy((void*)sgnt_buf, (void*)(file_buf + *file_size), (size_t)sgnt_size);
// create the signature context:
md_ctx = EVP_MD_CTX_new();
if(!md_ctx){
fprintf(stderr, "Error: EVP_MD_CTX_new returned NULL\n");
exit(1);
}
if(EVP_VerifyInit(md_ctx, md) == 0){
fprintf(stderr, "Error in EVP_VerifyInit\n");
exit(1);
}
if(EVP_VerifyUpdate(md_ctx, file_buf, *file_size) == 0){
fprintf(stderr, "Error in EVP_VerifyUpdate\n");
exit(1);
}
ret = EVP_VerifyFinal(md_ctx, sgnt_buf, sgnt_size, pubkey);
if(ret == 0){ // it is 0 if invalid signature, -1 if some other error, 1 if success.
fprintf(stderr, "Error: EVP_VerifyFinal failed: invalid signature\n");
exit(1);
} else if(ret == -1){
fprintf(stderr, "Some error occured during signature verification\n");
exit(1);
}else if (ret == 1){
// fprintf(stdout, "Signature verified\n");
}else{
fprintf(stderr, "I shouldn't be printed. EVP_VerifyFinal returned %d\n", ret);
exit(1);
}
EVP_MD_CTX_free(md_ctx);
EVP_PKEY_free(pubkey);
free(sgnt_buf);
}
在网上我找到了一些解决方案,但它们不在 C 语言中,或者/并且它们已被弃用并不能解决问题(我在此处找到的一个解决方案会生成一个私钥,它甚至不会为某些人签名原因...)
【问题讨论】:
-
您使用
PEM_write_RSAPublicKey编写 pubkey,这是“旧”算法特定的 PKCS1 格式,并尝试使用PEM_read_PUBKEY读取它,它需要基于 X.509 的通用格式,它不会不行;您的“密钥”实际上是空的,尽管您没有检测到这一点,并且使用空/不存在的密钥(可能还有空签名缓冲区,因为 malloc(0) 依赖于实现)进行验证不起作用。 In general whenever an OpenSSL libcrypto routine returns a failure value you should display the error stack. -
另外您应该知道 RSA 签名始终是密钥的大小,即
EVP_PKEY_size(x),但对于其他算法(如 DSA 和 ECDSA)而言并非如此,因此假设签名必须是并且这是固定的尺寸限制了您的使用。 -
@dave_thompson_085 好的,我(想我)明白了,但是我应该在代码中修改什么?我是 OpenSSL 的新手
-
最简单的解决方案是使用
PEM_write_RSA_PUBKEY编写公钥,这将与您的读取兼容。