内核调用crypto算法代码解析

1,042 阅读2分钟

前言

上周讲了使用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

crypto_example.png


行动,才不会被动!

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

博客地址: fishmwei.github.io

掘金主页: juejin.cn/user/208432…