ECC演算法分析–openssl的實現以及其調用流程

來源:互聯網
上載者:User

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.