Skip to content

第一百七十二章:密钥管理

渡劫期

涉及内核源码:

crypto API 指向的那道门,林小源推开了。门后不是密室,而是一座戒备森严的宝库。宝库的中央悬浮着一个巨大的环形结构——keyring,无数钥匙悬挂在环上,每一把都散发着不同颜色的光芒。

一个面容严肃的老者守在宝库入口。他的双手覆盖着一层金属光泽——那是 TPM 芯片的印记。

"我是 keyring 子系统,"老者的声音低沉而谨慎,"密钥的守护者。你刚才在 crypto API 那里看到了加密算法,但算法只是锁。钥匙在我这里。"

林小源走近那个环形结构。钥匙按照不同的类型分区悬挂:user 类型的钥匙由用户自己提供,trusted 类型的钥匙被 TPM 芯片包裹着一层无法穿透的外壳,encrypted 类型的钥匙本身就是加密的。

"trusted 密钥……"林小源伸手想触碰,老者立刻挡住了他。

"别碰。那把钥匙永远不会以明文形式离开 TPM。即使内核被攻破,攻击者也拿不到它的原始数据。TPM 是硬件级别的信任根——密钥在 TPM 内部生成,在 TPM 内部使用,从不暴露在外。"

"更准确地说,"老者又补了一句,"trusted key 让用户态只能看见、保存、重新加载加密 blob。普通 trusted key 的明文 key-data 在内核使用时仍可能出现在系统内存;protected key 才会把明文限制在 trust source 边界内。信任源不只有 TPM,也可能是 TEE、CAAM、DCP、PKWM。"

破关试炼

宝库初试

trusted/encrypted keys 中,用户态通常保存和加载的是明文密钥,还是加密 blob?

答对后才能继续滑动和进入下一章。
c
/*
 * 内核 keyring 子系统:
 *
 * 密钥类型:
 *   user — 用户密钥
 *   logon — 登录密钥
 *   big_key — 大密钥
 *   asymmetric — 非对称密钥
 *   encrypted — 加密密钥
 *   trusted — 可信密钥
 *
 * keyring 类型:
 *   进程 keyring — 进程私有
 *   会话 keyring — 会话共享
 *   用户 keyring — 用户共享
 *
 * 密钥操作:
 *   add_key() — 添加密钥
 *   request_key() — 请求密钥
 *   keyctl() — 密钥控制
 *
 * 使用场景:
 *   dm-crypt — 磁盘加密密钥
 *   IPsec — 网络加密密钥
 *   Kerberos — 认证密钥
 *   TLS — 会话密钥
 */

/* 模拟密钥 */
struct key {
    char type[32];
    char description[64];
    unsigned char data[256];
    int data_len;
    int permissions; /* rwx */
};

struct keyring {
    char name[32];
    struct key keys[10];
    int key_count;
};

void add_key(struct keyring *kr, const char *type,
             const char *desc, const unsigned char *data, int len) {
    if (kr->key_count >= 10) return;
    struct key *k = &kr->keys[kr->key_count];
    strncpy(k->type, type, 31);
    strncpy(k->description, desc, 63);
    memcpy(k->data, data, len < 256 ? len : 256);
    k->data_len = len < 256 ? len : 256;
    k->permissions = 0600; /* rw------- */
    kr->key_count++;
}

struct key *request_key(struct keyring *kr, const char *desc) {
    for (int i = 0; i < kr->key_count; i++) {
        if (strcmp(kr->keys[i].description, desc) == 0)
            return &kr->keys[i];
    }
    return NULL;
}

void print_key(struct key *k) {
    printf("  类型: %s\n", k->type);
    printf("  描述: %s\n", k->description);
    printf("  长度: %d 字节\n", k->data_len);
    printf("  权限: %o\n", k->permissions);
}

printf("=== 密钥管理 — 保护密钥 ===\n\n");

/* 创建进程 keyring */
struct keyring proc_keyring = {
    .name = "进程 keyring",
    .key_count = 0
};

/* 添加密钥 */
unsigned char aes_key[] = {
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
    0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10
};
add_key(&proc_keyring, "user", "disk-encryption-key",
        aes_key, sizeof(aes_key));

unsigned char ipsec_key[] = {
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
    0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20
};
add_key(&proc_keyring, "user", "ipsec-sa-key",
        ipsec_key, sizeof(ipsec_key));

printf("--- 进程 keyring ---\n");
printf("名称: %s\n", proc_keyring.name);
printf("密钥数量: %d\n\n", proc_keyring.key_count);

printf("--- 添加的密钥 ---\n");
for (int i = 0; i < proc_keyring.key_count; i++) {
    print_key(&proc_keyring.keys[i]);
    printf("\n");
}

printf("--- 请求密钥 ---\n");
struct key *k = request_key(&proc_keyring, "disk-encryption-key");
if (k) {
    printf("找到密钥:\n");
    print_key(k);
}

printf("\n--- keyring 类型 ---\n");
printf("进程 keyring:\n");
printf("  进程私有\n");
printf("  fork 时继承\n\n");
printf("会话 keyring:\n");
printf("  会话共享\n");
printf("  登录时创建\n\n");
printf("用户 keyring:\n");
printf("  用户共享\n");
printf("  持久存储\n\n");

printf("--- 密钥类型 ---\n");
printf("user: 用户提供的密钥\n");
printf("trusted: TPM 保护的密钥\n");
printf("encrypted: 加密的密钥\n");
printf("asymmetric: 非对称密钥对\n\n");

printf("--- 使用方式 ---\n");
printf("命令行:\n");
printf("  keyctl add user mykey \"data\" @u\n");
printf("  keyctl show\n\n");
printf("代码:\n");
printf("  add_key(\"user\", \"desc\", data, len, keyring)\n");
printf("  request_key(\"user\", \"desc\", NULL, keyring)\n");

老者带林小源走进宝库的更深处。墙壁上挂着三种不同的 keyring——进程 keyring、会话 keyring、用户 keyring。

"密钥不是随便放的,"老者指着它们,"进程 keyring 跟着进程走,fork 时继承,进程结束就销毁。会话 keyring 跟着登录会话走,登出时清除。用户 keyring 持久存储,只要用户存在,密钥就在。"

林小源注意到进程 keyring 上挂着两把钥匙——一把标注着 "disk-encryption-key",另一把是 "ipsec-sa-key"。

"进程通过 把钥匙挂上来,需要的时候用 取。取的时候按描述符查找——'给我那把磁盘加密的钥匙'。"

"权限呢?"

"每把钥匙都有权限位,"老者伸出金属光泽的手指,"0600 表示只有属主能读写。 命令可以查看和管理。钥匙的生命周期也很重要——过期的钥匙应该销毁,不能留着。"

"内核里的每把 key 都有更完整的档案。"老者展开一张铜页,"serial number、type、description、owner、group、permission mask、expiry time、payload、state。状态也不止活着和死了:uninstantiated、instantiated、negative、expired、revoked、dead。后几种会被垃圾回收。"

"权限位也更细。"他继续说,"View 看属性,Read 读 payload 或 keyring 链表,Write 更新 payload 或修改 keyring 链接,Search 决定能否搜索,Link 决定能否挂接,Set Attribute 决定能否改 UID、GID 和权限。"

破关试炼

钥权之试

key 权限中,允许搜索 keyring 并找到 key 的权限叫什么?

答对后才能继续滑动和进入下一章。

宝库的尽头,林小源看到一扇紧锁的小门。门上只有一个钥匙孔,形状与 TPM trusted 密钥完全吻合。

"那是 dm-crypt 的根密钥,"老者说,"整个磁盘的加密都依赖于它。它被 trust source 封装,用户态只能保存加密 blob。启动时,TPM 可以把 key seal 到 PCR 度量值上——如果 bootloader、内核、initramfs 的度量符合预期,TPM 才会解封对应密钥。"

"如果有人篡改了启动链?"

"TPM 拒绝释放密钥。磁盘永远是加密的。"

林小源沉默了。密钥管理不是简单的存储问题——它涉及信任链、硬件根基、生命周期管理。一把钥匙泄漏,整个加密体系崩溃。一把钥匙丢失,所有数据永久不可访问。

"安全与可用性,"他低声说,"永远是跷跷板的两端。"

"encrypted key 则更像一只快一些的铁盒。"老者说,"它不要求 trust source,用 AES 和指定 master key 加解密。master key 可以是 trusted key,也可以是 user key;如果根不够可信,整只盒子的安全性也随之下降。"

"所以 keyring 不是单纯藏钥匙。它还要处理查找、配额、过期、撤销、SELinux 标签,以及找不到 key 时回调用户态补钥匙。"

老者点了点头,转身回到宝库入口。那些钥匙在环形结构上缓缓旋转,每转动一圈,就有一把旧钥匙被销毁,一把新钥匙被生成——密钥轮换,永不停歇。

破关试炼

根信之试

encrypted key 的安全性取决于用来加密它的哪个 key?

答对后才能继续滑动和进入下一章。

道藏笔记

内核启示

加密算法只是锁,钥匙在 keyring 子系统里。key 有 serial、type、description、权限、过期时间、payload 和 state;keyring 本身也是一种 key,里面挂着其他 key。user/logon/keyring 是基础类型,trusted/encrypted keys 则把用户态看到的内容变成加密 blob。

keyring 也有层次:线程、进程、会话、用户相关 keyring 的继承和生命周期不同。权限不只是 rwx,而包括 View、Read、Write、Search、Link、Set Attribute。trusted key 依赖 TPM/TEE 等 trust source;encrypted key 不要求 trust source,但安全性取决于 master key 的可信程度。

keyring 是保险箱——保护密钥不被窃取。


破关试炼

密钥管理之试

密钥管理中,用硬件信任根保存和保护密钥的模块缩写是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道