openssl

title: ECDSA加密算法
date: 2021-12-31 16:42:15
categories:
tags:
- openssl
- c/c++

1、ECDSA简述

ECDSA的全名是Elliptic Curve DSA,即椭圆曲线DSA。它是Digital Signature Algorithm (DSA)应用了椭圆曲线加密算法的变种。椭圆曲线算法的原理很复杂,但是具有很好的公开密钥算法特性,通过公钥无法逆向获得私钥。

ECDSA算法用于数字签名,是ECC与DSA的结合,整个签名过程与DSA类似,所不一样的是签名中采取的算法为ECC,最后签名出来的值也是分为r,s。

ECC是建立在基于椭圆曲线的离散对数问题上的密码体制,给定椭圆曲线上的一个点G,并选取一个整数k,求解K=kG很容易(注意根据kG求解出来的K也是椭圆曲线上的一个点);反过来,在椭圆曲线上给定两个点K和G,若使K=kG,求整数k是一个难题。ECC就是建立在此数学难题之上,这一数学难题称为椭圆曲线离散对数问题。其中椭圆曲线上的点K则为公钥(注意公钥K不是一个整数而是一个椭圆曲线点,这个点在OpenSSL里面是用结构体EC_Point来表示的,整数k则为私钥(实际上是一个大整数)。

签名过程如下:

1、选择一条椭圆曲线Ep(a,b),和基点G;

2、选择私有密钥k(k<n,n为G的阶),利用基点G计算公开密钥K=kG;

3、产生一个随机整数r(r<n),计算点R=rG;

4、将原数据和点R的坐标值x,y作为参数,计算SHA256做为hash,即Hash=SHA1(原数据,x,y);

5、计算s≡r - Hash * k (mod n)

6、r和s做为签名值,如果r和s其中一个为0,重新从第3步开始执行

验证过程如下:

1、接受方在收到消息(m)和签名值(r,s)后,进行以下运算

2、计算:sG+H(m)P=(x1,y1), r1≡ x1 mod p。

3、验证等式:r1 ≡ r mod p。

4、如果等式成立,接受签名,否则签名无效。

2、生成ECDSA公钥和私钥

其中,ECDSAPriKeyFileName、ECDSAPubKeyFileName为要保存公钥和私钥的文件路径。

int GeneratorRsaKey::CreateECDSAKey()
{
    int Ret = 0;
    EC_KEY* ec_key = NULL;    //椭圆曲线的参数、私钥和公钥都保存在这个结构中。
    EC_GROUP* ec_group;        //这个结构体保存着椭圆曲线的参数

    ec_key = EC_KEY_new();//通过调用EC_KEY_new()来构造没有关联曲线的新EC_KEY。


    if (!ec_key)
    {
        printf("Error:ec_key is err!\n");
        return Ret;
    }

    ec_group = EC_GROUP_new_by_curve_name(NID_secp256k1);  //根据指定的椭圆曲线来生成密钥参数。
    if (!ec_group)
    {
        printf("Error:ec_group is err\n");
        return Ret;
    }

    //asn1_标志值用于确定曲线编码是使用显式参数还是使用asn1 OID的命名曲线:许多应用程序仅支持后一种形式。
    EC_GROUP_set_asn1_flag(ec_group, OPENSSL_EC_NAMED_CURVE);
    //如果asn1_标志为OPENSSL_EC_NAMED_CURVE,则使用命名曲线形式,并且参数必须具有相应的命名曲线NID集
    EC_GROUP_set_point_conversion_form(ec_group, POINT_CONVERSION_UNCOMPRESSED);//获取曲线的点转换形式。

    if (1 != EC_KEY_set_group(ec_key, ec_group))//将EC_GROUP结构体的内容填充到EC_KEY中
    {
        printf("Error:EC_KEY_set_group err\n");
        return Ret;
    }
    //EC_KEY_generate_KEY()为提供的eckey对象生成新的公钥和私钥。在调用此函数之前,eckey必须有一个与之关联的EC_组对象.
    //私钥是一个随机整数(0<priv_key<order,其中order是EC_组对象的顺序)。公钥是曲线上的一个EC_点,通过曲线生成器乘以私钥计算得出。
    if (!EC_KEY_generate_key(ec_key))
    {
        printf("Error:EC_KEY_generate_key()  err\n");
        return Ret;
    }

    //保存公钥
    if (!ECDSAPubKeyFileName)
    {
        fprintf(stderr, "Cannot open file %s\r\n", ECDSAPubKeyFileName);
        return ERR_OPENFILE;
    }
    Ret = WritePublicKeyToFile(ECDSAPubKeyFileName, ec_key);

    //保存私钥
    if (!ECDSAPriKeyFileName)
    {
        fprintf(stderr, "Cannot open file %s\r\n", ECDSAPriKeyFileName);
        return ERR_OPENFILE;
    }
    Ret = WritePrivateKeyToFile(ECDSAPriKeyFileName, ec_key, ec_group);

    if (ec_key)
    {
        EC_KEY_free(ec_key);
        ec_key = NULL;
    }

    return 0;
}
int GeneratorRsaKey::WritePublicKeyToFile(const char* Path, EC_KEY* pKey)
{
    int Ret = 0;
    BIO* pBioFile = BIO_new_file(Path, "wb+");

    if (!pBioFile)
    {
        printf("Error:pBioFile err \n");
        return Ret;
    }

    if (1 != PEM_write_bio_EC_PUBKEY(pBioFile, pKey))  //写入公钥
    {
        printf("Error:PEM_write_bio_EC_PUBKEY err\n");
        return Ret;
    }
    Ret = ECDSA_SUCESS;
    BIO_free(pBioFile);
    return Ret;
}
int GeneratorRsaKey::WritePrivateKeyToFile(const char* Path, EC_KEY* pKey, EC_GROUP* ec_group)
{
    int Ret = 0;
    BIO* pBioFile = BIO_new_file(Path, "wb+");

    if (!pBioFile)
    {
        printf("Error:pBioFile err \n");
        return Ret;
    }

    PEM_write_bio_ECPKParameters(pBioFile, ec_group);
    if (1 != PEM_write_bio_ECPrivateKey(pBioFile, pKey, NULL, NULL, 0, NULL, NULL))  //写入私钥
    {
        printf("Error:PEM_write_bio_ECPrivateKey err\n");
        return Ret;
    }

    Ret = ECDSA_SUCESS;
    BIO_free(pBioFile);
    return Ret;

}
生成密钥
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==              
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIAEDdomPvSs/hjUobObUr5Kq4xfjcKDn+QV1ExnIsyPQoAcGBSuBBAAK
oUQDQgAEkHwZd/CTpWrcfuvXGbHDZbyfPna1hiAQlEsiSIaVTFu99IEwcTginJiM
9TEJrYJ5LODD4znP9kVDuRbcZaguaQ==
-----END EC PRIVATE KEY-----

*BgUrgQQACg==* 是椭圆曲线的关键参数,对应secp256k1标识。

secp256k1生成私钥每次私钥是不同的,但EC PARAMETERS都是相同的。

只有用不同的name指定不同曲线EC PARAMETERS才会不同。

生成公钥
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEkHwZd/CTpWrcfuvXGbHDZbyfPna1hiAQ
lEsiSIaVTFu99IEwcTginJiM9TEJrYJ5LODD4znP9kVDuRbcZaguaQ==
-----END PUBLIC KEY-----

生成的是非压缩格式的公钥。

密钥数据结构

密钥数据结构定义在openssl-1.1.1\crypto\ec\ec_local.h文件中。

struct ec_key_st {
    const EC_KEY_METHOD *meth;
    ENGINE *engine;
    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;
    CRYPTO_EX_DATA ex_data;
    CRYPTO_RWLOCK *lock;
};

参数解释:

1、 EC_KEY_METHOD *meth;
在结构体ec_method_st中列举了实现过程中用到的各种椭圆曲线算法,比如椭圆曲线点群的建立和释放,设置群参数,点的比较,点的加法和倍乘等等,覆盖面很广,几乎涉及所有的椭圆曲线算法。
其主要作用在于能够将函数在素域和二元域的接口统一起来。举个例子,“判断点是否在曲线上”只需要调用EC_POINT_is_on_curve函数,而无需考虑是二元域还是素域。

2、ENGINE *engine;

ENGINEOPENSSL预留的用以加载第三方加密库引擎,主要包括了动态库加载的代码和加密函数指针管理的一系列接口。如果要使用Engine,那么首先要加载该*Engine(比如ENGINE_load_XXXX),然后选择要使用的算法或者使用支持的所有加密算法(有相关函数)。这样你的应用程序在调用加解密算法时,它就会指向你加载的动态库里的加解密算法,而不是原先的OPENSSL*的库里的加解密算法。

3、EC_GROUP *group;

椭圆曲线数据结构:EC_GROUP,该结构不仅包含各个参数,还包含了各种算法;根据密钥参数group来生公私钥。

对于ECC算法来说,仅仅知道公钥和私钥是不能调用OpenSSL自带的签名和验签API,还需要知道对应的椭圆曲线。*BgUrgQQACg==* 是椭圆曲线的关键参数,对应secp256k1标识。所生产得密钥中这参数便是用来确认椭圆曲线的。

4、EC_POINT *pub_key;

EC_POINT,其中的大数X、Y和Z为雅克比投影坐标,向量;

5、BIGNUM *priv_key;

私钥,为一个大数。

6、unsigned int enc_flag;

当前定义了两个编码标志EC_PKEY_NO_PARAMETERSEC_PKEY_NO_PUBKEY。这些标志定义了调用i2d_ECPrivateKey()时如何将密钥转换为ASN1的行为。如果设置了EC_PKEY_NO_PARAMETERS,则曲线的公共参数不会与私钥一起编码。如果设置了EC_PKEY_NO_PUBKEY,则公钥不会与私钥一起编码。

7、point_conversion_form_t conv_form;

/** Enum for the point conversion form as defined in X9.62 (ECDSA)
 *  for the encoding of a elliptic curve point (x,y) */
typedef enum {
        /** the point is encoded as z||x, where the octet z specifies
         *  which solution of the quadratic equation y is  */
    POINT_CONVERSION_COMPRESSED = 2,   //表示采用点压缩。
        /** the point is encoded as z||x||y, where z is the octet 0x04  */
    POINT_CONVERSION_UNCOMPRESSED = 4,     //表示不采用压缩
        /** the point is encoded as z||x||y, where the octet z specifies
         *  which solution of the quadratic equation y is  */
    POINT_CONVERSION_HYBRID = 6            //表示混合使用,即既包含点压缩又包含未压缩
} point_conversion_form_t;

8、CRYPTO_EX_DATA ex_data;

额外的附加信息

9、CRYPTO_RWLOCK *lock;

加密时候的线程锁

3、通过ECDSA私钥进行签名

其中,hash、hashsize为其他算法(sha256/SM3)所生成的32位摘要。

使用256位EC密钥进行签名使用的是DER 编码模式,所以签名长度是不定的,在71 - 73 个字节之间

相关链接:

https://www.thinbug.com/q/17269238

https://learnblockchain.cn/article/1038

https://zhuanlan.zhihu.com/p/422864492

int GeneratorRsaKey::ECDSAPrivkeySign(uint8_t* hash_value, uint32_t hash_size) 
{
    unsigned char* buffer;
    unsigned int buf_len;
    int Ret = 0;
    int size = 0;
    //-------------ECDSA加密------------------------------
    EC_KEY* ec_key;
    BIO* pBioKeyFile;
    pBioKeyFile = BIO_new_file(ECDSAPriKeyFileName, "rb");
    ec_key = PEM_read_bio_ECPrivateKey(pBioKeyFile, NULL, NULL, NULL);  //读取私钥
    if (!ec_key)
    {
        printf("read ec_key err!\n");
        Ret = ERR_ECDSA;
        goto Failed;
    }
    buf_len = ECDSA_size(ec_key);//获取ECC密钥大小字节数
    buffer = (unsigned char*)malloc(buf_len); //分配内存,buffer用来保存签名后的数据
    if (!(Ret=ECDSA_sign(0, Hash, HashSize, buffer, &buf_len, ec_key))) 
    {
        printf("ECDSA_sign is err!\n");
        Ret = ERR_ECDSA;
        goto Failed;
    }

    if (ec_key)
    {
        EC_KEY_free(ec_key);
        ec_key = NULL;
    }

    if (pBioKeyFile)
    {
        BIO_free(pBioKeyFile);
        pBioKeyFile = NULL;
    }

    return 0;

Failed:
    return Ret;
}

其中,ECDSA_size函数用来获取ECC密钥的大小,后面生成的签名数据大小也是由他来决定。

int ECDSA_size(const EC_KEY *ec)
{
    int ret;
    ECDSA_SIG sig;
    const EC_GROUP *group;
    const BIGNUM *bn;

    if (ec == NULL)
        return 0;
    group = EC_KEY_get0_group(ec);  //获取EC_key中的曲线参数
    if (group == NULL)
        return 0;

    bn = EC_GROUP_get0_order(group); //获取group的order,是一个大数,具体干啥的不知道
    if (bn == NULL)
        return 0;

    sig.r = sig.s = (BIGNUM *)bn;  //初始化sig.r和sig.s
    ret = i2d_ECDSA_SIG(&sig, NULL); //通过i2d_ECDSA_SIG函数对sig结构体进行编码为DER结构,返回值为其编码大小。

    if (ret < 0)
        ret = 0;
    return ret;
}

其中,i2d_ECDSA_SIG函数中有个关键函数:ossl_encode_der_dsa_sig该函数输出ECDSA-Sig-Value的DER编码至pkt,由pkt用来存储编码内容。

签名值数据结构

ECDSA 的签名结果表示为两项。 ECDSA 的签名结果数据结构定义在 crypto\ec\ec_lcl.h 中。

一般情况,最后的结果rs是用asn1格式的DER编码封装的,至少的TLS签名和数字证书签名中是这样的,不是简单的r+s这样字节直接拼接.

关于der编码方式可参见这边博客:https://www.twblogs.net/a/5b7e3af92b71776838560af9/?lang=zh-cn

struct ECDSA_SIG_st {
    BIGNUM *r;
    BIGNUM *s;
};

这里使用SM3或者SHA256得到数据摘要后进行签名,以下面一个签名为例讲解DER编码后的签名序列。

buffer=[3046022100adf6eafb0a32f398a88819ee984333e4241764487bbb47e68adb0b6533d6454c022100a4d4e55e928f1ba32e53405dffb781ae0bccf2acb8c19a1095e440189c20d388]
// 0x30表示DER序列的开始
// 0x46 - 序列的长度(70字节)
// 0x02 - 一个整数值
// 0x21 - 整数的长度(33字节)
// R-00adf6eafb0a32f398a88819ee984333e4241764487bbb47e68adb0b6533d6454c
// 0x02 - 接下来是一个整数
// 0x21 - 整数的长度(33字节)
// S-00a4d4e55e928f1ba32e53405dffb781ae0bccf2acb8c19a1095e440189c20d388

4、通过ECDSA公钥验签

其中,hash、hashsize为其他算法(sha256/SM3)所生成的32位摘要。

int GeneratorRsaKey::ECDSAPubkeyVerify(unsigned char* buffer, unsigned int buf_len)
{   
    int Ret = 0;
    EC_KEY* ec_key = NULL;
    BIO* pBioKeyFile = NULL;
    pBioKeyFile = BIO_new_file(ECDSAPubKeyFileName, "rb");
    ec_key = PEM_read_bio_EC_PUBKEY(pBioKeyFile, NULL, NULL, NULL); //从pem文件中读取公钥
    if (!ec_key)
    {
        printf("read pubkey failed!\n");
    }
    Ret = ECDSA_verify(0, Hash, HashSize, buffer, buf_len, ec_key); //buffer为加密后的数据,buf_len为加密数据长度一般为71
    if (Ret == 0)
    {
        printf("ECDSA_verify failed!\n");
    }

    if (pBioKeyFile)
    {
        BIO_free(pBioKeyFile);
        pBioKeyFile = NULL;
    }

    if (ec_key)
    {
        EC_KEY_free(ec_key);
        ec_key = NULL;
    }

    return Ret;
}

5、如何使用别人给的公私密钥对生成对应的密钥文件?

查看openssl中所支持的椭圆曲线
openssl ecparam -list_curves

在这里插入图片描述

选择一条曲线,生成参数文件

Linux中openssl支持很对椭圆曲线,这里只贴了一部分,下面以曲线brainpoolP256r1为例。

openssl ecparam -name brainpoolP256r1 -out brainpool_test.pem
显示文件参数
openssl ecparam -in brainpool_test.pem -text -param_enc explicit -noout

在这里插入图片描述

使用参数文件生成私钥
openssl ecparam -in brainpool_test.pem -genkey -out brainpool_priv.key

在这里插入图片描述

从私钥中导出公钥
openssl ec -in brainpool_priv.key -pubout -out brainpool_pub.key

在这里插入图片描述

查看公私密钥对
openssl ec -in brainpool_priv.key -text -noout 

在这里插入图片描述
至此,我们可以用使用Linux中自带的openssl库和相关命令生成ecdsa公私密钥对,但是如果工作中只有上图32字节的私钥和64字节的公钥(除去固定的开头04,X、Y各32字节),怎么生成对应的密钥文件呢?

6、通过设置公私密钥对生成对应密钥文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过设置曲线名称、私钥、公钥的方式生成固定的公私密钥文件。通过相关命令验证,和上面用openssl命令生成的公私密钥文件是一样的。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值