openssl rsa cpp 相关接口示例

使用openssl进行开发时,发现网上找到的接口版本过时,特分享下一些简单demo避免踩坑。

1. 常用命令

#1. 生成RSA秘钥
openssl genrsa -out test.key 2048
#2. 生成服务器证书申请(用于请求证书才需要,自签可跳过)
openssl req -new -sha256 -key test.key -out test.csr
#3. 自己签发客服务器证书
openssl req -new -x509 -key test.key -out test.dvc -days 1095


# 私钥导出公钥
openssl rsa -in private.key -pubout -out public.key
# 证书导出公钥
openssl x509 -in certificate.crt -pubkey -noout > public.key

# 查看公钥信息
openssl rsa -in private.key -text -noout
# 查看证书信息
openssl x509 -in certificate.crt -text -noout

# 导出bio信息
openssl rsa -in rsa.key -outform PEM -out rsa.pem

2. 基本常识:

2.1 非对称

RSA是否为公钥取决于秘钥是否公布出来。理论上公钥和私钥是完全对称的. 你可以随机选一个当私钥,另一个当公钥,他俩用一个推算出另一个的难度等同于暴力破解rsa。但在具体应用中:

  1. 软件生成的私钥里面包含了生成公钥的信息,所以他俩就不对称了,如果公钥弄丢了,可以很轻松的从私钥再生成一个,就几秒钟的时间
  2. 在算法中为了提高加密速度并没有有意避免私钥暴力生成公钥,所以即使没有第1条所说的信息,从私钥暴力生成公钥的难度远远小于反过来

2.2 加解密及相关参数

  • RSA签名填充模式 一般签名验签pss,加解密oaep 默认的padding不安全(每次计算结果相同)。
  • RSA加解密、签名验签内容长度不可超过key长度,比较重要的参数 (公钥的 e指数 n模数)
  1. e是加密指数,是一个随机数,满足1<e<n-1,并且e和(n-1)的最大公约数必须为 1。
  2. n是模数,是p和q的乘积,计算公式为n=p*q。
  3. d:解密指数,是e的逆元,可以通过e*d≡1(modφ(n))计算得到,其中φ(n)是n的欧拉函数。
  4. p和q:这两个质数是n的因数,必须保密,只有知道p和q的值才能计算出n的值。
  5. φ(n):欧拉函数,是小于n且与n互质的正整数的个数,可以通过φ(n)=(p-1)(q-1)计算得到。
  6. dp和dq:d关于p和q的指数,可通过dp=e^-1 mod (p-1)和dq=e^-1 mod (q-1)计算。
  7. k:随机数,用于在加密过程中生成密文。
  • 在线解析RSA公私钥模数指数等信息:

不要再使用 RSA_public_encrypt RSA_private_decrypt 这类使用 RSA* 的接口了,在新版编译时会报api弃用的警告 现在都是创建 EVP_PKEY 包含了ecc等类型的key来做。下面是创建key的接口示例:

// 通用接口
#include "openssl/engine.h"
#include "openssl/err.h"
#include "openssl/evp.h"
#include "openssl/rsa.h"
#include "openssl/ssl.h"

#include <string.h>

# define FUNC_ERR 1234
#define OPENSSL_FUNCTION_JUDGE(rv, function_name, label) \
    if (rv) \
    { \
    char *error_str = ERR_error_string(ERR_get_error(), NULL); \
    printf("    [%s:%d], %s Error: %s\n", __FILE__, __LINE__, #function_name, error_str); \
    goto label; \
    } \
    else \
    { \
    if (verbose_flag) \
        printf(#function_name": PASS\n"); \
    }

typedef enum RsaType {
	rsa_private_type,
	rsa_public_type,
	rsa_cert_type,
} RsaTypeEnum;

EVP_PKEY* get_rsa_key(const char* key, RsaTypeEnum type, int key_len) {
    EVP_PKEY* evp_key = NULL;    
    BIO* keybio = NULL ;    
    keybio= BIO_new_mem_buf(key, key_len); // key_len = -1, use strlen(key)
    if(keybio==NULL) {
        printf("Failed to create key BIO\n");
        return evp_key;   
    }
    if(type == rsa_private_type) {
        evp_key= PEM_read_bio_PrivateKey(keybio, NULL, NULL, NULL);
    } else if (type == rsa_public_type) {
        evp_key = PEM_read_bio_PUBKEY(keybio, NULL, NULL, NULL);
    } else {
        X509* cert_x509 = PEM_read_bio_X509(keybio, NULL, 0, NULL);
        evp_key = X509_get_pubkey(cert_x509);
        X509_free(cert_x509);
    }
    if(evp_key== NULL) {
        printf("Failed to create RSA\n");   
    }
    BIO_free(keybio);
    return evp_key;
}

3. 公钥加密 私钥解密

unsigned long int rsa_public_encrypt(
        unsigned char*      pPlain,
        unsigned long int   PlainLen,
        unsigned char*      pCipher,
        unsigned long int*	pCipherLen) {
    EVP_PKEY_CTX *ctx = NULL;
    EVP_PKEY *pub_key = NULL;
    int result = 0;
    pub_key = get_rsa_key(rsa_public_key, rsa_public_type, strlen(rsa_public_key)); 
    ctx = EVP_PKEY_CTX_new(pub_key, NULL); /* no engine */
    OPENSSL_FUNCTION_JUDGE(ctx==NULL, EVP_PKEY_CTX_new, func_end);

    result = EVP_PKEY_encrypt_init(ctx);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_encrypt_init, func_end);
    result = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
    OPENSSL_FUNCTION_JUDGE(result<=0, "RSA_PKCS1_OAEP_PADDING", func_end);
    result = EVP_PKEY_encrypt(ctx, pCipher, pCipherLen, pPlain, PlainLen);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_encrypt_init, func_end);

    /* Encrypted data is outlen bytes written to buffer out */
func_end:
    EVP_PKEY_free(pub_key);
    EVP_PKEY_CTX_free(ctx);
    return result == 1 ? 0 : FUNC_ERR;
}

unsigned long int rsa_private_decrypt(
		unsigned char*		pCipher,
		unsigned long int	CipherLen,
		unsigned char*		pPlain,
		unsigned long int*	pPlainLen) {
    EVP_PKEY_CTX *ctx = NULL;
    EVP_PKEY *priv_key = NULL;
    int result = 0;
    priv_key = get_rsa_key(rsa_private_key, rsa_private_type, strlen(rsa_private_key)); 
    ctx = EVP_PKEY_CTX_new(priv_key, NULL); /* no engine */
    OPENSSL_FUNCTION_JUDGE(ctx==NULL, EVP_PKEY_CTX_new, func_end);

    result = EVP_PKEY_decrypt_init(ctx);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_encrypt_init, func_end);
    result = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
    OPENSSL_FUNCTION_JUDGE(result<=0, "RSA_PKCS1_OAEP_PADDING", func_end);
    result = EVP_PKEY_decrypt(ctx, pPlain, pPlainLen, pCipher, CipherLen);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_decrypt, func_end);

    /* Encrypted data is outlen bytes written to buffer out */
func_end:
    EVP_PKEY_free(priv_key);
    EVP_PKEY_CTX_free(ctx);
    return result == 1 ? 0 : FUNC_ERR;
}

4. 私钥签名(加密) 公钥验签(解密)

这里根据传入的哈希仅支持sha256 sha512,仅提供一个模板

unsigned long int rsa_private_sign(
        const unsigned char*    hash,
        unsigned long int       hash_len,
        unsigned char*          sig,
        unsigned long int*      p_sign_len) {
    int result = 0;
    EVP_PKEY_CTX *ctx = NULL;
    EVP_PKEY *signing_key = NULL;
    const EVP_MD* hash_type = NULL ;
	switch (hash_len) {
	case 256/8:
		hash_type = EVP_sha256();
        printf("use digest 256\n");
		break;
	case 512/8:
		hash_type = EVP_sha512();
        printf("use digest 512\n");
		break;
	default:
        printf("    Error: invalid digest data\n");
        goto func_end;
	}

    signing_key = get_rsa_key(rsa_private_key, rsa_private_type, strlen(rsa_private_key));
    ctx = EVP_PKEY_CTX_new(signing_key, NULL); /* no engine */
    OPENSSL_FUNCTION_JUDGE(ctx==NULL, EVP_PKEY_CTX_new, func_end);

    result = EVP_PKEY_sign_init(ctx);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_sign_init, func_end);
    result = RSA_pkey_ctx_ctrl(ctx, -1, EVP_PKEY_CTRL_RSA_PADDING, RSA_PKCS1_PSS_PADDING, NULL);
    OPENSSL_FUNCTION_JUDGE(result<=0, "RSA_PKCS1_PSS_PADDING", func_end);
    result = EVP_PKEY_CTX_set_signature_md(ctx, hash_type);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_CTX_set_signature_md, func_end);
    result = EVP_PKEY_sign(ctx, sig, p_sign_len, hash, hash_len);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_sign, func_end);

func_end:
    EVP_PKEY_free(signing_key);
    EVP_PKEY_CTX_free(ctx);
    return result == 1 ? 0 : FUNC_ERR;
}

unsigned long int openssl_rsa_public_verify(
        const unsigned char*    hash, 
        unsigned long int       hash_len,
        unsigned char*          sig, 
        unsigned long int       sig_len) {
    int result = 0;
    EVP_PKEY_CTX *ctx = NULL;
    EVP_PKEY *verify_key = NULL;
    const EVP_MD* hash_type = NULL ;
	switch (hash_len) {
	case 256/8:
		hash_type = EVP_sha256();
		break;
	case 512/8:
		hash_type = EVP_sha512();
		break;
	default:
        printf("    Error: invalid digest data\n");
        goto func_end;
	}

    verify_key = get_rsa_key(rsa_public_key, rsa_public_type, strlen(rsa_public_key)); 
    ctx = EVP_PKEY_CTX_new(verify_key, NULL); /* no engine */
    OPENSSL_FUNCTION_JUDGE(ctx==NULL, EVP_PKEY_CTX_new, func_end);

    result = EVP_PKEY_verify_init(ctx);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_verify_init, func_end);
    result = RSA_pkey_ctx_ctrl(ctx, -1, EVP_PKEY_CTRL_RSA_PADDING, RSA_PKCS1_PSS_PADDING, NULL);
    OPENSSL_FUNCTION_JUDGE(result<=0, "RSA_PKCS1_PSS_PADDING", func_end);
    result = EVP_PKEY_CTX_set_signature_md(ctx, hash_type);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_CTX_set_signature_md, func_end);
    result = EVP_PKEY_verify(ctx, sig, sig_len, hash, hash_len);
    OPENSSL_FUNCTION_JUDGE(result<=0, EVP_PKEY_verify, func_end);

func_end:
    EVP_PKEY_free(verify_key);
    EVP_PKEY_CTX_free(ctx);
    return result == 1 ? 0 : FUNC_ERR;
}

5. RSA公私钥信息解析

关于解析rsa公钥的模数、指数等操作可以通过网页在线获取,这里提供一个cpp的版本解析:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <openssl/pem.h>
#include <openssl/rsa.h>

void printBuffer(const char* name, unsigned char* pData, unsigned long int size)
{
	printf("--------------%s--------------\n", name);
	printf(" [00] ");
	for(CK_ULONG i = 0; i < size; i++) {
		if ((i % 16 == 0) && (i != 0)) {
			printf("\n [%02lu] ", i);
		}
		printf("%02x ", pData[i]);
	}
	printf("\n-----------------------------\n");
}

void test_write_pem() {
    RSA* r = RSA_new();

    BIGNUM* bne = BN_new();
    BN_set_word(bne, RSA_3);
    int ret = RSA_generate_key_ex(r, 512, bne, NULL);
    printf("RSA_generate_key_ex ret:[%d] \n", ret);
    BN_free(bne);

    BIO* bioPri = BIO_new_file("private.pem", "w");
    ret = PEM_write_bio_RSAPrivateKey(bioPri, r, EVP_des_ede3_cbc(), NULL, 0, NULL, NULL);
    printf("PEM_write_bio_RSAPrivateKey ret:[%d] \n", ret);
    BIO_free(bioPri);

    BIO* bioPub = BIO_new_file("public.pem", "w");
    ret = PEM_write_bio_RSAPublicKey(bioPub, r);
    printf("PEM_write_bio_RSAPublicKey ret:[%d] \n", ret);
    BIO_free(bioPub);

    RSA_free(r);
}

void test_read_pem() {
    BIO* bioPri = BIO_new_file("private.pem", "r");
    RSA* rPri = PEM_read_bio_RSAPrivateKey(bioPri, NULL, NULL, NULL);
    printf("PEM_read_bio_RSAPrivateKey ret:[%p] \n", rPri);
    BIO_free(bioPri);
    RSA_free(rPri);

    BIO* bioPub = BIO_new_file("public.pem", "r");
    RSA* rPub = PEM_read_bio_RSAPublicKey(bioPub, NULL, NULL, NULL);
    printf("PEM_read_bio_RSAPublicKey ret:[%p] \n", rPub);
    BIO_free(bioPub);
    RSA_free(rPub);
}

// 0-9 0x30-0x39 
// A-F 0x41-0x46
// a-f 0x61-0x66
#define hex2num(c) ((c >'9'? c + 9 : c) & 0x0f)
int hex2byte(char* hex, unsigned char* Byte, int* p_Byte_len) {
    int len = strlen(hex), i = 0, j = 0;
    if(*p_Byte_len < len || len == 0) {
        return CKR_BUFFER_TOO_SMALL;
    }
    if(len % 2 == 0) {
        Byte[i++] = hex2num(hex[j]) << 4 | hex2num(hex[j+1]);
        j+=2;
    } else {
        Byte[i++] = hex2num(hex[j]);
        j++;
    }
    while(j < len) {
        Byte[i++] = hex2num(hex[j]) << 4 | hex2num(hex[j+1]);
        j+=2;
    }
    *p_Byte_len = i;
    return 0;
}

// 这里可以 通过 BIO_new_file 读pem文件,或者 BIO_new_mem_buf 读取hex数组
int ImportPubKey() {
    int Result = 0;
    int run_ok = 0;

    BIO* PubBuf = NULL;
    EVP_PKEY* EvpKey = NULL;
    RSA* RSAPubKey = NULL;
    const BIGNUM* RSA_PUB_N = NULL, * RSA_PUB_E = NULL; 
    // key len 4096, N_hex len is 512, N_byte len is 256
    unsigned char N_Byte[1024], E_Byte[16];
    char* temp_hex = NULL;
    int N_Byte_len = sizeof(N_Byte), E_Byte_len = sizeof(E_Byte);
 
    // BIO* bioPub = BIO_new_file("public.pem", "r");                   // get pem file data
    PubBuf = BIO_new_mem_buf(rsa_public_key, strlen(rsa_public_key));   // key_len = -1, use strlen(key)
    OPENSSL_FUNCTION_JUDGE(PubBuf == NULL, BIO_new_mem_buf, func_end);
    EvpKey = PEM_read_bio_PUBKEY(PubBuf, NULL, NULL, NULL);
    OPENSSL_FUNCTION_JUDGE(EvpKey == NULL, PEM_read_bio_PUBKEY, release1);
    RSAPubKey = EVP_PKEY_get1_RSA(EvpKey);
    OPENSSL_FUNCTION_JUDGE(RSAPubKey == NULL, EVP_PKEY_get1_RSA, release2);
    
    RSA_PUB_N = RSA_get0_n(RSAPubKey);
    RSA_PUB_E = RSA_get0_e(RSAPubKey);
    if(RSA_PUB_N == NULL || RSA_PUB_E == NULL) {
        printf("    ERROR: cant get RSA public key N E information\n");
        goto release3;
    }

    temp_hex = BN_bn2hex(RSA_PUB_N);
    OPENSSL_FUNCTION_JUDGE(temp_hex == NULL, BN_bn2hex, release3);
    Result = hex2byte(temp_hex, N_Byte, &N_Byte_len);
    if(Result != 0) {
        goto release3;
    }
    printf("RSA_PUB_N: 0x%s\n", temp_hex);
    printBuffer("N_Byte", N_Byte, N_Byte_len);
    OPENSSL_free(temp_hex);

    temp_hex = BN_bn2hex(RSA_PUB_E);
    OPENSSL_FUNCTION_JUDGE(temp_hex == NULL, BN_bn2hex, release3);
    Result = hex2byte(temp_hex, E_Byte, &E_Byte_len);
    if(Result != 0) {
        goto release3;
    }
    printf("RSA_PUB_E: 0x%s\n", temp_hex);
    printBuffer("E_Byte", E_Byte, E_Byte_len);
    OPENSSL_free(temp_hex);

release3:
    RSA_free(RSAPubKey);
release2:
    EVP_PKEY_free(EvpKey);
release1:
    BIO_free(PubBuf); 
func_end:
    return run_ok == CK_TRUE ? CKR_OK : CKR_FUNCTION_FAILED;
} 

/* 密钥的信息类似获取 这里略
    RSA_PRI_N = RSA_get0_n(RSAPriKey);
    RSA_PRI_E = RSA_get0_e(RSAPriKey);
    RSA_PRI_P = RSA_get0_p(RSAPriKey);
    RSA_PRI_Q = RSA_get0_q(RSAPriKey);
    RSA_PRI_D = RSA_get0_d(RSAPriKey);
    RSA_PRI_DMP1 = RSA_get0_dmp1(RSAPriKey);
    RSA_PRI_DMQ1 = RSA_get0_dmq1(RSAPriKey);
    RSA_PRI_IQMP = RSA_get0_iqmp(RSAPriKey);
*/