Photo of Joe T. Sylve

Joe T. Sylve, Ph.D.

Digital Forensic Researcher and Educator

Encryption Rolling

In our posts on Keybags, Wrapped Keys, and Decryption, we covered the static encryption architecture of APFS: how keys are stored, unwrapped, and used to decrypt data. This post covers encryption rolling, the background process that encrypts, decrypts, or re-keys an entire volume’s data while the system continues operating.

Overview

Encryption rolling is triggered when a volume transitions between encryption states: from unencrypted to encrypted, from encrypted to unencrypted, or from one key to another. Because a volume may contain terabytes of data, this operation cannot complete in a single transaction. Instead, APFS maintains an er_state_phys_t object that tracks progress across transactions, allowing the operation to resume after crashes or reboots.

The encryption rolling state object is referenced by the apfs_er_state_oid field of the Volume Superblock.

Phases

Encryption rolling proceeds through three phases, tracked in the ersb_flags field:

Phase Value Description
ER_PHASE_OMAP_ROLL 1 Rolling the volume’s Object Map nodes
ER_PHASE_DATA_ROLL 2 Rolling file data extents
ER_PHASE_SNAP_ROLL 3 Rolling snapshot data

Each phase processes its objects in windows, encrypting or decrypting a chunk of data at a time (typically 1 MiB per window).

er_state_phys_t

The on-disk encryption rolling state (version 2, 128 bytes total).

#define ER_MAGIC 0x464C4142 // 'FLAB'

typedef struct er_state_phys_header {
    obj_phys_t ersb_o;     // 0x00
    uint32_t ersb_magic;   // 0x20
    uint32_t ersb_version; // 0x24
} er_state_phys_header_t;  // 0x28

typedef struct er_state_phys {
    er_state_phys_header_t ersb_header;        // 0x00
    uint64_t ersb_flags;                       // 0x28
    uint64_t ersb_snap_xid;                    // 0x30
    uint64_t ersb_current_fext_obj_id;         // 0x38
    uint64_t ersb_file_offset;                 // 0x40
    uint64_t ersb_progress;                    // 0x48
    uint64_t ersb_total_blk_to_encrypt;        // 0x50
    oid_t ersb_blockmap_oid;                   // 0x58
    uint64_t ersb_tidemark_obj_id;             // 0x60
    uint64_t ersb_recovery_extents_count;      // 0x68
    oid_t ersb_recovery_list_oid;              // 0x70
    uint64_t ersb_recovery_length;             // 0x78
} er_state_phys_t;                             // 0x80

Encryption Rolling Flags

#define ERSB_FLAG_ENCRYPTING       0x00000001
#define ERSB_FLAG_DECRYPTING       0x00000002
#define ERSB_FLAG_KEYROLLING       0x00000004
#define ERSB_FLAG_PAUSED           0x00000008
#define ERSB_FLAG_FAILED           0x00000010
#define ERSB_FLAG_CID_IS_TWEAK     0x00000020
#define ERSB_FLAG_CM_BLOCK_SIZE_MASK  0x00000F00
#define ERSB_FLAG_CM_BLOCK_SIZE_SHIFT 8
#define ERSB_FLAG_ER_PHASE_MASK    0x00003000
#define ERSB_FLAG_ER_PHASE_SHIFT   12
#define ERSB_FLAG_FROM_ONEKEY      0x00004000

The operation type is one of ENCRYPTING, DECRYPTING, or KEYROLLING (key rolling is not supported in the current implementation). The current phase is extracted from bits 12-13. The checksum block size (bits 8-11) encodes the hardware encryption block size used for integrity checks.

Rolling Window Algorithm

The rolling process operates on a window of file extents at a time:

Pre-Roll Phase

  1. Enter a transaction.
  2. Lock file extents for the current window.
  3. For each extent, compute AES-XTS tweaks and read the unrolled data.
  4. Compute SHA-256 checksums for each checksum-block-sized chunk and store truncated hashes in a recovery buffer.
  5. Write recovery data to disk so the operation can resume after a crash.
  6. Commit the transaction.

Data Roll Phase

  1. Encrypt (or decrypt) each extent’s data in-place using XTS-AES.
  2. Write the rolled data back to its physical location.
  3. If a write fails, retry up to 30 times with a 1-second sleep between attempts. After exhausting retries, mark the operation as failed.

Post-Roll Phase

  1. Enter a new transaction.
  2. Update the block map to record the new encryption state.
  3. For decryption, update file extent records to remove the crypto association.
  4. Delete recovery data.
  5. Update progress counters.
  6. Commit.

Recovery Blocks

Recovery blocks store data needed for crash recovery during the data roll phase. If the system crashes after writing encrypted data but before committing the post-roll transaction, the recovery data allows the operation to be verified and resumed.

typedef struct er_recovery_block_phys {
    obj_phys_t erb_o;       // 0x00
    uint64_t erb_offset;    // 0x20
    oid_t erb_next_oid;     // 0x28
    uint8_t erb_data[];     // 0x30
} er_recovery_block_phys_t;

General-Purpose Bitmaps

A general-purpose bitmap tracks per-block rolling state, indicating which blocks have been processed and which remain.

typedef struct gbitmap_phys {
    obj_phys_t bm_o;       // 0x00
    oid_t bm_tree_oid;     // 0x20
    uint64_t bm_bit_count; // 0x28
    uint64_t bm_flags;     // 0x30
} gbitmap_phys_t;          // 0x38

The bitmap blocks themselves are stored as gbitmap_block_phys_t objects containing the raw bitmap data. Each bit represents one block in the volume; a set bit indicates the block has been rolled.

Forensic Considerations

Encryption rolling state reveals important information:

Conclusion

Encryption rolling provides crash-safe, incremental encryption state transitions for APFS volumes. Its multi-phase design (OMAP, data, snapshots) ensures all volume data is processed, while recovery blocks and general-purpose bitmaps enable correct resumption after interruptions. Understanding this mechanism is essential for forensic analysis of volumes in transitional encryption states.

Find an issue or technical inaccuracy in this post? Please file an issue so that it may be corrected.