目录

openssl ECDSA 库消息签名和验签

密钥生成

椭圆曲线参数选择

使用以下命令列出所有支持的曲线:

openssl ecparam -list_curves

若支持目标曲线,就可以在openssl/include/openssl/obj_mac.h文件中找到相应的 NID

命令行生成与读取

这里椭圆曲线参数由name指定,如NIST P-256(secp256r1,在 OpenSSL 称为prime256v1)

生成秘钥

openssl ecparam -genkey -name prime256v1 -param_enc explicit -outform pem -out ec_prikey.pem

获取公钥

openssl ec -in ec_prikey.pem -pubout -out ec_pubkey.pem

代码生成

生成密钥对

1
2
3
4
5
6
#include "openssl/ec.h"
#include "openssl/obj_mac.h"
EC_KEY         *key_pair = NULL;
int             ret_error;
key_pair = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
ret_error = EC_KEY_generate_key(key_pair);

方法说明

EC_KEY结构体内容如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct ec_key_st {
    int version;
    EC_GROUP  *group; 
    EC_POINT  *pub_key;  
    BIGNUM    *priv_key; 
    unsigned int enc_flag;
    point_conversion_form_t conv_form;
    int     references;
    int 	flags;
    EC_EXTRA_DATA *method_data;
} /* EC_KEY */;

函数方法说明

1
EC_KEY *EC_KEY_new_by_curve_name(int nid);

根据相关曲线的 nid 来构造新的 EC_KEY

1
int EC_KEY_generate_key(EC_KEY *key);

EC_KEY_generate_keyEC_KEY 对象生成新的公钥和私钥。在调用此函数之前, EC_KEY 必须具有与之关联的 EC_GROUP 对象。私钥是一个随机整数(0 < priv_key < order,其中order是EC_GROUP对象的顺序)。公钥是曲线上的 EC_POINT ,通过将曲线的生成器乘以私钥计算得出。

读取密钥

pem文件读取

一种方法是直接读取为EC_KEY

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <openssl/pem.h>
#include "openssl/bn.h"

const char * prikey_path="ec_prikey.pem";
const char * pubkey_path="ec_pubkey.pem";

EVP_PKEY *pubKey = NULL;
EC_KEY *ec_key;
BIO    *bio_key_file = NULL;
/* pri key */
/*get key from pem file*/
bio_key_file = BIO_new_file(prikey_path, "rb");
ec_key = PEM_read_bio_ECPrivateKey(bio_key_file, NULL, NULL, NULL);
/* pub key */
bio_key_file = BIO_new_file(pubkey_path, "rb");
ec_key = PEM_read_bio_EC_PUBKEY(bio_key_file, NULL, NULL, NULL);

另一种方法是先读取为EVP_PKEY再转换为EC_KEY

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <openssl/pem.h>
#include "openssl/bn.h"

const char * prikey_path="ec_prikey.pem";
const char * pubkey_path="ec_pubkey.pem";

EVP_PKEY * pubKey = NULL;
EC_KEY * ec_key;
BIO    *bio_key_file = NULL;
/* get key from pem file */
bio_key_file = BIO_new_file(pubkey_path, "rb");
/* get pub key */
pubKey = PEM_read_bio_PUBKEY(bio_key_file, NULL, NULL, NULL);
ec_key = EVP_PKEY_get1_EC_KEY(pubKey);

字符串读取

仅BIO读取方式不同

1
2
3
4
5
6
7
8
9

char *str_in_pem = "str";
BIO    *bio_key = NULL;
EC_KEY *ec_key;
bio_key = BIO_new_mem_buf(str_in_pem, strlen(str_in_pem));
/* for pri key */
ec_key = PEM_read_bio_ECPrivateKey(bio_key, NULL, NULL, NULL);
/* for pub key */
ec_key = PEM_read_bio_EC_PUBKEY(bio_key, NULL, NULL, NULL);

方法说明

PEM_read_bio_ECPrivateKey 为例,在文档OpenSSL libraries里可能找不到这个方法,但是其实是能用的。获取EC_KEY有两种方式,要么接收返回值,要么在第二个参数传入key指针地址(若该指针已存在EC_KEY,则更新为现在读取到的EC_KEY,否则不做处理)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
EC_KEY *PEM_read_bio_ECPrivateKey(BIO *bp, EC_KEY **key, pem_password_cb *cb,
                                  void *u)
{
    EVP_PKEY *pktmp;
    pktmp = PEM_read_bio_PrivateKey(bp, NULL, cb, u);
    return pkey_get_eckey(pktmp, key); /* will free pktmp */
}

static EC_KEY *pkey_get_eckey(EVP_PKEY *key, EC_KEY **eckey)
{
    EC_KEY *dtmp;
    if (!key)
        return NULL;
    dtmp = EVP_PKEY_get1_EC_KEY(key);
    EVP_PKEY_free(key);
    if (!dtmp)
        return NULL;
    if (eckey) {
        EC_KEY_free(*eckey);
        *eckey = dtmp;
    }
    return dtmp;
}

消息签名

签名方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include "openssl/ecdsa.h"
#include "openssl/sha.h"

const char     *message = "message to sign";
unsigned char   buffer_digest[SHA256_DIGEST_LENGTH];
uint8_t        *digest;
uint8_t       *signature;
uint32_t        signature_len;
int             ret_error;

signature_len = ECDSA_size(key_pair);
signature     = (uint8_t *) OPENSSL_malloc(signature_len);
digest        = SHA256((const unsigned char *)message, strlen(message), buffer_digest);
ret_error     = ECDSA_sign(0, (const uint8_t *)digest, SHA256_DIGEST_LENGTH, signature, &signature_len, key_pair);

方法说明

1
2
int ECDSA_sign(int type, const unsigned char *dgst, int dgstlen,
               unsigned char *sig, unsigned int *siglen, EC_KEY *eckey);

ECDSA_sign()使用私有 EC 密钥 eckey 计算 dgstlen 字节哈希值 dgst 的数字签名。DER 编码的签名存储在 sig 中,其长度在 siglen 中返回。注意:sig 必须指向 ECDSA_size(eckey) 字节的内存。

return: 成功时返回 1,错误时返回 0

这里siglen传入的是指针,在签名后长度可能会比传入之前的siglen短。

消息验签

验签方法

变量来自前面

1
2
int verification;
verification = ECDSA_verify(0, digest, SHA256_DIGEST_LENGTH, signature, signature_len, key_pair);

方法说明

1
2
int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen,
                 const unsigned char *sig, int siglen, EC_KEY *eckey);

ECDSA_verify()使用公钥 eckey 验证长为 siglen 的签名 sig 是长为dgstlen 的哈希值 dgst 的有效 ECDSA 签名。

return: 有效签名返回 1,对无效签名返回 0,对错误返回 -1

这里siglen传入的不是指针