linux内核加载openssl,linux内核cryto接口的实现以及与openssl的比较

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];

union {

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的设计也算是圆满了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值