0%

周谈(28)-内核调用crypto算法代码解析

前言

上周讲了使用tcrypt对内核加密框架linux kernel crypto中算法的测试。今天具体看一下几个示例。

算法类型分为4种: hash、对称、非对称、随机数。

hash算法调用

对hash算法的调用主要有几个步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
   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,最后再调用加解密的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

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


非对称算法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
   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函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

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这个结构体。

1
2
3
4
5
6
7
8
9
10
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

执行效果:

1
2
3
4
5
6
7

cd crypto_example
make

journalctl -f &
insmod crypto.ko alg=aes len=16

avatar


行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。

博客地址: https://fishmwei.github.io

掘金主页: https://juejin.cn/user/2084329776486919