linux核心實作了crypto接口,用于類似IPSec之類要在核心中實作的與作業系統綁定的安全機制,如果不是用于這樣的機制,不要使用核心中的crypto接口,總的來說,linux的crypto中最重要的結構體有兩個:crypto_tfm和crypto_alg
struct crypto_tfm {
u32 crt_flags;
union {
struct cipher_tfm cipher;
struct digest_tfm digest;
struct compress_tfm compress;
} crt_u; //該聯合體指出linux實作了cipher,digest和compress三類算法,每一類中有許多種算法,注意這個聯合的每一個都僅僅實作了一大類算法的實作封裝,并不是具體的實作。
struct crypto_alg *__crt_alg;
char __crt_ctx[] __attribute__ ((__aligned__));
};
下面這個結構體才是具體算法的實作,上面crypto_tfm中的crt_u中一系列函數都是對下面結構體中cra_u中一系列函數的封裝:
struct crypto_alg {
struct list_head cra_list;
u32 cra_flags; //這個flags很重要
unsigned int cra_blocksize;
unsigned int cra_ctxsize;
unsigned int cra_alignmask;
int cra_priority;
char cra_name[CRYPTO_MAX_ALG_NAME];
char cra_driver_name[CRYPTO_MAX_ALG_NAME];
struct cipher_alg cipher;
struct digest_alg digest;
struct compress_alg compress;
} cra_u;
int (*cra_init)(struct crypto_tfm *tfm);
void (*cra_exit)(struct crypto_tfm *tfm);
struct module *cra_module;
上面的兩個結構中都有一個聯合體,事實上每個聯合體字段都是一個完整的算法實作,而且三個算法類型幾乎沒有任何共同點可言,對于摘要類算法來說,回調函數是init,update,final等,而對于cipher而言,就是encrypt和decrypt等,linux實際上是利用聯合體的性質來将三類算法封裝到了一個結構體中。
crt_u裡面的回調函數和cra_u中的回調函數名稱幾乎一模一樣,但是它們的層次不同,crt中的函數實作了一大類算法的運作邏輯,比如cipher中的des中的塊應該怎麼分割等等,雖然對于摘要算法,sha1或者别的什麼的算法邏輯沒有什麼差別,但是對于cipher來講就不是這樣了,同一種算法可能擁有ecb,cbc,fcb等不同的模式,于是就來了個中間層,這個中間層就是上面的聯合體crt_u。
最後,linux将所有的三大類算法組織成了并列的連結清單,在注冊算法的時候要通過crypto_register_alg将一個crypto_alg結構體注冊進核心,就是說摘要和加密算法不做差別,隻有在crypto_alloc_tfm的時候才會根據算法的名稱和flags來确定這個算法到底是做什麼的,判斷的依據就是crypto_alg中的cra_name字段和cra_flags字段。
從上述結構體和代碼可以看出,linux對加crypto的算法的組織要遠比openssl的簡單,這也許是linux核心運作效率決定的吧。下面看一下重要的crypto_alloc_tfm函數,很多工作都在這個函數裡面完成,比如初始化函數的執行邏輯,也就是初始化crt_u的一堆函數指針,順便說一下,如果你把crypto_alg了解成一個靜态的結構的話,那麼crypto_tfm就是一個動态的容器,這種動靜結合的方式在優秀的開源代碼中很常見,都是用靜态的結構初始化動态的結構,然後再根據一些政策邏輯完成動态結構的初始化,有時候動态結構就是一個用于解除耦合的橋梁,比如一些過程的controler,有的時候它就是一個上下文環境或者說是一個容器,比如命名為xxx_ctx的結構體,對于linux核心的cryptoAPI,crypto_tfm可以說是一個橋梁,也可以說是一個容器,一個controler,具體是什麼不重要,重要的是代碼靈活了:
struct crypto_tfm *crypto_alloc_tfm(const char *name, u32 flags)
{
struct crypto_tfm *tfm = NULL;
struct crypto_alg *alg;
unsigned int tfm_size;
alg = crypto_alg_mod_lookup(name);//根據名稱來查找算法實作
tfm_size = sizeof(*tfm) + crypto_ctxsize(alg, flags);
tfm = kzalloc(tfm_size, GFP_KERNEL);
tfm->__crt_alg = alg;
crypto_init_flags(tfm, flags);
crypto_init_ops(tfm);
alg->cra_init(tfm);
...
return tfm;
}
crypto_init_ops函數完成了具體實作的封裝,也就是說它初始化了crypto_tfm結構體中crt_u中的一系列函數指針:
static int crypto_init_ops(struct crypto_tfm *tfm)
switch (crypto_tfm_alg_type(tfm)) {
case CRYPTO_ALG_TYPE_CIPHER:
return crypto_init_cipher_ops(tfm);//初始化cipher封裝函數,下面例子中以digest來說明,它在這裡的調用邏輯和cipher原理是一樣的,都是在crypto_init_ops實作具體實作的封裝過程。
下面是使用linux核心中cryptoAPI實作的digest的一個例子的大緻過程,流程和openssl的evp系列是差不多的,隻是在涉及算法實作的點上有差異:
char *Kern_Digest(const void *data, size_t count,
unsigned char *md, unsigned int *size, const char *name)
struct crypto_tfm *tfm;
struct scatterlist sg[1];
tfm = crypto_alloc_tfm(name, 0);
sg_init_one(sg, data, count); //此往下,摘要算法的實作已經确定了,接下來隻剩下回調函數的調用了
crypto_digest_init(tfm);
crypto_digest_update(tfm, sg, 1);
crypto_digest_final(tfm, md);
if (size != NULL)
*size = tfm->cra_digest.dia_digestsize;
crypto_free_tfm(tfm);
crypto_digest_xxx系列函數僅僅是具體實作的封裝,以init為例:
static inline void crypto_digest_init(struct crypto_tfm *tfm)
tfm->crt_digest.dit_init(tfm);//dit_init依然是一個封裝,它在crypto_init_ops中被初始化,這個dit_YYY系列的封裝才是有意義的封裝。
這種封裝解除了調用者和實作者之間的耦合。下面是openssl中的digest的實作過程:
int EVP_Digest(const void *data, size_t count,
unsigned char *md, unsigned int *size, const EVP_MD *type, ENGINE *impl)
{ //傳入的type參數其實隻是一個空殼,僅僅包含了nid字段是有效的,對于EVP_MD來說就是type字段,因為别的字段具體是什麼還要看engine的實作,是以此處的參數type可以僅僅了解成一個算法id。
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
M_EVP_MD_CTX_set_flags(&ctx,EVP_MD_CTX_FLAG_ONESHOT);
ret=EVP_DigestInit_ex(&ctx, type, impl)
&& EVP_DigestUpdate(&ctx, data, count)
&& EVP_DigestFinal_ex(&ctx, md, size);
EVP_MD_CTX_cleanup(&ctx);
在對外呈現的接口上,openssl和linux是一緻的,openssl需要一個算法的id,而linux需要一個算法的名稱,所不同的是,openssl有engine的支援,這樣同一個算法就可以指定不同的實作,engine作為算法名稱或者id和算法實作之間的橋梁存在,解除了它們之間的耦合。而linux由于沒有engine,那麼隻要給定了一個算法名稱,比如sha1,那麼隻能得到唯一的實作,雖然核心可能通過增加優先級字段來影響算法實作的被選中率,但是這種做法遠沒有openssl的engine靈活,但是反過來想一下,核心中的機制都是比較穩定的,不經常變動的,而且linux本身就不提倡你在核心完成一些功能,除非萬不得已,是以linux的crypto的設計也算是圓滿了。
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271919