oxideav-aacs
Pure-Rust, clean-room implementation of the AACS (Advanced Access Content System) decryption layer used by Blu-ray Disc, per the publicly-published AACS LA technical specifications Common Final 0.953 (Oct 2012) and BD-Prerecorded Final 0.953 (Oct 2012).
Round 188 adds the Content Hash Table integrity layer per BD-Prerecorded Final 0.953 §2.3 — the per-Hash-Unit SHA-1 check a Licensed Player runs to detect tampered Clip AV stream data:
cht::ContentHashTable::parse(bytes, number_of_digests, number_of_hash_units)— parses aContentHash00N.tbl(Table 2-2): a header ofNumber_of_Digests12-byte per-Clip descriptors (Starting_HU_Num/Clip_Num/HU_Offset_in_Clip) followed byNumber_of_HashUnits8-byte Hash Values. The two counts come from the per-layer Content Certificate (Table 2-1), not the table file itself, so they are passed in.cht::ContentHashTable::verify_hash_unit(index, hash_unit_bytes)— recomputesHash_Value = [SHA-1(Hash_Unit)]_lsb_64(§2.3.2.1) and compares it to the stored value. A Hash Unit is 96 Logical Sectors =96 × 2048= 196608 bytes (cht::HASH_UNIT_SIZE). The hash is taken over the encrypted on-disc bytes, so a player verifies integrity without holding the Title Key.cht::hash_value_of_unit(hash_unit)— the standalone[SHA-1(·)]_lsb_64primitive for a CHT author.
New error variants AacsError::BadHashUnitLength and
ContentHashMismatch { index }. The crate ships no Content
Certificate signature path yet (that's the AACS-LA-signed Table 2-1
digest layer) — only the CHT Hash-Unit verification it protects.
Round 183 wires AACS_Verify into the MKB parser so callers that
hold the AACS LA public key can check the three signatures the
Common spec defines: the End-of-Media-Key-Block Record signature
(§3.2.5.1.8) and the per-block ECDSA signatures inside the Host /
Drive Revocation List Records (§3.2.5.1.2 / §3.2.5.1.3).
Mkb::verify_end_of_block_signature(original_bytes, aacs_la_pub)— runsAACS_Verify(AACS_LApub, Signature, MKB)against the MKB bytes up to but not including the End-of-MKB record.Mkb::verify_host_revocation_list/verify_drive_revocation_list— verify the per-signature-block ECDSA signatures cumulatively (block N's signed range = Type-and-Version Record || HRL/DRL record bytes up to the byte immediately preceding block N's signature).Mkb::end_of_block_signature: Option<[u8; 40]>and the newhost_revocation_blocks/drive_revocation_blocks: Vec<RevocationSignatureBlock>fields expose the raw 40-byte ECDSA signatures so a caller can also feed them to an external verifier.
The crate ships no AACS LA public key (AACS LA distributes it to
licensees only); the verifiers take a &ec::Point parameter the
caller supplies. The parser still tolerates revocation blocks whose
trailing signature field is truncated per §3.2.5.1.2 final paragraph
("hosts are required to store only the data being signed for the
first signature block, but not required to store the signature
itself"); the verifier surfaces AacsError::MkbSignatureMissing
rather than panicking on a None-signature block.
Phase D (round 127) wires the Type-4 MKB / Key Conversion Data path into the volume pipeline per AACS Common Final 0.953 §3.2.5.1.4 + BD-Prerecorded Final 0.953 §3.8:
subdiff::apply_key_conversion_data(kmp, kcd)— theK_m = AES-G(K_mp, KCD)primitive.AacsVolume::derive_vuk_from_device_key_with_kcd— Type-4-aware VUK derivation that walks the SD tree, tries the Verify Media Key Record on the precursor first (the spec's "old MKB" rule — precursor that verifies directly is the Media Key; KCD is NOT applied), and only invokesAES-G(K_mp, KCD)when the precursor fails verification.AacsVolume::derive_media_key_from_device_key— the raw SD-walk primitive for callers that want to make the verify/KCD decision themselves.Mkb::is_verified_media_key(km) -> bool+ newMkbType::requires_kcd/MkbType::as_u32predicates.
KEYDB.cfg already parses the | KCD | record into
DiscRecords::kcd; that's the conventional source for the 16-byte
KCD payload the BD-ROM KCD-Mark would otherwise supply out-of-band.
Phase C (round 96) adds the Drive-Host Authentication & Key Exchange (AKE) layer per AACS Common Final 0.953 §4.3, on top of the Phase B MMC wire layer:
- ECDSA over the AACS 160-bit curve (Table 2-1,
a = -3overGF(p)) — a clean-room big-integer + short-Weierstrass point implementation (ecmodule, Jacobian scalar multiply) withAACS_Sign/AACS_Verify(ecdsamodule) and a clean-room FIPS 180-2 SHA-1 message digest. Cross-checked bit-exact against an independent Python big-int reference vector. - AES-128-CMAC (NIST SP 800-38B) for the §4.4 transferred-ID MAC, validated against the SP 800-38B Appendix D.1 example vectors.
host_authenticate— the full §4.3 Host-side state machine: AGID →Hn || Host_Cert→Dn || Drive_Cert(verify) →Dv || Dsig(verify) →Hv || Hsig→ Bus Key. The drive side is modelled by an authenticatingDriveAuthStatewired intoMockDrive, so a synthetic-cert handshake authenticates end-to-end and both sides derive the same 128-bit Bus Key (BK = lsb_128(x-coordinate of Hk·Dv) = lsb_128(x(Dk·Hv))).Certificateparse + AACS-LA-signature verification for the 92-byte Drive (Table 4-1) / Host (Table 4-2) certificates, andread_verified_volume_idfor the §4.4 Volume ID transfer withCMAC(BK, Volume_ID)verification.
No real AACS LA keys, no real certificates, no disc fixtures — every test mints its own synthetic LA root + Drive/Host identities and runs the handshake in-process.
Phase B (round 93) adds the SCSI MMC drive-command wire layer:
REPORT_KEY(0xA4),SEND_KEY(0xA3), andREAD_DISC_STRUCTURE(0xAD) typed CDB constructors per MMC-6 r02g + AACS Common Final 0.953.- AACS Key Class 0x02 sub-payload constructors / parsers — AGID
request, Drive Certificate Challenge (
Dn+ Drive Cert), Drive Key (Dv+Dsig), Drive Certificate, Host Certificate Challenge (Hn+ Host Cert), Host Key (Hv+Hsig), Invalidate-AGID. - Volume Identifier read via
READ_DISC_STRUCTUREFormat0x80(32-byteVolume ID || MAC); Pre-recorded Media Serial Number (Format0x81, §4.14.3.2 Table 4-16), Media Identifier (Format0x82, §4.14.3.3 Table 4-17), and Media Key Block Pack (Format0x83, §4.14.3.4 Table 4-18) constructors + response decoders. DriveCommandtrait abstraction over the SCSI pass-through surface (SG_IO/IOSCSITaskDeviceInterface/IOCTL_SCSI_PASS_THROUGH_DIRECT) — Phase B ships only the trait- an in-process
MockDrivefor tests. No real-hardware transport yet.
- an in-process
Round 1 ships the full prerecorded-BD decryption pipeline:
- KEYDB.cfg parser (the de-facto community VUK key-database format)
with XDG search order +
OXIDEAV_AACS_KEYDBenv override. - MKB_RO.inf parser — every record type defined in the Common spec §3.2.5 (Type-and-Version, Host/Drive Revocation List, Verify Media Key, Explicit Subset-Difference, Subset-Difference Index, Media Key Data, Variant Data, End-of-MKB).
- Unit_Key_RO.inf parser — full BD-Prerecorded §3.9.3 Unit Key File header + Unit Key Block decode.
AACS/directory walker — discoversMKB_RO.infandUnit_Key_RO.infunder a disc-mount root, withAACS/DUPLICATE/fallback.- AES primitives: AES-128 ECB block, AES-128-CBC stream with
caller-supplied IV, AES-G one-way function, AES-G3 triple generator,
AES-H hash — all built on top of the RustCrypto
aescrate. - Subset-Difference tree walk (Common spec §3.2.1 — §3.2.4): Device Key + MKB → Processing Key → Media Key.
- VUK derivation (BD-Prerecorded spec §3.3):
Kvu = AES-G(Km, IDv). - Title Key unwrap (BD-Prerecorded spec §3.9.3): per-CPS-Unit
Encrypted CPS Unit Key = AES-128E(Kvu, Kcu). - Content scrambling (BD-Prerecorded spec §3.10): the 6144-byte
Aligned Unit / 16-byte cleartext seed / 6128-byte AES-128-CBC body
decryption pipeline, with
BlockKey = AES-128E(Kcu, seed) XOR seed.
The crate has no real-disc fixtures, no embedded Device Keys, no embedded Processing Keys, no embedded Title Keys, and no disc-specific test vectors. Every test constructs its own key material from scratch and roundtrips through encrypt → parse → decrypt.
Quick example
use ;
let volume = open?;
let keydb = load_default?;
let vuk = volume.resolve_vuk_from_keydb
.expect;
let mut volume = volume;
volume.unwrap_title_keys?;
// Now `volume.cps_units()[i].title_key()` holds the unwrapped key for
// CPS Unit `i`, and `volume.decrypt_unit(&unit, &aligned_6144)` is
// callable.
# Ok::
Crate features
| Feature | Default | Effect |
|---|---|---|
registry |
yes | Pulls in oxideav-core for the workspace-wide Error enum alias. |
default-features = false gives a standalone build that exposes a
crate-local AacsError enum and the same parsing/crypto API surface
without the framework dependency tree.
Legal hygiene
AACS LA publishes the protocol specifications openly at
https://aacsla.com/aacs-specifications/. Implementing the spec
non-commercially is the explicit purpose for which they are published.
This crate does not include or claim an AACS LA Approved Drive /
Approved Player licence (which is the LA's commercial business model
and a separate contractual artefact). Using oxideav-aacs against
real disc content additionally requires that the user have lawfully
obtained both the disc and the relevant Device Key / VUK material —
which AACS LA distributes only to licensees.
The implementation is clean-room: only the AACS LA PDFs, the
KEYDB.cfg format reference at docs/container/aacs/keydb-cfg-format.md,
the SCSI MMC working drafts under docs/container/aacs/mmc/, and a
2007-era Doom9 community thread on the Subset-Difference scheme were
consulted.
Spec source ↔ module map
| Module | Spec § (Common) | Spec § (BD-Prerecorded) |
|---|---|---|
aes |
§2.1.1 — §2.1.4 | (constant IV in §3.10) |
cht |
(SHA-1 §2.1.5) | §2.3 |
subdiff |
§3.2.1 — §3.2.4 | — |
mkb |
§3.2.5 | §3.1, §3.4 |
unit_key |
— | §3.9.3 |
vuk |
— | §3.3 |
content |
— | §3.10 |
volume |
— | §3.1, §3.9, Figure 3-5 |
keydb |
(de-facto community) | — |
Out of scope
- Bus encryption (BD-Prerecorded §3.7) — drive/host SCSI transport concern only.
- AACS Drive / Host authentication ECDSA layer (Common spec ch. 4 §4.3 steps 14-23) — the wire-format CDBs that ferry the AKE protocol are now staged (Phase B); the ECDSA-secp160r1 sign / verify primitives are still pending (Phase C).
- Real AACS LA public key — AACS LA distributes it only to
licensees, so the verifiers (
Mkb::verify_end_of_block_signature,verify_host_revocation_list,verify_drive_revocation_list,Certificate::verify_signature) take a&ec::Pointparameter the caller supplies; tests use a self-issued synthetic LA identity. - Content Certificate (BD-Prerecorded §2.1 / Table 2-1) — the
AACS-LA-signed wrapper that carries the per-Clip Content Hash
Table Digests. The Content Hash Table Hash-Unit integrity
check it protects (§2.3,
[SHA-1(Hash_Unit)]_lsb_64) IS implemented in thechtmodule as of round 188; parsing the signed Table 2-1 certificate + verifying its Signature Data and per-Clip digests is the remaining piece. - AACS 2.0 (Ultra HD Blu-ray) — separate spec family, not publicly released.
- BD+ — separate copy-protection layer, not public.
Authoritative references
- AACS LA, Advanced Access Content System (AACS) — Introduction and Common Cryptographic Elements, Revision 0.953 Final, 26 Oct 2012.
- AACS LA, Advanced Access Content System (AACS) — Blu-ray Disc Pre-recorded Book, Revision 0.953 Final, 26 Oct 2012.
- Doom9's Forum, "Understanding AACS (including Subset-Difference)", thread 122363 (2007) — used only for cross-checking the §3.2.1 diagram, never for code text.
All three are mirrored in
docs/container/aacs/
in the workspace repo.
License
MIT © 2026 Karpelès Lab Inc.