前言
上周讲了使用tcrypt对内核加密框架linux kernel crypto中算法的测试。今天具体看一下几个示例。
算法类型分为4种: hash、对称、非对称、随机数。
hash算法调用
对hash算法的调用主要有几个步骤:
struct scatterlist sg[TVMEMSIZE];
struct crypto_wait wait;
struct ahash_request *req;
struct crypto_ahash *tfm;
char *output;
int i, ret;
tfm = crypto_alloc_ahash(algo, 0, mask); // 根据算法名找到tfm
...
test_hash_sg_init(sg); // 创建输入数据
req = ahash_request_alloc(tfm, GFP_KERNEL); //申请一个request
crypto_init_wait(&wait); // 初始化一个完成量
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
crypto_req_done, &wait); // 设置完成回调
...
output = kmalloc(MAX_DIGEST_SIZE, GFP_KERNEL); //申请输出空间
...
if (klen) // 如果算法有key,设置key
crypto_ahash_setkey(tfm, tvmem[0], klen);
ret = crypto_wait_req(crypto_ahash_init(req), &wait); // hash初始化调用
// 把输入和输出的指针设置进去, 调用update 根据数据的多少 可能调用多次
ahash_request_set_crypt(req, sg, output, speed[i].plen);
ret = crypto_wait_req(crypto_ahash_update(req), &wait);
// 最后调用final
ahash_request_set_crypt(req, &sg, out, 0);
ret = crypto_wait_req(crypto_ahash_final(req), &wait);
这个是典型的init, update, final的流程。 有时候,数据没有那么长, 可以直接调用digest接口 一次运算就得到结果。 还有一个就是 init, update , finup的流程, 最后一次的upate和final结合起来, 中间可能就没有update的调用。
主要根据用户对数据的长度判断来决定。总之最后的结果肯定都是一样的。
对称算法调用
大的总体流程都是差不多的,首先根据算法名创建tfm,然后设置key, iv,最后再调用加解密的接口。
tfm = crypto_alloc_skcipher(algo, 0, async ? 0 : CRYPTO_ALG_ASYNC); // 申请tfm
...
req = skcipher_request_alloc(tfm, GFP_KERNEL); // 申请request
skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
crypto_req_done, &wait);
ret = crypto_skcipher_setkey(tfm, key, *keysize); // 设置key
if (ret) {
pr_err("setkey() failed flags=%x\n",
crypto_skcipher_get_flags(tfm));
goto out_free_req;
}
//初始化数据
sg_init_table(sg, DIV_ROUND_UP(k, PAGE_SIZE));
...
sg_set_buf(sg, tvmem[0] + *keysize, bs);
// 设置iv
skcipher_request_set_crypt(req, sg, sg, bs, iv); // 加密
ret = crypto_wait_req(crypto_skcipher_encrypt(req), wait);
//释放空间
skcipher_request_free(req);
crypto_free_skcipher(tfm);
非对称算法调用
tfm = crypto_alloc_akcipher(driver, type, mask);
req = akcipher_request_alloc(tfm, GFP_KERNEL);
crypto_init_wait(&wait);
if (vecs->public_key_vec)
err = crypto_akcipher_set_pub_key(tfm, key, vecs->key_len);
else
err = crypto_akcipher_set_priv_key(tfm, key, vecs->key_len);
...
out_len_max = crypto_akcipher_maxsize(tfm);
outbuf_enc = kzalloc(out_len_max, GFP_KERNEL);
if (!outbuf_enc)
goto free_key;
if (!vecs->siggen_sigver_test) {
m = vecs->m;
m_size = vecs->m_size;
c = vecs->c;
c_size = vecs->c_size;
op = "encrypt";
} else {
/* Swap args so we could keep plaintext (digest)
* in vecs->m, and cooked signature in vecs->c.
*/
m = vecs->c; /* signature */
m_size = vecs->c_size;
c = vecs->m; /* digest */
c_size = vecs->m_size;
op = "verify";
}
...
sg_init_table(src_tab, 3);
sg_set_buf(&src_tab[0], xbuf[0], 8);
sg_set_buf(&src_tab[1], xbuf[0] + 8, m_size - 8);
if (vecs->siggen_sigver_test) {
if (WARN_ON(c_size > PAGE_SIZE))
goto free_all;
memcpy(xbuf[1], c, c_size);
sg_set_buf(&src_tab[2], xbuf[1], c_size);
akcipher_request_set_crypt(req, src_tab, NULL, m_size, c_size);
} else {
sg_init_one(&dst, outbuf_enc, out_len_max);
akcipher_request_set_crypt(req, src_tab, &dst, m_size,
out_len_max);
}
akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
crypto_req_done, &wait);
err = crypto_wait_req(vecs->siggen_sigver_test ?
/* Run asymmetric signature verification */
crypto_akcipher_verify(req) :
/* Run asymmetric encrypt */
crypto_akcipher_encrypt(req), &wait);
...
// 释放空间
...
随机数算法调用
// 参考testmgr.c alg_test_cprng函数
rng = crypto_alloc_rng(driver, type, mask);
...
seedsize = crypto_rng_seedsize(tfm);
...
memset(result, 0, 32);
memcpy(seed, template[i].v, template[i].vlen);
memcpy(seed + template[i].vlen, template[i].key,
template[i].klen);
memcpy(seed + template[i].vlen + template[i].klen,
template[i].dt, template[i].dtlen);
err = crypto_rng_reset(tfm, seed, seedsize);
if (err) {
printk(KERN_ERR "alg: cprng: Failed to reset rng "
"for %s\n", algo);
goto out;
}
for (j = 0; j < template[i].loops; j++) {
err = crypto_rng_get_bytes(tfm, result,
template[i].rlen);
if (err < 0) {
printk(KERN_ERR "alg: cprng: Failed to obtain "
"the correct amount of random data for "
"%s (requested %d)\n", algo,
template[i].rlen);
goto out;
}
}
// result中就是结果了
scatterlist
在前面的代码中看到,在设置数据的时候,很多地方使用了sg_init_table、 sg_set_buf函数,使用的就是scatterlist这个结构体。
struct scatterlist {
unsigned long page_link; // 页索引, 4字节对齐的
unsigned int offset; // 页内偏移
unsigned int length; //长度
dma_addr_t dma_address; // dma地址
#ifdef CONFIG_NEED_SG_DMA_LENGTH
unsigned int dma_length;
#endif
};
对于一个给定的数据块,在内存中可能是在一些离散的区域。scatterlist就是把这些区域信息汇聚在一起的结构,一般是以数组的形式出现。
sg_init_table用来初始化这一个数组,参数是数组指针和元素的个数。内部实现就是清空内存,然后在最后一个元素中page_link成员中设置结束标识。
sg_set_buf就是把缓冲区的地址和长度设置到每个scatterlist元素中。
然后,算法调用的时候再通过xxxkcipher_request_set_crypt函数把scatterlist形式的数据设置到request中的src/dst。
另一边,在算法实现接口中,取出src/dst。获取到对应的虚拟地址,也就得到了数据,然后进行算法计算。
参考代码: https://gitee.com/fishmwei/blog_code/blob/master/linux-kernel/crypto_example/crypto.c
执行效果:
cd crypto_example
make
journalctl -f &
insmod crypto.ko alg=aes len=16
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。
博客地址: fishmwei.github.io
掘金主页: juejin.cn/user/208432…