news 2026/9/16 14:24:01

Linux 内核加密 API 实战指南:从 AES-256-XTS 对称加密到 SHASH 哈希与 RNG 随机数(api-samples 代码示例精讲)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 内核加密 API 实战指南:从 AES-256-XTS 对称加密到 SHASH 哈希与 RNG 随机数(api-samples 代码示例精讲)

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(驱动名);typemask在示例中传 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 * 2inst->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_skcipherreqsize字节私有上下文(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 都传同一个&sgskcipher_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-EBUSYcrypto_wait_reqwait_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_algdescsize字段),不同算法差异很大——例如注释里提到最坏情况是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收口避免句柄泄漏。

五、把示例接入真实内核代码的注意事项

结合源码与文档注释,将上述示例移植进真实驱动/模块时请重点核对以下几点:

  1. 上下文限制:三组示例都使用GFP_KERNEL分配、依赖crypto_wait_reqwait_for_completion阻塞等待,因此只能运行在可睡眠的进程上下文。在原子上下文(中断、软中断、持自旋锁)中必须改用原子分配标志与完全异步的回调风格。
  2. 算法名可移植性xts(aes)是通用组合名,只要内核配置了CONFIG_CRYPTO_XTSCONFIG_CRYPTO_AES即可使用;sha1-padlock-nano是平台驱动名,请按需替换;stdrng依赖CONFIG_CRYPTO_DRBG。可用/proc/crypto查看当前系统实际注册的算法、类型、密钥/IV 尺寸与优先级。
  3. 密钥与 IV 尺寸:XTS 是“双倍密钥”模式,AES-256-XTS 必须给 64 字节 key、16 字节 IV,尺寸不符会直接导致crypto_skcipher_setkey或加密调用失败;哈希则要用crypto_shash_descsize()动态获取状态内存大小。
  4. 错误处理路径:skcipher 示例用goto out统一释放 tfm、request 与数据缓冲;RNG 示例的提前return路径需要自行补上crypto_free_rng,避免句柄泄漏。
  5. 性能取舍:文档明确指出 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 14:22:52

红黑树核心原理与工程实践详解

1. 红黑树学习中的典型困惑解析第一次接触红黑树时&#xff0c;我被那五条性质规则和复杂的旋转操作彻底绕晕了。记得当时盯着"黑色高度平衡"这个概念发了半小时呆&#xff0c;完全不明白为什么非要这样设计。后来在实现插入操作时&#xff0c;更是被各种case的分支判…

作者头像 李华
网站建设 2026/9/16 14:21:07

SpringBoot3+Vue3校园跳蚤市场毕业设计实战

简介&#xff1a;本资源是一套面向计算机专业本科生的2025届毕业设计实战项目——大学校园跳蚤市场平台&#xff0c;聚焦高校二手交易场景&#xff0c;完整覆盖需求分析、前后端开发、数据库设计与部署实践&#xff0c;适用于Java全栈入门到进阶学习者及课程设计/毕设选题参考。…

作者头像 李华
网站建设 2026/9/16 14:19:31

机械手PID控制与Simulink仿真:从PD控制到跟踪微分器

简介&#xff1a;面向机械手控制课程设计与毕业设计场景&#xff0c;一套基于Matlab的机械手PID控制源码包&#xff0c;提供了从动力学建模、控制器设计到Simulink仿真验证的完整参考路径。资源主要面向具备一定Matlab基础、需要完成机械手或机器人控制相关作业的高校学生&…

作者头像 李华