ECC算法分析--openssl的实现以及其调用流程

晨曦之光 发布于 2012/04/10 15:00
阅读 3K+
收藏 1

ecc的过程与rsa相比有很大的不同,ecc涉及到了很多额外的概念,比如group等等,另外ecc包含两套截然不同的机制,这就是ecdsa和ecdh,这两套机制统一于ecc,在非ecc算法中,这两套机制是由两个独立的算法实现的,比如对于加密/解密以及签名/验证这一类需求来说使用的是rsa,dsa,对于密钥协商这一类需求来说使用的是dh,如何把这两类算法统一到一个结构中是类似openssl等框架需要解决的问题,openssl将所有的关于“密钥”的信息全部放入一个结构体中,这个结构体就是EVP_PKEY,这些信息包括密钥本身和关于这些密钥的操作,这些操作中最重要的就是诸如产生密钥以及验证签名之类的操作了,由于密钥操作仅仅是一些标准,各个机构可以有自己的定制化实现,于是engine的作用就体现出来了,按照oo的观点,将engine封装到key当中是个很好的设计,因为几乎所有的加密解密签名验证操作都是围绕着key进行的,所以engine几乎都在key相关的结构中,另外对于对称算法,engine专门提供了一个机制,详细信息见前文。
struct evp_pkey_st
{
    int type;
    int save_type;
    int references;
    union{
        char *ptr;
#ifndef OPENSSL_NO_RSA
        struct rsa_st *rsa;    /* RSA */
#endif
...
#ifndef OPENSSL_NO_EC
        struct ec_key_st *ec;    /* ECC */
#endif
    } pkey;
    int save_parameters;
    STACK_OF(X509_ATTRIBUTE) *attributes; /* [ 0 ] */
...
}
对于rsa来讲很简单,一个下面的rsa_st足以容纳所有的数据和操作,包括公钥,私钥,sign,verify等等:
struct rsa_st
{
    int pad;
    long version;
    const RSA_METHOD *meth; //可以重载的方法,所有的关于rsa的操作全部在这个meth里面
    /* functional reference if 'meth' is ENGINE-provided */
    ENGINE *engine; //当前的engine,内部包含一个RSA_METHED,该methed可以将上面的meth重载
    BIGNUM *n;
    BIGNUM *e;
    BIGNUM *d;
    BIGNUM *p;
    BIGNUM *q;
    BIGNUM *dmp1;
    BIGNUM *dmq1;
    BIGNUM *iqmp;
    ...
}
但是对于ecc来说就没有这么简单了,虽然它也有一个类似rsa_st的ec_key_st结构,很显然这个结构体中存放的也是关于密钥的信息或者说是密钥本身以及关于密钥的操作,但是这个结构体却要服务于两个机制,就是上面所提到的密钥协商算法和公钥/私钥算法(或者称作签名算法,因为几乎不用ecc的ecdsa进行加密/解密和数字信封),一种可行但是不合理的组织方式就是将所有这两类算法中的所有操作函数全部放到一个结构体比如叫做ecc_methed_st中,然后将此结构体加入到engine结构中,完全类似rsa的做法虽然看似很简洁,但是却不合理,因为签名算法和密钥协商算法之间的关系并不是很紧密,将它们强制粘合在一起很出现很多的问题,比如一个engine实现者利用自己的硬件加密卡实现了一个ecc的签名算法,但是他却无心去管什么密钥协商之类的事情,于是为了ecc_methed_st结构的完整性,他不得不去获取ecc对于密钥协商的默认实现,然后加入到ecc_methed_st中,这种工作完全可以通过更好的设计来避免的。于是将二者分开,让其分别属于两个engine,也就是说不同的engine管理不同的算法,于是就有了下面的结构体:
typedef struct ecdh_data_st {
    /* EC_KEY_METH_DATA part */
    int (*init)(EC_KEY *);
    /* method specific part */
    ENGINE    *engine;
    int    flags;
    const ECDH_METHOD *meth;
    CRYPTO_EX_DATA ex_data;
} ECDH_DATA;
typedef struct ecdsa_data_st {
    /* EC_KEY_METH_DATA part */
    int (*init)(EC_KEY *);
    /* method (ECDSA) specific part */
    ENGINE    *engine;
    int    flags;
    const ECDSA_METHOD *meth;
    CRYPTO_EX_DATA ex_data;
} ECDSA_DATA;
可以看出,这两个结构体与rsa的非常相似,为了不触动EVP_PKEY的优良结构,必然需要一个同样设计优良的ec_key_st结构体,这个结构体可以动态决定是使用ECDH_DATA还是使用ECDSA_DATA,也就是一个engine开关的作用,从设计的层面来理解ec_key_st的话,它的内容实际上就是ecdh和ecdsa公共的信息,根据ecc的原理,它们公共的信息就是一个公钥和一个私钥,不同的是公私钥的操作以及用途,而这些操作和用途都会在engine中得到体现,于是就有了以下结构体:
struct ec_key_st {
    int version;
    EC_GROUP *group;  //曲线组,这是ecc的核心概念
    EC_POINT *pub_key;  //公钥,在ecc中,弓腰就是一个曲线上的“点”
    BIGNUM     *priv_key; //私钥,在ecc中,私钥是一个大数,通过这个大数和“基点”相乘能得到公钥
    unsigned int enc_flag;
    point_conversion_form_t conv_form;
    int     references;
    EC_EXTRA_DATA *method_data; //这个结构体动态确定是dh还是dsa或者别的什么。注释0
} /* EC_KEY */;
注释0说还会有别的什么,这是什么意思?实际上ec_key_st的method_data字段是一个链表:
typedef struct ec_extra_data_st {
    struct ec_extra_data_st *next;
    void *data;  //这个data就是上面的ECDSA_DATA或者ECDH_DATA以及别的什么methed
    void *(*dup_func)(void *);
    void (*free_func)(void *);
    void (*clear_free_func)(void *);
} EC_EXTRA_DATA; /* used in EC_GROUP */
由于method_data是动态决定的,那么谁来决定到底是个什么method呢,这实际上是由调用者决定的,因为通过程序的执行流程可以知道此时需要做什么,然后就可以得到相应的method_data结构体了,然后就可以交给该method的engine来实现功能了。
    接下来用一个实用的流程来体验一把openssl对于ecc签名验证的执行过程:
1.openssl命令后面加入verify参数,后面接入ca证书和用户证书;
2.进入verify.c,提取证书,验证证书链(参见前面的),提取签名算法,提取公钥;
3.X509_get_pubkey得到EVP_PKEY结构。根据x509结构可以得到签名算法以及公钥本身,然后根据签名算法的oid可以得到相应的密钥的type,然后:
else if (ret->type == EVP_PKEY_EC) {
    if (a->parameter && (a->parameter->type == V_ASN1_SEQUENCE)){
            if ((ret->pkey.ec= EC_KEY_new()) == NULL) //初始化ec_key_st
...
4.进入X509_verify进行验证签名,首先需要对签名的消息自己重新做一起摘要,然后再验证,函数ASN1_item_verify的逻辑很明白,本着深度优先的原则先进入ASN1_item_verify并且深入理解之:
int ASN1_item_verify(const ASN1_ITEM *it, X509_ALGOR *a, ASN1_BIT_STRING *signature, void *asn, EVP_PKEY *pkey)
{
    EVP_MD_CTX ctx;
    const EVP_MD *type;
    EVP_MD_CTX_init(&ctx);
    i=OBJ_obj2nid(a->algorithm); //得到算法的nid
    type=EVP_get_digestbyname(OBJ_nid2sn(i)); //得到对应nid算法的摘要处理函数结构,注释1
    EVP_VerifyInit_ex(&ctx,type, NULL);
    inl = ASN1_item_i2d(asn, &buf_in, it);
    EVP_VerifyUpdate(&ctx,(unsigned char *)buf_in,inl);
    EVP_VerifyFinal(&ctx,(unsigned char *)signature->data,
            (unsigned int)signature->length,pkey);//实现验证
...
}
对于注释1有必要解释一下,EVP_MD是一个很重要的数据结构,其实也是一个数据以及操作封装在一起的结构体,实现了一个签名以及签名验证逻辑框架,EVP_MD是一个逻辑上比engine更上层的封装,EVP_MD可以看作是逻辑封装,而engine则是实现这些逻辑的算法封装:
struct env_md_st {
    int type;
    int pkey_type;
    int md_size;
    unsigned long flags;
    int (*init)(EVP_MD_CTX *ctx); //初始化
    int (*update)(EVP_MD_CTX *ctx,const void *data,size_t count); //操作
    int (*final)(EVP_MD_CTX *ctx,unsigned char *md); //操作结束
    int (*copy)(EVP_MD_CTX *to,const EVP_MD_CTX *from);
    int (*cleanup)(EVP_MD_CTX *ctx);
    int (*sign)(int type, const unsigned char *m, unsigned int m_length,
            unsigned char *sigret, unsigned int *siglen, void *key);
    int (*verify)(int type, const unsigned char *m, unsigned int m_length,
              const unsigned char *sigbuf, unsigned int siglen,
              void *key);
    int required_pkey_type[5]; /*EVP_PKEY_xxx */
    int block_size;
    int ctx_size;
} /* EVP_MD */;
EVP_MD不但实现了摘要算法,还包含了签名以及验证的过程封装函数,实际上所谓的签名算法就是摘要算法和不对称私钥加密的组合算法,比如说摘要算法有很多种,md5,sha1等等,而不对称私钥加密算法也有很多,比如rsa,dsa,ecdsa等等,于是签名算法就是它们的笛卡儿积。EVP_MD的init,updatefinal基本就是在计算摘要了,而往往在更更上层的逻辑中在调用完final之后要调用verify来验证签名或者调用sign来签名,比如EVP_VerifyFinal和EVP_SignFinal,EVP_系列函数是openssl密码学实现最上层的逻辑。下面接着4开始看看过程5。
5.调用EVP_VerifyInit_ex,该函数会从默认engine中寻找对应type的EVP_MD,如果你有自己的engine并且实现了对应的EVP_MD,那么就可以通过加载自己的engine来使用自己的EVP_MD;顺便说一句,EVP_MD从engine中的提取过程和cipher从engine中的提取过程无异。
6.调用EVP_VerifyUpdate,这是通过调用在第5步初始化好的EVP_MD中的update回调函数完成的;
7.调用EVP_VerifyFinal完成最后的步骤。在EVP_VerifyFinal中首先调用EVP_DigestFinal_ex完成摘要的计算过程,随后调用EVP_MD的verify回调函数:
EVP_DigestFinal_ex(&tmp_ctx,&(m[0]),&m_len);
i = ctx->digest->verify(ctx->digest->type,m,m_len, sigbuf,siglen,pkey->pkey.ptr);
8.对于ecc来讲,该回调函数verify就是ECDSA_verify,该函数最终调用ECDSA_do_verify:
int ECDSA_do_verify(const unsigned char *dgst, int dgst_len,
        const ECDSA_SIG *sig, EC_KEY *eckey)
{ //该回调函数最后一个参数之所以会随着算法的不同而不同是因为pkey成员是个联合
    ECDSA_DATA *ecdsa = ecdsa_check(eckey);
    return ecdsa->meth->ecdsa_do_verify(dgst, dgst_len, sig, eckey);
}
9.最终ecdsa_check决定了现在要使用的是EC_KEY中的method_data中的类型为ECDSA_DATA的数据;
10.接下来调用ECDSA_METHOD的ecdsa_do_verify完成最后的操作。
以上10个步骤就是verify命令需要执行的步骤,对于x509命令签发证书的步骤来讲也是一样的,只不过将verify换成了sign而已。如果对比rsa的签名和验证过程就会发现ecc多了一个步骤,这个多出来的步骤就是ecdsa_check的过程。
     到此为止,ecc的大致实现已经十分清晰了,但是ecc到底是什么,ecc密钥到底怎么回事还是一个问题,仅仅从编程的角度理解ecc是不够的,下面就依次介绍ecc的原理,如果只会编程的话,充其量是个IT民工,所以对于一项技术,不但要知其然还要知其所以然。


原文链接:http://blog.csdn.net/dog250/article/details/5540500
加载中
返回顶部
顶部