1. 程式人生 > >ECC演算法分析--openssl的實現以及其呼叫流程

ECC演算法分析--openssl的實現以及其呼叫流程

               

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民工,所以對於一項技術,不但要知其然還要知其所以然。