mp4ff-encrypt cbcs: a -iv that differs from the init tenc constant IV encrypts with an IV no reader uses
perfloop-oss/mp4ff · Uncatalogued correctness
https://perfloop.ai/t/oss/case_v0m8hr2ttd
Verdict
VERIFIED · settled 2026-11-14 · pull request opened as Eyevinn/mp4ff#720
What happened: The check fails before this change and passes with it.
Hypothesis
The submitter reports that CBCS media-segment encryption silently succeeds with an initialization vector (IV) different from the constant IV recorded in the protected init segment. The supplied IV is not recorded for readers, which instead use the init's track-encryption box, tenc, and recover different media bytes. This can make successfully produced segments unusable. The reported outcomes and affected-input coverage still need independent confirmation; real-world prevalence is unknown.
The source anchor is run at cmd/mp4ff-encrypt/main.go:67-139, containing the submitted locality at line 91. It requires both key and IV even with -init, loads the protected init at lines 108-123, and forwards the supplied IV into encryptFile at line 134. The -iv flag is required in parseOptions at lines 48-52; -init help lists scheme, kid and pssh, without saying that the IV must repeat the init's value. encryptFile at lines 141-214 extracts existing protection data at line 192 but constructs the fragment encryptor with the separately supplied IV at line 202.
The shared library mechanism is in mp4/crypto.go. InitProtect records the CBCS constant IV in tenc at lines 834-843. InitProtectData.NewFragmentEncryptor at lines 988-1025 validates IV length and copies the supplied IV without comparing it with that constant. FragmentEncryptor.EncryptFragment at lines 1036-1176 uses that IV for CBCS at lines 1121-1131 and adds no per-sample IV at lines 1135-1140. decryptSamplesInPlace at lines 1475-1528 starts from tenc.DefaultConstantIV. The requested repair includes all three shared library routes: EncryptFragment (1203-1205), EncryptFragments (1184-1195), and the stateful NewFragmentEncryptor/FragmentEncryptor.EncryptFragment route, not just the CLI.
At upstream HEAD 8a0670f, also the accepted checkout revision, the submitter reports that the stored check TestContractCBCSBinding in cmd/mp4ff-encrypt fails in 8 of 8 differing-IV cases across the CLI and three library routes and passes its 8 equal-IV cases. I did not find that named check in the accepted cmd/mp4ff-encrypt sources; these counts are attributed reports, not results rerun in this model-build session.
The reported probe uses mp4/testdata/init.mp4 and mp4/testdata/1.m4s. Protect the init with -scheme cbcs and -iv a0a1a2a3a4a5a6a7a8a9aaabacadaeaf, then encrypt 1.m4s with -init enc_init.mp4, the same valid key, and -iv a0a1a2a3a4a5a6a7a8a9aaabacadaeb0. The submitter reports that encryption exits 0 without a message, mp4ff-decrypt also exits 0, and the decrypted mdat differs from the clear mdat. The submitter also reports that ffmpeg -decryption_key yields 0 of 60 frames equal, versus 60 of 60 for the same-IV control. Those reader results remain to be checked independently.
The smallest useful next check is to recover or recreate TestContractCBCSBinding with those fixtures, exercising the CLI and all three library routes with differing and equal IVs. Record errors and CLI diagnostics, and byte-compare each successful decrypted mdat with the original using the init's tenc rather than the caller's IV. Successful differing-IV encryption followed by unequal clear bytes confirms the binding failure; an already-clear mismatch rejection or exact recovery for accepted calls would contradict it. Repeat the reported ffmpeg frame comparison as an independent-reader check. The test implementation and file layout are provisional; the requested repository regression test and its differing-IV round-trip-or-error property are not optional.
Fresh upstream open-PR reads, including the check immediately before publication, returned no open PRs. CONTRIBUTING.md:3 directs contributions to GitHub master, and a bounded recent history read of the two implementation files found no matching constant-IV binding repair. https://github.com/Eyevinn/mp4ff/issues/499 and the merged implementation https://github.com/Eyevinn/mp4ff/pull/500 concern CENC IV advancement between fragments. Its actual patch threads the next IV between calls while leaving CBCS constant-IV handling unchanged. This submission retains the distinct CBCS encryption-versus-tenc binding concern and does not reopen the CENC advancement fix.
Change to test: When a protected init has a CBCS constant IV, use that IV for segment encryption or reject a different supplied IV with a clear error in the CLI and all three shared library routes. Add a regression test for differing IVs that requires decryption through the init's tenc to recover the original clear mdat or requires the call to fail, retaining equal-IV controls.
Where it lives
perfloop-oss/mp4ff · cmd/mp4ff-encrypt/main.go
Evidence
The check fails before this change and passes with it: `With a valid key and the committed AVC init.mp4 and 1.m4s fixtures, EncryptFragment, EncryptFragments and NewFragmentEncryptor/FragmentEncryptor.EncryptFragment reject a CBCS IV that differs after zero-padding from the init tenc constant IV; matching 8- and 16-byte IVs succeed and round-trip mdat through the serialized init's tenc.`
The check fails before this change and passes with it: `When mp4ff-encrypt encrypts the committed mp4/testdata/1.m4s with a CBCS protected init from mp4/testdata/init.mp4 and a valid key, differing IVs are rejected; matching 8- and 16-byte IVs succeed and round-trip mdat through the init tenc.`
The check fails before this change and passes with it: `For CBCS protection data extracted from a serialized AVC init with no constant IV and per-sample IV sizes 0, 8 or 16, NewFragmentEncryptor, EncryptFragment and EncryptFragments reject encryption with a valid key and an all-zero 16-byte IV.`
The check fails before this change and passes with it: `mp4ff-encrypt rejects encryption of the committed mp4/testdata/1.m4s using a CBCS init derived from mp4/testdata/init.mp4 with per-sample IV size 8 and no constant IV, a valid key and an all-zero 16-byte IV.`
The check fails before this change and passes with it: `With valid encryption options and committed v300_multiple_segments.mp4 input, examples/stream-encrypt serves CBCS media that decrypts through its serialized init tenc to every original sample, with 8- or 16-byte IVs, no refragmentation or 30 samples per fragment, on two successive HTTP requests.`
The check fails before this change and passes with it: `With valid encryption options and committed v300_multiple_segments.mp4 input, examples/stream-encrypt serves decryptable CENC media with 8- or 16-byte per-sample IVs, starting at the configured IV and continuing each fragment at the next unconsumed counter, with no refragmentation or 30 samples per fragment, on two successive HTTP requests.`
Checks: 5 of 5 passed. Verification: no defect found.
Timeline
2026-11-10· Case opened2026-11-12· Case closed2026-11-13· PR opened