Linux 内核加密 API 实战指南:从 AES-256-XTS 对称加密到 SHASH 哈希与 RNG 随机数(api-samples 代码示例精讲)
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本文是 Linux 内核加密子系统(Crypto API)的实战入门指南,围绕 Documentation/crypto/api-samples.rst 中的三组完整内核态代码示例展开:如何使用xts(aes)完成对称密钥加解密、如何结合struct sdesc为 SHASH 同步哈希分配“操作状态内存(operational state memory)”,以及如何通过标准 RNG 句柄获取随机字节。读者学完后将掌握内核模块中 skcipher / shash / rng 三条 API 的标准调用范式、异步请求同步等待的惯用法,以及对应的内核源码实现位置,可直接照搬到自己的内核驱动或安全模块中。
一、示例总览:内核 Crypto API 的三种典型用法
Linux 内核把密码学操作抽象为一组统一的变换(transformation)对象,用户态通过 AF_ALG 套接字使用,内核态驱动则直接调用 include/linux/crypto.h 与include/crypto/下的头文件暴露的 API。Documentation/crypto/api-samples.rst 提供了三组可以直接内嵌进内核代码的示例:
| 示例 | 核心 API | 典型场景 |
|---|---|---|
| 对称密钥加解密(AES-256-XTS) | crypto_alloc_skcipher/crypto_skcipher_encrypt | 磁盘加密、存储加密等块设备场景 |
| SHASH 同步哈希(带操作状态内存) | crypto_alloc_shash/crypto_shash_digest | 校验和、HMAC、一次性 digest 计算 |
| RNG 随机数生成 | crypto_alloc_rng/crypto_rng_get_bytes | 密钥派生、IV/盐值生成、随机填充 |
三个示例的共性骨架都是:分配变换对象(tfm)→ 设置密钥(如需要)→ 分配请求对象(如需要)→ 执行操作 → 检查错误 → 释放资源。下面逐一深入。
二、对称密钥加密示例:AES-256-XTS 原地加解密
原文档给出的test_skcipher()函数用xts(aes)算法对 512 字节随机数据做原地(in-place)加密,并注释强调:示例输入全部为随机字节、加密为原地操作、代码运行在可睡眠(can sleep)的上下文(如进程上下文而非硬中断)。
2.1 分配变换对象并设置密钥
tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); if (IS_ERR(tfm)) { pr_err("Error allocating xts(aes) handle: %ld\n", PTR_ERR(tfm)); return PTR_ERR(tfm); } get_random_bytes(key, sizeof(key)); err = crypto_skcipher_setkey(tfm, key, sizeof(key)); if (err) { pr_err("Error setting key: %d\n", err); goto out; }要点解读:
crypto_alloc_skcipher()在 include/crypto/skcipher.h 中声明,第一个参数是算法名,可以是cra_name(如xts(aes))或cra_driver_name(驱动名);type与mask在示例中传 0,表示不附加任何类型约束。返回的句柄必须用IS_ERR()/PTR_ERR()检查。crypto_skcipher_setkey()在 crypto/skcipher.c 中实现,负责把密钥编程进硬件或存入变换上下文,并校验密钥长度合法性。示例使用 64 字节密钥,注释明确说明 “AES-256-XTS takes a 64-byte key”。这背后有源码依据:在 crypto/xts.c 中,XTS 实例化时执行inst->alg.min_keysize = alg->min_keysize * 2、inst->alg.max_keysize = alg->max_keysize * 2——XTS 模式需要两个 AES 密钥(data key + tweak key),因此 AES-256-XTS 的密钥长度正好是 256 位 × 2 = 512 位 = 64 字节。iv[16]注释指出 “AES-256-XTS takes a 16-byte IV”,对应struct skcipher_alg_common中的ivsize字段(见 include/crypto/skcipher.h),XTS 的 IV(即 tweak 起始值)固定为 16 字节(XTS_BLOCK_SIZE)。
2.2 分配请求对象并准备数据
req = skcipher_request_alloc(tfm, GFP_KERNEL); if (!req) { err = -ENOMEM; goto out; } data = kmalloc(datasize, GFP_KERNEL); if (!data) { err = -ENOMEM; goto out; } get_random_bytes(data, datasize); get_random_bytes(iv, sizeof(iv));skcipher_request_alloc()为一次加解密操作分配请求对象,内部会额外分配struct crypto_skcipher的reqsize字节私有上下文(include/crypto/skcipher.h)。- 分配失败返回 NULL(注意这里与 tfm 不同,不用
IS_ERR判断),错误码取-ENOMEM。 get_random_bytes()是内核通用随机数接口(include/linux/random.h),此处仅用于构造示例数据,实际产品中密钥与 IV 应由安全随机源生成。
2.3 设置回调、组装 SG 列表并执行加密
sg_init_one(&sg, data, datasize); skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, crypto_req_done, &wait); skcipher_request_set_crypt(req, &sg, &sg, datasize, iv); err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);- 内核 skcipher 以scatter-gather(SG)列表为数据载体。
sg_init_one()把单块data缓冲封装成一个 SG 条目;由于是原地加密,src 与 dst 都传同一个&sg。skcipher_request_set_crypt()(include/crypto/skcipher.h)一次设置源、目的、密文长度(cryptlen)与 IV。 - 示例通过
DECLARE_CRYPTO_WAIT(wait)在栈上声明一个完成量等待对象(宏定义见 include/linux/crypto.h),并用crypto_req_done作为完成回调。这两个符号配合crypto_wait_req()(include/linux/crypto.h)构成“异步请求、同步等待”的标准惯用法:若底层驱动返回-EINPROGRESS或-EBUSY,crypto_wait_req会wait_for_completion()阻塞直到回调触发,再返回最终错误码。这也就是文档强调“代码必须运行在可睡眠上下文”的原因。 - 标志位
CRYPTO_TFM_REQ_MAY_BACKLOG表示允许请求进入驱动队列排队,CRYPTO_TFM_REQ_MAY_SLEEP表示允许在操作中睡眠。 crypto_skcipher_encrypt()在 crypto/skcipher.c 实现,它会解析请求拿到对应算法后派发执行。要改为解密,只需把crypto_skcipher_encrypt()换成crypto_skcipher_decrypt()(crypto/skcipher.c),其余全部保持不变——这正是对称密码的便利之处。
2.4 收尾释放
out: crypto_free_skcipher(tfm); skcipher_request_free(req); kfree(data); return err;crypto_free_skcipher()(include/crypto/skcipher.h)会清零并销毁变换对象,对 NULL / 错误指针是安全的;skcipher_request_free()内部使用kfree_sensitive()释放请求(include/crypto/skcipher.h),确保敏感上下文被抹除。注意请求释放与数据缓冲kfree(data)都必须执行,示例中用goto out统一收口,避免任一失败路径泄漏资源。
2.5 可复用性说明
文档特别提醒:真实场景中一个 tfm 与密钥通常会被复用执行很多次加解密(例如磁盘加密每块 I/O 一次),示例只做单次加密“效率并不高”。在实际驱动中,应把crypto_alloc_skcipher+crypto_skcipher_setkey放在初始化阶段、crypto_free_skcipher放在退出阶段,数据路径只保留skcipher_request_*相关操作。
三、SHASH 哈希示例:为同步哈希分配操作状态内存
SHASH(Synchronous HASH)是内核为同步(可能睡眠)上下文提供的一类哈希 API。与struct shash_desc配套,每个哈希操作都需要一块“操作状态内存”(descsize),保存算法运行过程中的内部状态(如 SHA 的中间摘要)。示例展示了一种优雅的封装:把struct shash_desc与可变长上下文char ctx[]打包进自定义结构struct sdesc,一次分配、按需取用。
3.1 结构定义与初始化
struct sdesc { struct shash_desc shash; char ctx[]; }; static struct sdesc *init_sdesc(struct crypto_shash *alg) { struct sdesc *sdesc; int size; size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); sdesc = kmalloc(size, GFP_KERNEL); if (!sdesc) return ERR_PTR(-ENOMEM); sdesc->shash.tfm = alg; return sdesc; }源码佐证:
struct shash_desc定义于 include/crypto/hash.h:仅含tfm指针与对齐的可变长__ctx[],因此必须由调用方在struct shash_desc之后再分配一段算法私有的上下文空间。crypto_shash_descsize()(include/crypto/hash.h)返回该哈希算法需要的操作状态字节数(即struct shash_alg中descsize字段),不同算法差异很大——例如注释里提到最坏情况是hmac(sha3-224-s390),其嵌套上下文使HASH_MAX_DESCSIZE相当可观(include/crypto/hash.h)。所以千万不要假设 descsize 是固定值,必须动态查询。init_sdesc()用kmalloc一次性分配sizeof(struct shash_desc) + descsize,并把sdesc->shash.tfm指向算法句柄,随后即可把&sdesc->shash直接传给哈希 API。
3.2 计算摘要
static int calc_hash(struct crypto_shash *alg, const unsigned char *data, unsigned int datalen, unsigned char *digest) { struct sdesc *sdesc; int ret; sdesc = init_sdesc(alg); if (IS_ERR(sdesc)) { pr_info("can't alloc sdesc\n"); return PTR_ERR(sdesc); } ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); kfree(sdesc); return ret; }crypto_shash_digest()是“一次性”接口:内部等价于 init → update(全部数据) → final,适合只需整块摘要的场景;若需分多次喂数据,则应改用crypto_shash_init()/crypto_shash_update()/crypto_shash_final()组合(对应struct shash_alg中的init/update/final/digest回调,见 include/crypto/hash.h)。- 用完后直接
kfree(sdesc)即可,无需单独释放内部ctx,因为它是柔性数组成员、与sdesc同一块内存。
3.3 分配算法句柄
static int test_hash(const unsigned char *data, unsigned int datalen, unsigned char *digest) { struct crypto_shash *alg; char *hash_alg_name = "sha1-padlock-nano"; int ret; alg = crypto_alloc_shash(hash_alg_name, 0, 0); if (IS_ERR(alg)) { pr_info("can't alloc alg %s\n", hash_alg_name); return PTR_ERR(alg); } ret = calc_hash(alg, data, datalen, digest); crypto_free_shash(alg); return ret; }crypto_alloc_shash()/crypto_free_shash()声明于 include/crypto/hash.h,前者失败时返回错误指针,后者对 NULL / 错误指针安全。- 示例中的算法名
"sha1-padlock-nano"是一个具体的驱动名(cra_driver_name),仅在特定平台(VIA PadLock 硬件加速器)可用。文档使用它是为了演示“可以用驱动名分配”,你在其他平台应替换为通用名,如"sha1"、"sha256"、"hmac(sha256)",或直接查/proc/crypto看系统里实际注册了哪些算法。
四、RNG 示例:获取内核随机数
最后一个示例展示如何通过 Crypto API 分配一个标准随机数生成器并取随机字节:
static int get_random_numbers(u8 *buf, unsigned int len) { struct crypto_rng *rng = NULL; char *drbg = "stdrng"; int ret; if (!buf || !len) { pr_debug("No output buffer provided\n"); return -EINVAL; } rng = crypto_alloc_rng(drbg, 0, 0); if (IS_ERR(rng)) { pr_debug("could not allocate RNG handle for %s\n", drbg); return PTR_ERR(rng); } ret = crypto_rng_get_bytes(rng, buf, len); if (ret < 0) pr_debug("generation of random numbers failed\n"); else if (ret == 0) pr_debug("RNG returned no data"); else pr_debug("RNG returned %d bytes of data\n", ret); out: crypto_free_rng(rng); return ret; }要点解读:
- 入口先做参数校验:
buf为空或len为 0 时直接返回-EINVAL,这是内核 API 的标准防御式写法。 - 算法名
"stdrng"是内核预注册的标准随机数生成器别名:在 crypto/drbg.c 中,DRBG(NIST SP800-90A 确定性随机比特生成器)实例以cra_name = "stdrng"注册,并带有MODULE_ALIAS_CRYPTO("stdrng");FIPS 模式下还会提升优先级,确保请求"stdrng"一定命中 DRBG 实现。 crypto_rng_get_bytes()(include/crypto/rng.h)实际是crypto_rng_generate(tfm, NULL, 0, rdata, dlen)的便捷封装——即不附加额外输入数据直接生成dlen字节随机数;如果需要“加盐”的确定性生成,可改用crypto_rng_generate()传入附加输入。- 返回值为 0 表示成功;为负表示错误(如该 RNG 需要先播种);示例中还处理了“返回正字节数”的边界并打印调试信息。
- 最后通过
crypto_free_rng()(include/crypto/rng.h)释放句柄。注意示例中out:标签直接接释放语句,若前面任何一步失败提前return则不会走到out——严格说这是示例的简化写法,实际驱动建议统一用goto out收口避免句柄泄漏。
五、把示例接入真实内核代码的注意事项
结合源码与文档注释,将上述示例移植进真实驱动/模块时请重点核对以下几点:
- 上下文限制:三组示例都使用
GFP_KERNEL分配、依赖crypto_wait_req的wait_for_completion阻塞等待,因此只能运行在可睡眠的进程上下文。在原子上下文(中断、软中断、持自旋锁)中必须改用原子分配标志与完全异步的回调风格。 - 算法名可移植性:
xts(aes)是通用组合名,只要内核配置了CONFIG_CRYPTO_XTS与CONFIG_CRYPTO_AES即可使用;sha1-padlock-nano是平台驱动名,请按需替换;stdrng依赖CONFIG_CRYPTO_DRBG。可用/proc/crypto查看当前系统实际注册的算法、类型、密钥/IV 尺寸与优先级。 - 密钥与 IV 尺寸:XTS 是“双倍密钥”模式,AES-256-XTS 必须给 64 字节 key、16 字节 IV,尺寸不符会直接导致
crypto_skcipher_setkey或加密调用失败;哈希则要用crypto_shash_descsize()动态获取状态内存大小。 - 错误处理路径:skcipher 示例用
goto out统一释放 tfm、request 与数据缓冲;RNG 示例的提前return路径需要自行补上crypto_free_rng,避免句柄泄漏。 - 性能取舍:文档明确指出 tfm/密钥应跨多次操作复用,不要在每笔 I/O 里反复
crypto_alloc_*;请求对象(skcipher_request)在热路径上同样建议复用。
如需进一步系统学习,可继续阅读 Documentation/crypto/index.rst 下的其他章节(如 intro、architecture、desciption、async-tx 等),并以 include/crypto/skcipher.h、include/crypto/hash.h、include/crypto/rng.h 及 crypto/ 目录下的实现(skcipher.c、drbg.c、xts.c 等)作为权威参考。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考