add_key系统调用及示例
我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 add_key
函数,它是 Linux 密钥保留服务(Key Retention Service)的一部分,用于向内核密钥管理系统添加新密钥。
1. 函数介绍
add_key
是一个 Linux 系统调用,属于密钥保留服务(Key Retention Service)的一部分。它用于向内核的密钥管理系统中添加一个新的密钥。
Linux 密钥保留服务提供了一个框架来管理各种类型的密钥和凭证,包括:
- 用户密钥(user keys)
- 登录密钥(login keys)
- 密钥环密钥(keyring keys)
- PKCS#7 消息密钥
- 大量密钥(big keys)
- 可信密钥(trusted keys)
- 加密密钥(encrypted keys)
这个服务的主要用途包括:
- 文件系统加密:管理加密文件系统的密钥
- 网络认证:存储 Kerberos 票据等认证信息
- 内核模块:为内核模块提供密钥管理
- 安全通信:管理 TLS/SSL 会话密钥
- 用户空间应用:为应用程序提供安全的密钥存储
add_key
函数允许用户空间程序将密钥安全地存储在内核中,这些密钥可以被其他有权限的进程访问,并且在系统重启后会丢失(除非使用持久化密钥环)。
2. 函数原型
#include <sys/types.h>
#include <keyutils.h>key_serial_t add_key(const char *type, const char *description,const void *payload, size_t plen,key_serial_t keyring);
3. 功能
- 创建新密钥: 在内核密钥管理系统中创建一个指定类型和描述的新密钥。
- 设置密钥数据: 将提供的有效载荷(payload)数据与新密钥关联。
- 链接到密钥环: 将新创建的密钥链接到指定的密钥环中。
- 返回密钥序列号: 成功时返回新密钥的唯一序列号。
4. 参数
const char *type
: 密钥的类型字符串,指定要创建的密钥类型。常见类型包括:"user"
: 用户定义的密钥"login"
: 登录相关的密钥"keyring"
: 密钥环"big_key"
: 大量密钥(存储在 tmpfs 或加密的环回文件系统中)"trusted"
: 可信平台模块(TPM)保护的密钥"encrypted"
: 加密密钥
const char *description
: 密钥的描述字符串,用于标识密钥。在同一密钥环中,描述必须唯一。const void *payload
: 指向密钥数据的指针。对于某些密钥类型,这可能是指向构造参数的指针。- 可以为
NULL
,表示创建一个空密钥。
- 可以为
size_t plen
: 有效载荷数据的长度(字节)。- 如果
payload
为NULL
,此值应为 0。
- 如果
key_serial_t keyring
: 目标密钥环的序列号,新密钥将被链接到这个密钥环中。- 可以使用特殊值:
KEY_SPEC_THREAD_KEYRING
: 当前线程的密钥环KEY_SPEC_PROCESS_KEYRING
: 当前进程的密钥环KEY_SPEC_SESSION_KEYRING
: 当前会话的密钥环KEY_SPEC_USER_KEYRING
: 当前用户的密钥环KEY_SPEC_USER_SESSION_KEYRING
: 当前用户的会话密钥环
- 可以使用特殊值:
5. 返回值
- 成功时: 返回新创建密钥的序列号(正整数)。
- 失败时: 返回 -1,并设置全局变量
errno
来指示具体的错误原因:EACCES
: 调用进程没有权限在指定密钥环中创建密钥。EDQUOT
: 密钥配额已满。EEXIST
: 具有相同描述的密钥已在目标密钥环中存在。EINVAL
: 参数无效(如类型字符串不支持)。ENOKEY
: 指定的密钥环不存在。ENOMEM
: 内存不足。EPERM
: 操作不被允许(如权限不足)。ERANGE
: 有效载荷大小超出限制。
6. 相似函数,或关联函数
keyctl(int cmd, ...)
: 通用的密钥控制函数,用于执行各种密钥管理操作。request_key(const char *type, const char *description, const char *callout_info, key_serial_t keyring)
: 请求一个已存在的密钥或通过调用用户空间程序创建密钥。keyctl_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t keyring)
:add_key
的别名。keyctl_read(key_serial_t key, char *buffer, size_t buflen)
: 读取密钥的有效载荷。keyctl_describe(key_serial_t key, char *buffer, size_t buflen)
: 获取密钥的描述信息。
7. 示例代码
示例 1:基本的用户密钥创建
#define _GNU_SOURCE
#include <sys/types.h>
#include <keyutils.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>int main() {key_serial_t key1, key2;const char *secret_data = "This is my secret key data";char read_buffer[256];long read_result;printf("=== Add_key 基本使用演示 ===\n");printf("当前进程 UID: %d, GID: %d\n\n", getuid(), getgid());// 1. 创建一个简单的用户密钥printf("1. 创建用户密钥\n");key1 = add_key("user", "my_first_key", secret_data, strlen(secret_data), KEY_SPEC_SESSION_KEYRING);if (key1 == -1) {perror("创建用户密钥失败");printf("注意: 需要适当的权限,某些系统可能需要 root 权限\n");exit(EXIT_FAILURE);}printf(" 成功创建密钥,序列号: %d\n", key1);// 2. 创建另一个用户密钥const char *another_secret = "Another secret value";key2 = add_key("user", "my_second_key", another_secret, strlen(another_secret),KEY_SPEC_SESSION_KEYRING);if (key2 == -1) {perror("创建第二个用户密钥失败");} else {printf(" 成功创建第二个密钥,序列号: %d\n", key2);}// 3. 尝试读取密钥数据(需要使用 keyctl)printf("\n3. 读取密钥数据\n");read_result = keyctl(KEYCTL_READ, key1, (unsigned long)read_buffer, sizeof(read_buffer), 0);if (read_result == -1) {perror("读取密钥数据失败");} else {read_buffer[read_result] = '\0';printf(" 从密钥 %d 读取到数据: %s\n", key1, read_buffer);}// 4. 获取密钥描述信息printf("\n4. 获取密钥描述信息\n");read_result = keyctl(KEYCTL_DESCRIBE, key1, (unsigned long)read_buffer, sizeof(read_buffer), 0);if (read_result == -1) {perror("获取密钥描述失败");} else {read_buffer[read_result] = '\0';printf(" 密钥 %d 的描述信息: %s", key1, read_buffer);}// 5. 创建空密钥printf("\n5. 创建空密钥\n");key_serial_t empty_key = add_key("user", "empty_key", NULL, 0, KEY_SPEC_SESSION_KEYRING);if (empty_key == -1) {perror("创建空密钥失败");} else {printf(" 成功创建空密钥,序列号: %d\n", empty_key);}// 6. 演示重复密钥创建(应该失败)printf("\n6. 演示重复密钥创建\n");key_serial_t duplicate_key = add_key("user", "my_first_key", "duplicate data", 15,KEY_SPEC_SESSION_KEYRING);if (duplicate_key == -1) {if (errno == EEXIST) {printf(" 创建重复密钥失败(预期): %s\n", strerror(errno));printf(" 说明: 同一密钥环中不能有相同描述的密钥\n");} else {perror(" 创建重复密钥失败(其他错误)");}}// 7. 查看当前会话密钥环中的密钥printf("\n7. 查看会话密钥环信息\n");key_serial_t session_keyring = keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1);if (session_keyring != -1) {printf(" 当前会话密钥环 ID: %d\n", session_keyring);read_result = keyctl(KEYCTL_DESCRIBE, session_keyring, (unsigned long)read_buffer, sizeof(read_buffer), 0);if (read_result != -1) {read_buffer[read_result] = '\0';printf(" 会话密钥环描述: %s", read_buffer);}}// 8. 清理密钥(通过撤销)printf("\n8. 清理密钥\n");if (keyctl(KEYCTL_REVOKE, key1, 0, 0, 0) == -1) {perror("撤销密钥失败");} else {printf(" 成功撤销密钥 %d\n", key1);}if (keyctl(KEYCTL_REVOKE, key2, 0, 0, 0) == -1) {perror("撤销第二个密钥失败");} else {printf(" 成功撤销密钥 %d\n", key2);}return 0;
}
示例 2:不同密钥类型的演示
#define _GNU_SOURCE
#include <sys/types.h>
#include <keyutils.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>void demonstrate_key_types() {printf("=== 不同密钥类型演示 ===\n");// 1. 用户密钥(最常见的类型)printf("1. 用户密钥 (user)\n");key_serial_t user_key = add_key("user", "demo_user_key", "user data", 9,KEY_SPEC_SESSION_KEYRING);if (user_key != -1) {printf(" 成功创建用户密钥: %d\n", user_key);keyctl(KEYCTL_REVOKE, user_key, 0, 0, 0);} else {printf(" 创建失败: %s\n", strerror(errno));}// 2. 登录密钥printf("\n2. 登录密钥 (login)\n");key_serial_t login_key = add_key("login", "demo_login_key", "login data", 10,KEY_SPEC_SESSION_KEYRING);if (login_key != -1) {printf(" 成功创建登录密钥: %d\n", login_key);keyctl(KEYCTL_REVOKE, login_key, 0, 0, 0);} else {printf(" 创建失败: %s\n", strerror(errno));printf(" 说明: 登录密钥可能需要特殊权限\n");}// 3. 密钥环printf("\n3. 密钥环 (keyring)\n");key_serial_t keyring = add_key("keyring", "demo_keyring", NULL, 0,KEY_SPEC_SESSION_KEYRING);if (keyring != -1) {printf(" 成功创建密钥环: %d\n", keyring);// 在新密钥环中创建密钥key_serial_t key_in_ring = add_key("user", "key_in_new_ring", "data", 4, keyring);if (key_in_ring != -1) {printf(" 在新密钥环中创建密钥: %d\n", key_in_ring);keyctl(KEYCTL_REVOKE, key_in_ring, 0, 0, 0);}keyctl(KEYCTL_REVOKE, keyring, 0, 0, 0);} else {printf(" 创建密钥环失败: %s\n", strerror(errno));}// 4. 大量密钥printf("\n4. 大量密钥 (big_key)\n");// 创建较大的数据char *big_data = malloc(32768); // 32KBif (big_data) {memset(big_data, 'A', 32768);key_serial_t big_key = add_key("big_key", "demo_big_key", big_data, 32768,KEY_SPEC_SESSION_KEYRING);if (big_key != -1) {printf(" 成功创建大量密钥: %d\n", big_key);keyctl(KEYCTL_REVOKE, big_key, 0, 0, 0);} else {printf(" 创建大量密钥失败: %s\n", strerror(errno));}free(big_data);}
}void demonstrate_keyring_targets() {printf("\n=== 不同密钥环目标演示 ===\n");const char *key_data = "test data";// 测试各种密钥环目标struct {key_serial_t keyring;const char *name;} keyrings[] = {{KEY_SPEC_THREAD_KEYRING, "线程密钥环"},{KEY_SPEC_PROCESS_KEYRING, "进程密钥环"},{KEY_SPEC_SESSION_KEYRING, "会话密钥环"},{KEY_SPEC_USER_KEYRING, "用户密钥环"},{KEY_SPEC_USER_SESSION_KEYRING, "用户会话密钥环"}};for (int i = 0; i < 5; i++) {char desc[50];snprintf(desc, sizeof(desc), "key_for_%s", keyrings[i].name);key_serial_t key = add_key("user", desc, key_data, strlen(key_data), keyrings[i].keyring);if (key != -1) {printf(" 在%s中创建密钥成功: %d\n", keyrings[i].name, key);keyctl(KEYCTL_REVOKE, key, 0, 0, 0);} else {printf(" 在%s中创建密钥失败: %s\n", keyrings[i].name, strerror(errno));}}
}int main() {printf("Add_key 函数不同密钥类型演示\n\n");demonstrate_key_types();demonstrate_keyring_targets();printf("\n=== 密钥类型总结 ===\n");printf("user: 用户定义的通用密钥\n");printf("login: 登录相关的认证密钥\n");printf("keyring: 密钥环,用于组织其他密钥\n");printf("big_key: 大量密钥,存储在特殊文件系统中\n");printf("trusted: TPM 保护的可信密钥\n");printf("encrypted: 加密密钥\n\n");printf("=== 密钥环目标总结 ===\n");printf("KEY_SPEC_THREAD_KEYRING: 当前线程\n");printf("KEY_SPEC_PROCESS_KEYRING: 当前进程\n");printf("KEY_SPEC_SESSION_KEYRING: 当前会话\n");printf("KEY_SPEC_USER_KEYRING: 当前用户\n");printf("KEY_SPEC_USER_SESSION_KEYRING: 当前用户会话\n");return 0;
}
示例 3:错误处理和安全考虑
#define _GNU_SOURCE
#include <sys/types.h>
#include <keyutils.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>void demonstrate_error_handling() {printf("=== Add_key 错误处理演示 ===\n");// 1. EEXIST - 密钥已存在printf("1. EEXIST 错误演示\n");key_serial_t key = add_key("user", "unique_key", "data", 4, KEY_SPEC_SESSION_KEYRING);if (key != -1) {key_serial_t duplicate = add_key("user", "unique_key", "more data", 9, KEY_SPEC_SESSION_KEYRING);if (duplicate == -1 && errno == EEXIST) {printf(" 创建重复密钥失败(预期): %s\n", strerror(errno));}keyctl(KEYCTL_REVOKE, key, 0, 0, 0);}// 2. EINVAL - 无效参数printf("\n2. EINVAL 错误演示\n");key_serial_t invalid_key = add_key("invalid_type", "test", "data", 4, KEY_SPEC_SESSION_KEYRING);if (invalid_key == -1 && errno == EINVAL) {printf(" 使用无效密钥类型失败(预期): %s\n", strerror(errno));}// 3. ENOKEY - 密钥环不存在printf("\n3. ENOKEY 错误演示\n");key_serial_t no_keyring = add_key("user", "test", "data", 4, 999999);if (no_keyring == -1 && errno == ENOKEY) {printf(" 使用不存在的密钥环失败(预期): %s\n", strerror(errno));}// 4. ERANGE - 数据太大printf("\n4. ERANGE 错误演示\n");char *huge_data = malloc(1024 * 1024); // 1MBif (huge_data) {memset(huge_data, 'A', 1024 * 1024);key_serial_t big_key = add_key("user", "huge_key", huge_data, 1024 * 1024,KEY_SPEC_SESSION_KEYRING);if (big_key == -1 && errno == ERANGE) {printf(" 创建过大的密钥失败(预期): %s\n", strerror(errno));} else if (big_key != -1) {printf(" 成功创建大密钥\n");keyctl(KEYCTL_REVOKE, big_key, 0, 0, 0);}free(huge_data);}
}void demonstrate_security_considerations() {printf("\n=== 安全考虑演示 ===\n");printf("密钥保留服务的安全特性:\n");printf("1. 访问控制: 密钥有 UID/GID 和权限位\n");printf("2. 隔离性: 不同用户的密钥相互隔离\n");printf("3. 生命周期: 进程退出时密钥可能被清理\n");printf("4. 加密存储: 某些密钥类型提供加密保护\n\n");// 演示密钥权限key_serial_t key = add_key("user", "secure_key", "secret", 6, KEY_SPEC_SESSION_KEYRING);if (key != -1) {printf("创建密钥用于权限演示: %d\n", key);// 获取密钥信息char info_buffer[256];long info_len = keyctl(KEYCTL_DESCRIBE, key, (unsigned long)info_buffer, sizeof(info_buffer), 0);if (info_len != -1) {info_buffer[info_len] = '\0';printf("密钥信息: %s", info_buffer);}keyctl(KEYCTL_REVOKE, key, 0, 0, 0);}
}void demonstrate_practical_usage() {printf("\n=== 实际使用场景 ===\n");printf("1. 文件系统加密密钥管理:\n");printf(" - 存储加密文件系统的主密钥\n");printf(" - 管理用户会话的解密密钥\n\n");printf("2. 网络认证凭据:\n");printf(" - 存储 Kerberos 票据\n");printf(" - 保存 TLS 会话密钥\n\n");printf("3. 应用程序密钥管理:\n");printf(" - 数据库连接密码\n");printf(" - API 访问令牌\n");printf(" - 加密操作的对称密钥\n\n");// 模拟应用程序使用场景printf("模拟应用程序密钥使用:\n");const char *app_keys[] = {"database_password","api_token","encryption_key"};for (int i = 0; i < 3; i++) {char key_data[50];snprintf(key_data, sizeof(key_data), "secret_data_for_%s", app_keys[i]);key_serial_t app_key = add_key("user", app_keys[i], key_data, strlen(key_data), KEY_SPEC_SESSION_KEYRING);if (app_key != -1) {printf(" 为 %s 创建密钥: %d\n", app_keys[i], app_key);// 在实际应用中,这里会使用密钥进行操作keyctl(KEYCTL_REVOKE, app_key, 0, 0, 0);}}
}int main() {printf("Add_key 函数安全和错误处理演示\n\n");demonstrate_error_handling();demonstrate_security_considerations();demonstrate_practical_usage();printf("\n=== 使用建议 ===\n");printf("1. 错误处理: 始终检查返回值和 errno\n");printf("2. 权限管理: 确保有足够的权限创建密钥\n");printf("3. 资源清理: 使用完密钥后及时撤销\n");printf("4. 类型选择: 根据用途选择合适的密钥类型\n");printf("5. 密钥环: 选择合适的密钥环目标\n");printf("6. 数据大小: 注意密钥数据大小限制\n\n");printf("=== 系统要求 ===\n");printf("1. 需要 Linux 内核 2.6.10+\n");printf("2. 需要安装 keyutils 库\n");printf("3. 某些功能可能需要 root 权限\n");printf("4. 需要内核配置支持密钥保留服务\n");return 0;
}
编译和运行说明
# 安装 keyutils 开发包(Ubuntu/Debian)
sudo apt-get install libkeyutils-dev# 安装 keyutils 开发包(CentOS/RHEL/Fedora)
sudo yum install keyutils-libs-devel
# 或
sudo dnf install keyutils-libs-devel# 编译示例
gcc -o add_key_basic add_key_basic.c -lkeyutils
gcc -o add_key_types add_key_types.c -lkeyutils
gcc -o add_key_errors add_key_errors.c -lkeyutils# 运行示例
./add_key_basic
./add_key_types
./add_key_errors
重要注意事项
- 内核支持: 需要 Linux 内核 2.6.10+ 和相应的配置支持
- 权限要求: 某些操作可能需要 root 权限或特定的能力
- 库依赖: 需要链接
libkeyutils
库 - 资源管理: 使用完密钥后应及时撤销,避免资源泄漏
- 安全性: 密钥在内核中存储,比用户空间存储更安全
- 生命周期: 密钥的生命周期与进程和密钥环相关
- 类型限制: 不同密钥类型有不同的用途和限制
- 大小限制: 密钥数据有大小限制(通常几 KB 到几 MB)