好的,我们继续学习 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); // 32KB
if (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); // 1MB
if (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)