用户态程序可以通过 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)"
};

字段含义:

算法名不能随意自定义。可以通过 /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 适合以下场景:

普通应用层协议开发通常优先使用成熟用户态库,例如 OpenSSL、BoringSSL、libsodium。它们的文档、错误处理、协议封装和跨平台能力更完整。

注意事项

AF_ALG 是 Linux 专有接口,不具备跨平台能力。不同内核版本、配置和模块加载状态会影响可用算法列表。

密码算法本身只是一部分。实际系统还需要正确处理随机数、IV 唯一性、认证标签、padding、密钥生命周期和错误路径。错误的参数组合会让加密结果不可用,严重时会破坏安全属性。

总结

AF_ALG 通过 socket 把用户态程序连接到 Linux 内核 crypto API。正常用法是先用父 socket 选择算法和 key,再用 accept 得到操作 socket,通过 sendmsgreadrecv 完成一次具体运算。它适合 Linux 系统编程和内核加密能力复用,不适合追求跨平台的通用业务代码。

本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。