用户态程序可以通过 AF_ALG 调用 Linux 内核加密
AF_ALG 是 Linux 暴露给用户态的内核加密接口。应用程序可以通过 socket 调用内核 crypto API,使用哈希、对称加密、AEAD 等算法。
它不是 OpenSSL 这类用户态密码库的替代品。它更适合需要复用内核算法实现、硬件加速驱动或统一内核 crypto 框架的场景。
基本模型
AF_ALG 通常分为两层 socket:
父 socket:选择算法,设置 key 等公共参数
子 socket:执行一次具体的加密、解密或哈希操作常见流程:
int alg_socket = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(alg_socket, ...);
setsockopt(alg_socket, SOL_ALG, ALG_SET_KEY, key, key_len);
int op_socket = accept(alg_socket, NULL, 0);
send(op_socket, input, input_len, 0);
read(op_socket, output, output_len);父 socket 定义算法上下文,accept 创建具体操作实例。多个操作可以从同一个父 socket 创建多个子 socket。
选择算法
算法通过 struct sockaddr_alg 指定:
#include <linux/if_alg.h>
struct sockaddr_alg alg_addr = {
.salg_family = AF_ALG,
.salg_type = "skcipher",
.salg_name = "cbc(aes)"
};字段含义:
salg_family:固定为AF_ALG。salg_type:算法类型,例如hash、skcipher、aead、rng。salg_name:内核已经注册的算法名称,例如sha256、cbc(aes)。
算法名不能随意自定义。可以通过 /proc/crypto 查看当前内核支持的算法:
cat /proc/crypto如果算法名不存在,bind 会失败。
AES-CBC 加密示例
下面示例演示 skcipher 类型的 AES-CBC 用法。代码只展示核心路径,生产代码需要补充完整错误处理和 padding 处理。
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/if_alg.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int encrypt_aes_cbc(const unsigned char *key, size_t key_len,
const unsigned char *iv_data,
const unsigned char *input, size_t input_len,
unsigned char *output, size_t output_len) {
int alg_socket = -1;
int op_socket = -1;
struct sockaddr_alg alg_addr = {
.salg_family = AF_ALG,
.salg_type = "skcipher",
.salg_name = "cbc(aes)"
};
alg_socket = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (alg_socket < 0) {
return -1;
}
if (bind(alg_socket, (struct sockaddr *)&alg_addr, sizeof(alg_addr)) < 0) {
close(alg_socket);
return -1;
}
if (setsockopt(alg_socket, SOL_ALG, ALG_SET_KEY, key, key_len) < 0) {
close(alg_socket);
return -1;
}
op_socket = accept(alg_socket, NULL, 0);
if (op_socket < 0) {
close(alg_socket);
return -1;
}
unsigned char control_buf[CMSG_SPACE(4) + CMSG_SPACE(sizeof(struct af_alg_iv) + 16)];
memset(control_buf, 0, sizeof(control_buf));
struct msghdr msg = {0};
msg.msg_control = control_buf;
msg.msg_controllen = sizeof(control_buf);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = ALG_SET_OP;
cmsg->cmsg_len = CMSG_LEN(4);
*((unsigned int *)CMSG_DATA(cmsg)) = ALG_OP_ENCRYPT;
cmsg = CMSG_NXTHDR(&msg, cmsg);
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = ALG_SET_IV;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct af_alg_iv) + 16);
struct af_alg_iv *iv = (struct af_alg_iv *)CMSG_DATA(cmsg);
iv->ivlen = 16;
memcpy(iv->iv, iv_data, 16);
struct iovec iov = {
.iov_base = (void *)input,
.iov_len = input_len
};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (sendmsg(op_socket, &msg, 0) < 0) {
close(op_socket);
close(alg_socket);
return -1;
}
ssize_t nread = read(op_socket, output, output_len);
close(op_socket);
close(alg_socket);
return nread < 0 ? -1 : 0;
}CBC 模式要求输入长度是块大小的整数倍。AES 块大小是 16 字节。实际业务中需要明确 padding 方案,例如 PKCS#7,并在加密前补齐数据。
setsockopt 与控制消息
AF_ALG 有两类常见参数传递方式。
第一类是父 socket 上的 setsockopt,例如设置 key:
setsockopt(alg_socket, SOL_ALG, ALG_SET_KEY, key, key_len);第二类是发送数据时通过 sendmsg 附带控制消息,例如设置操作方向和 IV:
ALG_SET_OP
ALG_SET_IV这种设计使父 socket 可以保存公共配置,而每次操作可以使用不同 IV 或不同操作方向。
适用场景
AF_ALG 适合以下场景:
- 需要使用内核 crypto framework 中的算法实现。
- 需要复用内核支持的硬件加速驱动。
- 数据已经在文件描述符或内核缓冲路径中,适合和
sendmsg、splice等接口组合。 - 系统组件希望减少用户态密码库依赖。
普通应用层协议开发通常优先使用成熟用户态库,例如 OpenSSL、BoringSSL、libsodium。它们的文档、错误处理、协议封装和跨平台能力更完整。
注意事项
AF_ALG 是 Linux 专有接口,不具备跨平台能力。不同内核版本、配置和模块加载状态会影响可用算法列表。
密码算法本身只是一部分。实际系统还需要正确处理随机数、IV 唯一性、认证标签、padding、密钥生命周期和错误路径。错误的参数组合会让加密结果不可用,严重时会破坏安全属性。
总结
AF_ALG 通过 socket 把用户态程序连接到 Linux 内核 crypto API。正常用法是先用父 socket 选择算法和 key,再用 accept 得到操作 socket,通过 sendmsg、read 或 recv 完成一次具体运算。它适合 Linux 系统编程和内核加密能力复用,不适合追求跨平台的通用业务代码。
本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。