2022 APFS Advent Challenge Day 16 - Wrapped Keys
In our last post, we discussed both [Volume and Container Keybags](/post/2022/12/21/APFS-Keybags and how they protect wrapped Volume Encryption and Key Encryption Keys. Depending on whether the encrypted volume was migrated from an HFS+ encrypted Core Storage volume, there are subtle differences in how these keys are used. In this post, we will discuss the structure of these wrapped keys and how they can be used to access the raw Volume Encryption Keys that encrypt data on the file system.
Key Encryption Key Blobs
Each Key Encryption Key (KEK
) is encoded in a binary DER blob with the following structure:
KEKBLOB ::= SEQUENCE {
unknown [0] INTEGER
hmac [1] OCTET STRING
salt [2] OCTET STRING
keyblob [3] SEQUENCE {
unknown [0] INTEGER
uuid [1] OCTET STRING
flags [2] INTEGER
wrapped_key [3] OCTET STRING
iterations [4] INTEGER
salt [5] OCTET STRING
}
}
The keys begin with a header that contains an HMAC-SHA256
hash of the key blob data. The HMAC key is generated from the SHA-256
hash of a magic value concatenated with the given salt.
hmac_key := SHA256("\x01\x16\x20\x17\x15\x05" + salt)
The key blob encodes the wrapped KEK
and additional information needed for unwrapping, including a set of bit-flags.
KEK Flags
Name | Value | Description |
---|---|---|
KEK_FLAG_CORESTORAGE | 0x00010000’0000000000 | Key is a legacy CoreStorage KEK |
KEK_FLAG_HARDWARE | 0x00020000’0000000000 | Key is hardware encrypted |
If the KEK_FLAG_CORESTORAGE
flag is set, then the wrapped KEK was migrated from a Core Storage encrypted HFS+ volume and used a 128-bit key to encrypt the KEK; otherwise, a 256-bit key is used.
Generate a key using the PBKDF2-HMAC-SHA256
algorithm, the user’s password, the provided salt, and the number of iterations.
// Calculate size of wrapping key (in bytes)
key_size := (flags & KEK_FLAG_CORESTORAGE) ? 16 : 32
// Generate unwrapping key from user's password
key := pbkdf2_hmac_sha256(password, salt, iterations, key_size)
// Unwrap the encrypted KEK
kek := rfc3394_unwrap(key, wrapped_key);
If the encrypted volume was migrated from Core Storage and the user changed their password afterward, it’s possible to have a non-Core-Storage wrapped KEK
containing only a 128-bit key. In these instances, the last 128 bits of the unwrapped KEK
will be zeros and should be ignored.
// Shorten the KEK if needed
if is_zeroed(kek[16:]) {
kek = kek[:16];
}
Volume Encryption Key Blobs
Volume Encryption Key (VEK
) blobs have a very similar structure to the KEK
blobs that we just discussed. Depending on if they were migrated from Core Storage, they can also be 128-bit or 256-bit keys.
VEKBLOB ::= SEQUENCE {
unknown [0] INTEGER
hmac [1] OCTET STRING
salt [2] OCTET STRING
keyblob [3] SEQUENCE {
unknown [0] INTEGER
uuid [1] OCTET STRING
flags [2] INTEGER
wrapped_key [3] OCTET STRING
}
}
VEK Flags
Name | Value | Description |
---|---|---|
VEK_FLAG_CORESTORAGE | 0x00010000’0000000000 | Key is a legacy CoreStorage VEK |
VEK_FLAG_HARDWARE | 0x00020000’0000000000 | Key is hardware encrypted |
Use the KEK
to unwrap the VEK
using the RFC3394
key wrapping algorithm. If the wrapped VEK
is a 128-bit Core Storage VEK
, then only the first 128-bits of the KEK
are used.
// Calculate size of wrapping key (in bytes)
vek_size = (flags & VEK_FLAG_CORESTORAGE) ? 16 : 32;
if (vek_size == 16) {
kek = kek[:16];
}
// Unwrap the VEK
vek = rfc3394_unwrap(vek, wrapped_key)
128-bit Core Storage VEKs
must be extended to 256-bit encryption keys. This is accomplished by using the first 128 bits of the SHA256
hash of the VEK
and its UUID as the second half of the key.
// 128-bit veks need to be combined with the first 128-bits of a hash
if vek_size == 16 {
vek = append(vek, SHA256(vek + uuid)[16:])
}
Conclusion
In this post, we discussed utilizing the wrapped keys stored in APFS key bags to gain access to the Volume Encryption Key that protects a user’s data in APFS. Tomorrow, we will conclude our discussion about APFS encryption by describing how to identify and decrypt protected information using these keys.
Find an issue or technical inaccuracy in this post? Please file an issue so that it may be corrected.