2022 APFS Advent Challenge Day 15 - Keybags
APFS is designed with encryption in mind and removes the need for the Core Storage layer used to provide encryption in HFS+. When you enable encryption on a volume, the entire File System Tree and the contents of files within that volume are encrypted. The type of encryption depends on the capabilities of the hardware that it is running on. For example, hardware encryption is used for internal storage on devices that support it, such as macOS computers with T2, M1, or M2 security chips and all iOS devices. Software encryption is used for external and internal storage devices without hardware encryption support. It’s worth noting that when hardware encryption is used, the data cannot be decrypted on any other device. For our purposes, we will focus on the software encryption mechanisms used in macOS. The hardware encryption functions similarly, but the security chip must broker all decryption operations.
Encryption Keys
In macOS, APFS uses a single Volume Encryption Key (VEK
) to access encrypted content on a volume. This VEK
is stored on disk wrapped in several layers of encryption that allow any authorized user on the system to access the volume’s contents. In addition, several recovery keys can be used to access the VEK
.
The VEK
is stored encrypted on disk by a Key Encryption Key (KEK
). Multiple copies of the KEK
are stored on disk, each encrypted (wrapped) with a different key to allow indirect access to the VEK by various users on a system. The keys that are used to encrypt the KEK
can be derived from the following:
- Each user’s password
- The drive’s Personal Recovery Key
- An organization’s Institutional Recovery Key
- Each user’s iCloud Recovery Key
These wrapped keys are stored securely on disk in encrypted objects known as Keybags.
Keybags
Once decrypted, a Keybag is stored as a media_keybag_t
structure on disk.
// A keybag object
typedef struct media_keybag {
obj_phys_t mk_obj; // 0x00
kb_locker_t mk_locker; // 0x20
mk_obj
: The object’s headermk_locker
: The keybag data
The main component of a Keybag is a kb_locker_t
structure.
#define APFS_KEYBAG_VERSION 2
// A keybag
typedef struct kb_locker {
uint16_t kl_version; // 0x00
uint16_t kl_nkeys; // 0x02
uint32_t kl_nbytes; // 0x04
uint8_t padding[8]; // 0x08
keybag_entry_t kl_entries[]; // 0x10
} kb_locker_t;
kl_version
: The keybag’s version (currently always 2)kl_nkeys
: Number of entries stored in the keybagkl_nbytes
: The size (in bytes) of the data stored in thekl_entries
fieldpadding
: reservedkl_entries
: The start of the entries
Immediately following the kb_locker_t
structure is a keybag_entry_t
structure for the first entry in the Keybag. After this structure is the data for the entry, followed by the structure for the next entry.
// An entry in a keybag
typedef struct keybag_entry {
uuid_t ke_uuid; // 0x00
uint16_t ke_tag; // 0x10
uint16_t ke_keylen; // 0x12
uint8_t padding[4]; // 0x14
uint8_t ke_keydata[]; // 0x18
} keybag_entry_t;
ke_uiid
: A context-specific UUID that identifies the entryke_tag
: A description of the kind of data stored in this keybag entryke_keylen
: The length (in bytes) of the keybag entry’s datapadding
: reservedke_keydata
:ke_keylen
bytes of entry data
Keybag Tags
Name | Value | Description |
---|---|---|
KB_TAG_UNKNOWN | 0 | reserved (never found on disk) |
KB_TAG_RESERVED_1 | 1 | reserved |
KB_TAG_VOLUME_KEY | 2 | The key data stored a wrapped VEK |
KB_TAG_VOLUME_UNLOCK_RECORDS | 3 | In a container’s keybag, the key data stores the location of the volumeʼs keybag; in a volume keybag, the key data stores a wrapped KEK. |
KB_TAG_VOLUME_PASSPHRASE_HINT | 4 | The key data stores a user’s password hint as plain text |
KB_TAG_WRAPPING_M_KEY | 5 | The key data stored a key that’s used to wrap a media key |
KB_TAG_VOLUME_M_KEY | 6 | The key data stored a key that’s used to wrap volume media keys |
KB_TAG_RESERVED_F8 | 0xF | reserved |
Container Keybags
The nx_keylocker
field of the container’s NX Superblock is used to locate the encrypted blocks on disk that store the Container Keybag. The XTS-AES-128 encryption key is a 256-bit key, derived from the container’s UUID. Read the 128-bit UUID from the nx_uuid
field of the NX Superblock and concatinate it with itself.
container_keybag_key = container_uuid + container_uuid
Once decrypted the container keybag stores the location of each encrypted volume’s keybag as well as the wrapped VEK
for each.
Volume Unlock Records
Volume Unlock Records are stored in the Container Keybag with a ke_tag
value of KB_TAG_VOLUME_UNLOCK_RECORDS
. The ke_uuid
field stores the same UUID as the apfs_vol_uuid
field of the Volume Superblock. The ke_keydata
is a prange_t
structure that gives the location of the encrypted blocks for the volume’s keybag.
Wrapped VEK
The wrapped VEK
of a volume is stored in the Container Keybag with a ke_tag
volume of KB_TAG_VOLUME_KEY
with the ke_uuid
also being the same as the volume’s UUID. This KEK must be unwrapped using the Key Encryption Key.
Volume Keybags
Each encrypted volume has its own keybag that stores the wrapped KEKs
needed to access the VEK
. For software encrypted APFS, these keybags are encrypted in the same fashion as the container keybags, using two copies of the volume’s UUID as a 256-bit XTS-AES-128 encryption key. Volume keybags can also store human-readable hints to remind user’s of their passphrases.
Volume Unlock Records
In the context of Volume Keybags, Volume Unlock Records store DER encoded information about wrapped KEKs
. The ke_tag
value is always KB_TAG_VOLUME_UNLOCK_RECORDS
and the ke_uuid
is either a cryptograpic user’s UUID or a hardcoded value to denote a recovery key. We will discuss more about the wrapped keys that are found in the ke_keydata
field in tomorrow’s post.
Recovery Key UUIDs
Name | UUID |
---|---|
INSTITUTIONAL_RECOVERY_UUID | {C064EBC6-0000-11AA-AA11-00306543ECAC} |
INSTITUTIONAL_USER_UUID | {2FA31400-BAFF-4DE7-AE2A-C3AA6E1FD340} |
PERSIONAL_RECOVERY_UUID | {EBC6C064-0000-11AA-AA11-00306543ECAC} |
ICLOUD_RECOVERY_UUID | {64C0C6EB-0000-11AA-AA11-00306543ECAC} |
ICLOUD_USER_UUID | {EC1C2AD9-B618-4ED6-BD8D-50F361C27507} |
Passphrase Hints
Passphrase Hint Records are stored with the ke_tag
value of KB_TAG_VOLUME_PASSPHRASE_HINT
and a cryptographic user’s UUID. The ke_keydata
field contains a null-terminated UTF-8 string with the user’s provided passphrase hint.
Conclusion
This post discusses a general overview of APFS Keybags and their on-disk structures. In our next post, we will discuss methods of unwrapping and using the decryption keys.
Find an issue or technical inaccuracy in this post? Please file an issue so that it may be corrected.