shiny crates that could help rewrite iTunes or smth
README.md

fairplay #

Clean Rust port of https://github.com/EthanArbuckle/fairplay-decrypt.

A Rust library for the recovered FairPlay page-key algorithm and AES-128-CBC decryption of protected executable regions. It has no IPA, bundle, filesystem, Mach-O or device dependencies. The caller supplies the region and metadata.

use fairplay::{Decryptor, PageRecords, PAGE_SIZE};

fn decrypt_selected_region(
    credential: &[u8; 16],
    sinf: &[u8],
    supf: &[u8],
    original_file_offset: u64,
    encrypted_region: &mut [u8],
) -> Result<usize, Box<dyn std::error::Error>> {
    let decoder = Decryptor::from_sinf(credential, sinf)?;
    let records = PageRecords::from_supf(supf, 0x0100000c, 0)?;
    let first_page = u32::try_from(original_file_offset / PAGE_SIZE as u64)?;
    Ok(decoder.decrypt_region_in_place(encrypted_region, first_page, records)?)
}

Cache a Decryptor for binaries sharing a SINF. from_sinf_fields accepts an already parsed SinfFields { name, iviv, private }; from_work_key accepts the recovered 16-byte masked working key directly. A working key differs from the saved credential and from an individual page AES key.

PageRecords::from_bytes borrows flat 32-byte records from an existing SUPF parser. from_supf provides optional metadata parsing and selects an exact CPU type/subtype; it supports SUPF 3/5 and tags 507/510/612. SINF decoding handles both 440-byte and 448-byte private records. No code parses the executable.

Caller responsibilities #

  • Pass only the protected region, containing a nonzero whole number of 4096-byte pages. Decryption resets CBC with each record's IV and removes no padding. Extra SUPF records are allowed.
  • Compute the first absolute page index from the original complete executable offset. For a FAT binary this includes the architecture slice's file base: (slice_base + cryptoff) / 4096. Do not reset it to zero for a section or slice.
  • Match the SUPF architecture and record range to the selected region. Records start at the beginning of the protected region. For a later subregion, advance both first_page_index and the record range via records.slice(start..end).
  • Clear the executable's encryption flag only after decryption succeeds, and handle bundle metadata, signing and output publication in the consuming code.

For page-at-a-time or parallel processing, PageRecords::get provides a PageRecord for decrypt_page_in_place. derive_page_key is available when another component owns CBC decryption. The decoder can be shared across threads.

Region decryption allocates no buffers. Size, record count and page-index validation happens before mutation. Those checks do not authenticate page ciphertext: the recovered scheme uses CBC without an authentication tag. The SINF private record's markers detect credentials that fail its decoding check. Secrets are omitted from Debug and temporary SINF keys/plaintext and AES key schedules use zeroization.