Kernel Keyring
Key storage, key types, keyrings, and TPM-backed keys
Overview
The Linux kernel keyring subsystem provides a general-purpose mechanism for storing cryptographic material (keys, tokens, certificates) in kernel memory with access-control enforcement. Keys live in kernel space — they can be referenced by processes without ever being readable in userspace if the key type forbids it.
Callers interact with keyrings via the keyctl(2) syscall (or wrappers like add_key(2)
and request_key(2)). Subsystems such as fscrypt, NFS, IPsec, and IMA use the keyring
internally to store keys that were installed by userspace.
struct key
Every key — including a keyring — is represented by struct key in include/linux/key.h:
struct key {
refcount_t usage;
key_serial_t serial; /* unique key ID (keyctl show prints these) */
union {
struct list_head graveyard_link;
/* internal linkage — varies by kernel version */
};
struct rw_semaphore sem; /* protects key contents */
struct key_user *user; /* owner accounting */
union {
time64_t expiry; /* 0 = no expiry */
time64_t revoked_at;
};
time64_t last_used_at;
kuid_t uid;
kgid_t gid;
key_perm_t perm; /* permission bits (see below) */
unsigned short quotalen; /* bytes charged against quota */
unsigned short datalen; /* payload length */
unsigned long flags;
/* KEY_FLAG_INSTANTIATED, KEY_FLAG_DEAD, KEY_FLAG_REVOKED, ... */
char *description; /* human-readable name */
const struct key_type *type; /* "user", "logon", "keyring", ... */
struct keyring_index_key index_key; /* holds type and description */
union {
union key_payload payload; /* the actual key material */
struct {
/* For keyrings: */
struct assoc_array keys;
};
};
};
key_serial_t is a 32-bit integer (the numeric ID shown by keyctl show). Keys are
reference-counted; key_get() / key_put() manage the lifetime.
Key types
The struct key_type defines how a key is instantiated, validated, read, and destroyed.
Built-in key types:
"user" — userspace-readable bytes
An arbitrary byte blob. Both the description and payload are visible to permitted processes. Used for general key storage where userspace needs to retrieve the value:
"logon" — kernel-only, unreadable by userspace
Same payload format as "user" but the key type's .read callback returns -EPERM. Designed
for kernel consumers (fscrypt, dm-crypt) that need to store key material that must never
be exposed back to userspace:
keyctl add logon fscrypt:abcdef0123456789 <binary_key_data> @s
# Cannot be read back: keyctl pipe will return "permission denied"
fscrypt uses logon keys in the format fscrypt:<key_identifier>. dm-crypt (via cryptsetup)
uses cryptsetup:<uuid>.
"keyring" — a keyring is itself a key
A keyring holds references to other keys. When you search a keyring, the kernel traverses
the chain recursively. The payload is an assoc_array of struct key *.
keyctl newring mykeyring @u # create a named keyring linked to user keyring
keyctl link <key_serial> <ring_serial> # link key into a keyring
"encrypted" — encrypted under a master key
Encrypted keys are stored encrypted under a "master key" (a trusted or user key). The plaintext never leaves kernel space:
/* security/keys/encrypted-keys/encrypted.c */
struct encrypted_key_payload {
struct rcu_head rcu;
char *master_desc; /* description of the encrypting key */
char *datalen;
u8 *iv;
u8 *encrypted_data;
unsigned short decrypted_datalen;
unsigned short payload_datalen;
unsigned short format;
u8 *decrypted_data; /* AES-CBC decrypted in kernel */
u8 payload_data[];
};
Creating an encrypted key:
# Create a user master key first
keyctl add user kmk "$(dd if=/dev/urandom bs=32 count=1 2>/dev/null)" @u
# Create a new 32-byte encrypted key, encrypted under kmk
keyctl add encrypted myenckey "new user:kmk 32" @u
# Export (still encrypted) to save
keyctl pipe <serial> > myenckey.blob
# Later: reload from blob
keyctl add encrypted myenckey "load user:kmk $(cat myenckey.blob)" @u
Two subtypes: "new" creates a fresh random key; "load" loads an existing encrypted blob.
"trusted" — sealed to a TPM
Trusted keys are generated and sealed by the TPM (Trusted Platform Module). The plaintext key material is never exposed to userspace — it only exists inside the TPM or in kernel memory while unsealed:
/* security/keys/trusted-keys/trusted_tpm1.c and trusted_tpm2.c */
struct trusted_key_payload {
struct rcu_head rcu;
unsigned int key_len; /* key size in bytes */
unsigned int blob_len; /* TPM sealed blob size */
unsigned char migratable; /* can the key be migrated? */
unsigned char old_format;
unsigned char key[MAX_KEY_SIZE + 1]; /* plaintext in kernel only */
unsigned char blob[MAX_BLOB_SIZE]; /* TPM-sealed ciphertext */
};
The TPM seals the key under its Storage Root Key (SRK), optionally binding it to PCR values (so the key only unseals when the system is in a specific measured boot state):
# Requires CONFIG_TRUSTED_KEYS=y and a TPM
# Create a new 32-byte trusted key, seal it to current PCR state
keyctl add trusted mytrustedkey "new 32" @u
# Export the sealed blob (the TPM-encrypted form — safe to save)
keyctl pipe <serial> > mytrustedkey.blob
# After reboot: reload the blob (TPM unseals it if PCRs match)
keyctl add trusted mytrustedkey "load $(xxd -p mytrustedkey.blob | tr -d '\n')" @u
# Use the trusted key as a master key for encrypted keys:
keyctl add encrypted myenckey "new trusted:mytrustedkey 64" @u
"asymmetric" — public key for signature verification
Stores an RSA or EC public key (X.509 certificate, PKCS#7). Used by IMA (Integrity Measurement Architecture) for file signature verification, and by the kernel module signing subsystem:
# IMA: add a signing certificate to the IMA keyring
# (usually done during initramfs before pivot_root)
evmctl import /etc/keys/ima.crt $(keyctl show @u | awk '/\.ima/ {print $1}')
The kernel verifies the certificate chain up to a key in the system keyring
(.builtin_trusted_keys or .secondary_trusted_keys).
Keyring hierarchy
Keyrings are arranged in a hierarchy. When a key is searched, the kernel walks up the chain:
@t thread keyring ← searched first (per-thread, rarely used)
@p process keyring ← per-process (all threads share it)
@s session keyring ← per-login-session (inherited across fork/exec)
@u user keyring ← per-UID, persists while user is logged in
@us user-session keyring ← per-UID session (union of @u and @s for most purposes)
@g group keyring ← per-GID (rarely used)
System keyrings (kernel-internal, not per-process):
.builtin_trusted_keys ← built-in X.509 certificates (module signing, IMA)
.secondary_trusted_keys ← runtime-added trusted certs
.ima ← IMA policy and measurement list keys
.blacklist ← revoked certificate hashes
keyctl show # current session keyring tree
keyctl show @u # user keyring
keyctl show @s # session keyring
# Example output:
# Keyring
# xxxxxxxx --alswrv 500 500 keyring: _ses
# yyyyyyyy --alswrv 500 500 \_ keyring: _uid.500
# zzzzzzzz --alswrv 500 500 \_ user: mykey
On fork(), the child inherits the parent's session and user keyrings. On exec(), the
thread keyring is cleared. On CLONE_NEWUSER, a new user keyring is created for the new
user namespace.
Key permissions: key_perm_t
Permissions are a 32-bit value encoding four subjects × six permission bits:
Bits 31..24: possessor permissions (the process that "possesses" the key)
Bits 23..16: user permissions (process UID == key UID)
Bits 15.. 8: group permissions (process GID == key GID)
Bits 7.. 0: other permissions
Per-subject permission bits:
0x01 view see the key's description and metadata
0x02 read read the key's payload
0x04 write update the payload or add links to a keyring
0x08 search find the key during a search
0x10 link link the key into a keyring
0x20 setattr change ownership, permissions, expiry
# Show permissions in octal-like format:
# --alswrv means: all of {link,search,write,read,view} for possessor
keyctl show @u
# xxxxxxxx --alswrv 500 500 keyring: _uid.500
# ^^^^^^^^
# perm field: possessor=alswrv, user=alswrv, ...
# Set permissions explicitly (hex):
keyctl setperm <serial> 0x3f3f0000
# 0x3f = view|read|write|search|link|setattr
# possessor=0x3f, user=0x3f, group=0, other=0
keyctl(2) syscall
#include <sys/types.h>
#include <keyutils.h> /* userspace library wrapping keyctl(2) */
/* Add a key */
key_serial_t add_key(const char *type, const char *description,
const void *payload, size_t plen,
key_serial_t keyring);
/* Request (search for) a key, optionally calling a userspace helper if not found */
key_serial_t request_key(const char *type, const char *description,
const char *callout_info, key_serial_t dest_keyring);
/* keyctl operations */
long keyctl(int operation, ...);
Key keyctl operations:
| Operation | Description |
|---|---|
KEYCTL_GET_KEYRING_ID |
Resolve a special keyring constant (KEY_SPEC_*) to its serial |
KEYCTL_DESCRIBE |
Get description, type, UID, GID, permissions |
KEYCTL_READ |
Read the payload (returns -EPERM for "logon" type) |
KEYCTL_UPDATE |
Update a key's payload |
KEYCTL_REVOKE |
Revoke a key (marks it dead, searches skip it) |
KEYCTL_UNLINK |
Remove a key from a keyring |
KEYCTL_LINK |
Add a key to a keyring |
KEYCTL_SEARCH |
Search a keyring tree for a key by type+description |
KEYCTL_SET_TIMEOUT |
Set or clear key expiry |
KEYCTL_SETPERM |
Change key permissions |
KEYCTL_INSTANTIATE |
Instantiate a key from a userspace key helper |
KEYCTL_INVALIDATE |
Immediately invalidate a key |
KEY_SPEC_* constants for special keyrings:
KEY_SPEC_THREAD_KEYRING /* @t */
KEY_SPEC_PROCESS_KEYRING /* @p */
KEY_SPEC_SESSION_KEYRING /* @s */
KEY_SPEC_USER_KEYRING /* @u */
KEY_SPEC_USER_SESSION_KEYRING /* @us */
KEY_SPEC_GROUP_KEYRING /* @g */
KEY_SPEC_REQKEY_AUTH_KEY /* for request_key authentication */
Shell usage
# Add a "user" type key to the session keyring
keyctl add user mypasswd "s3cr3t" @s
# Show the keyring tree
keyctl show @s
# Read a key's payload
keyctl pipe <serial>
keyctl print <serial> # same but ensures text output
# Search for a key by type and description
keyctl search @s user mypasswd
# Describe a key (type, uid, gid, perm, description)
keyctl describe <serial>
# Set timeout (seconds from now)
keyctl timeout <serial> 300 # key expires in 5 minutes
# Revoke a key immediately
keyctl revoke <serial>
# Unlink from a keyring
keyctl unlink <serial> @s
# Create a new empty keyring
keyctl newring myring @s
# Link a key into an additional keyring
keyctl link <key_serial> <ring_serial>
Key garbage collection
Keys are automatically collected when:
-
Expiry: if
expiryis set andtime64_thas passed. The key enters "expired" state; subsequent searches and reads return-EKEYEXPIRED. -
Revocation:
keyctl revokeor kernel call tokey_revoke(). Returns-EKEYREVOKED. -
Reference count drops to zero: when all processes holding the key have exited or unlinked it, and no keyrings reference it.
-
User quota: each UID has a quota for number of keys and total bytes. Creating a key that exceeds the quota returns
-EDQUOT. Tunable via/proc/sys/kernel/keys/maxkeysand/proc/sys/kernel/keys/maxbytes.
# Check key quota
cat /proc/key-users
# 0: 4 4/1000000 4/1000000 0/1000000 (uid: keys/max keys/max bytes/max)
cat /proc/keys # all keys visible to the calling process
Kernel-side key lookup
Kernel subsystems use request_key() internally:
/* Look up an existing key (no userspace upcall) */
struct key *key = request_key(&key_type_logon, "fscrypt:identifier", NULL);
if (IS_ERR(key)) {
return PTR_ERR(key); /* -ENOKEY if not found */
}
/* Read the payload under the key's RCU lock */
const struct user_key_payload *ukp;
rcu_read_lock();
ukp = user_key_payload_rcu(key);
if (!ukp) {
rcu_read_unlock();
key_put(key);
return -EKEYREVOKED;
}
memcpy(out_key_material, ukp->data, ukp->datalen);
rcu_read_unlock();
key_put(key); /* decrement refcount */
Trusted keys with TPM: deeper look
Trusted keys use the TPM's key hierarchy. The TPM 2.0 flow:
Boot:
UEFI/firmware → measures boot components → extends TPM PCRs
kernel loads → PCR[4] = kernel hash, PCR[7] = SecureBoot state, etc.
keyctl add trusted mytrustedkey "new 32 keyhandle=0x81000001 pcr:sha256:0,1,7" @u
│ │
│ └─ seal to PCR values 0, 1, 7
└─ use this persistent SRK handle
# TPM seals the key:
# 1. Generate random 32 bytes
# 2. TPM2_Create() under the SRK, binding the current PCR[0,1,7] values
# 3. Return TPM2B_PRIVATE + TPM2B_PUBLIC (the sealed blob)
# Later (after reboot):
keyctl add trusted mytrustedkey "load <hex_blob>" @u
# TPM2_Load() + TPM2_Unseal() — only succeeds if PCRs match
If PCRs don't match (e.g., a different kernel booted), TPM2_Unseal returns an error and
the key cannot be instantiated. This is the mechanism behind full-disk encryption that
auto-unlocks only when the system is in a known-good state (similar to Windows BitLocker with
TPM-only mode).
Encrypted keys: "new" vs "load"
Encrypted keys require a master key to be present in the keyring at the time of creation:
# "new": generate a new random key, encrypt it under master, store encrypted blob in kernel
keyctl add encrypted myenckey "new user:mymaster 32" @u
# Export the encrypted blob (this is ciphertext — safe to store on disk)
keyctl pipe <serial> | xxd
# "load": decrypt an existing blob using the master key in the keyring
keyctl add encrypted myenckey "load user:mymaster <hex_blob>" @u
The payload format on disk is: <key_type> <master_desc> <datalen> <iv> <ciphertext> <hmac>,
all in hex. The encryption uses AES-128-CBC with an HMAC-SHA256 integrity check.
fscrypt and IMA integration
fscrypt uses logon keys. When userspace calls FS_IOC_ADD_ENCRYPTION_KEY, the kernel:
- Derives a "filesystem-level" key identifier (HKDF-SHA512 of the raw key material — specifically the first 16 bytes of an HKDF-SHA512 expansion, not a plain SHA-512 hash)
- Stores the master key in the filesystem's in-kernel key structure
- Optionally also accepts "logon" type keys via the legacy v1 API
IMA uses asymmetric keys for file signature verification:
# IMA signature verification flow:
# 1. At boot, IMA loads certs from the IMA keyring (.ima)
# 2. At file access, IMA reads the security.ima xattr (PKCS#7 signature)
# 3. ima_verify_signature() looks up the signing key in .ima
# 4. If not found, or if the signature is invalid: policy action (warn/deny)
# Add an IMA cert manually (e.g., in initramfs):
keyctl padd asymmetric "" %:.ima < /etc/ima/signing_key.der
Container isolation
Key namespaces (struct key_namespace) were added in Linux 5.2, tied to user namespaces
via CLONE_NEWUSER. Each user namespace has its own keyring namespace, providing isolation
between containers using separate user namespaces. Specifically:
CLONE_NEWUSERcreates a new user namespace, which gets a fresh user keyring (@u) and session keyring (@s) for UIDs within that namespace.- A container's
@uis separate from the host's@u, sokeyctl add user key "data" @uinside the container does not affect the host. - The kernel-internal system keyrings (
.builtin_trusted_keys,.ima) are global — all namespaces share them.
Container runtimes rely on the session keyring being established fresh for each container.
Observing the keyring
# Keys visible to the current process
cat /proc/keys
# Serial Flags Usage Expiry Perm UID GID Type Description
# 0c7ec5e3 I--Q-- 1 perm 1f3f0000 0 0 keyring _uid_ses.0
# All keys in the system (requires CAP_SYS_ADMIN for others' keys)
cat /proc/key-users
# Show the keyring tree from shell
keyctl show @s
# List keys on the IMA keyring
keyctl show %:.ima
# Check if a specific key exists
keyctl search @u logon fscrypt:0123456789abcdef
Relevant source
security/keys/— core keyring implementationsecurity/keys/trusted-keys/— trusted key type (TPM 1.2 and 2.0)security/keys/encrypted-keys/— encrypted key typeinclude/linux/key.h—struct key,struct key_typeinclude/uapi/linux/keyctl.h—KEYCTL_*constantscrypto/asymmetric_keys/— asymmetric key typeDocumentation/security/keys/core.rst— kernel documentation
Further reading
- dm-crypt and fscrypt — how logon keys integrate with fscrypt and dm-crypt
- Kernel Crypto API — the crypto primitives that process key material
man 7 keyrings— userspace keyring overviewman 1 keyctl— shell commands for key management