简体   繁体   English

如何在 C 中正确生成 RSA 密钥对?

[英]How to correctly generate a RSA key pair in C?

I'm trying to generate a RSA key pair in C with the following function:我正在尝试使用以下 function 在 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;
}

It works fine except for the fact that when I used the public key to verify a signed message an error pops out ( error:0909006C:PEM routines:get_name:no ) as result of EVP_VerifyFinal .它工作正常,除了当我使用公钥验证签名消息时弹出错误( error:0909006C:PEM routines:get_name:no )作为EVP_VerifyFinal的结果。 Notice that, weird thing, the returning value of EVP_VerifyFinal is not 0 but -1 : if it was 0 then It would be unrefutably a signature verification failure but it is not, it happens some other thing that I could not figure out.请注意,奇怪的是, EVP_VerifyFinal的返回值不是0而是-1 :如果它是0那么这将是无可辩驳的签名验证失败,但事实并非如此,它发生了一些我无法弄清楚的其他事情。 In the following I past the code used to sign and verify.在下面我过去了用于签名和验证的代码。

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);
}

On the web I found some solution but either they are not in C, or/and they are deprecated don't solve the problem (one solution that I found here generates a private key that it doesn't even sign for some reason...)在 web 上,我找到了一些解决方案,但是它们不在 C 中,或者/并且它们已被弃用并不能解决问题(我在这里找到的一个解决方案会生成一个私钥,由于某种原因它甚至没有签名。 .)

Your verify code makes a assumation that is incorrect.您的验证码做出了不正确的假设。

If you read the EVP_PKEY_size documentation is states:如果您阅读EVP_PKEY_size文档是状态:

VP_PKEY_size() returns the maximum suitable size for the output buffers for almost all operations that can be done with pkey. VP_PKEY_size() 为几乎所有可以使用 pkey 完成的操作返回 output 缓冲区的最大合适大小。

The sign function is correctly returning the reported signature size based on the returned value from EVP_SignFinal .符号 function 根据EVP_SignFinal的返回值正确返回报告的签名大小。

The verify funciton is assuming that the size of the signature is the returned value from EVP_PKEY_size.验证功能假设签名的大小是 EVP_PKEY_size 的返回值。 This is most likely incorrect.这很可能是不正确的。

You need to change how you "store" the signature to also store it's size somewhere as well.您需要更改“存储”签名的方式,以便还将其大小存储在某处。 You can confirm that the size looks correct in that is must always be <= the returned value from EVP_PKEY_size.您可以确认大小看起来正确,因为它必须始终 <= EVP_PKEY_size 的返回值。

eg If you had a storage format like:例如,如果您有如下存储格式:

[file data][signature][size]EOF [文件数据][签名][大小]EOF

You could change your verify to look like this:您可以将验证更改为如下所示:

void verify(const unsigned char* const file_buf, unsigned long* const file_size, const char* const pubkey_file_name){
   // declare some useful variables:
   ...
   unsigned sgnt_size;
   unsigned max_sgnt_size;
   
   ...

   max_sgnt_size = (unsigned) EVP_PKEY_size(pubkey);

   ...

   if(*file_size < sizeof(unsigned)) {
       fprintf(stderr, "Error: File size to small\n");
       exit(1);
   }

   *file_size -= sizeof(unsigned);
   sgnt_size = *(const unsigned *)(file_buf + *file_size);
   if(sgnt_size > max_sgnt_size) {
       fprintf(stderr, "Error: Invalid signature size\n");
       exit(1);
   }

   if(sgnt_size <= *file_size) {
       fprintf(stderr, "Error: Invalid file size\n");
       exit(1);
   }

   *file_size -= (unsigned long)sgnt_size;

   ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM