CBCS encryption accepts an IV that readers do not receive
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- go
- Domain
- cryptography
Research direction
The constructor at mp4/crypto.go:1022–1024 copies the supplied IV without binding it to tenc; read from there into the CBCS write path (1121–1139) and the decrypt-side default (1481–1483). Run the reproduction in the issue body, which builds examples/stream-encrypt and cmd/mp4ff-encrypt, and note the per-fragment IV derivation in examples/stream-encrypt/encryptor.go:57–62. Done means the script reports 4/4 and 60/60 or an explicit encryption error, plus a regression test for a mismatched IV and CBCS init data without a constant IV.
Written by the indexing model from the issue text.
Description
CBCS encryption can succeed with an initialization vector (IV) different from the constant IV in the init segment's tenc box. Readers use the init's IV and recover different media bytes. This affects callers that protect an init once, then supply a different IV through mp4.EncryptFragment, mp4.EncryptFragments, or InitProtectData.NewFragmentEncryptor / FragmentEncryptor.EncryptFragment, including mp4ff-encrypt -init.
Reproduced at d98f0d6 with committed files, Go 1.27.4 and FFmpeg 7.1.5:
| Reproduction | Observed | Expected |
|---|---|---|
examples/stream-encrypt, -scheme cbcs, mp4/testdata/v300_multiple_segments.mp4 |
0/4 decrypted media payloads equal the originals | 4/4, or an explicit encryption error |
Same example, -scheme cenc |
4/4 equal | 4/4 |
CLI, differing IV, mp4/testdata/init.mp4 + mp4/testdata/1.m4s |
Both CLIs exit 0; FFmpeg frames equal: 0/60 | 60/60, or an explicit encryption error |
| Same CLI input, matching IV | FFmpeg frames equal: 60/60 | 60/60 |
The likely cause is in mp4/crypto.go:1022–1024: the constructor copies the supplied IV without binding it to tenc. CBCS uses that IV but writes no per-sample IV (mp4/crypto.go:1121–1139); decryption starts from tenc.DefaultConstantIV (mp4/crypto.go:1481–1483). The streaming example derives a new IV before each fragment (examples/stream-encrypt/encryptor.go:57–62). The constructor also lacks a binding check for CBCS init data without a constant IV.
Production prevalence is unknown.
Reproduction from the repository root (Bash, Python 3 and FFmpeg)
set -euo pipefail
mkdir -p /tmp/cbcs-iv-repro
out=/tmp/cbcs-iv-repro
key=00112233445566778899aabbccddeeff
iv=a0a1a2a3a4a5a6a7a8a9aaabacadaeaf
bad=a0a1a2a3a4a5a6a7a8a9aaabacadaeb0
go build -o "$out/stream" ./examples/stream-encrypt
go build -o "$out/encrypt" ./cmd/mp4ff-encrypt
go build -o "$out/decrypt" ./cmd/mp4ff-decrypt
for scheme in cbcs cenc; do
"$out/stream" -port 18080 -input mp4/testdata/v300_multiple_segments.mp4 \
-scheme "$scheme" -key "$key" -keyid "$key" -iv "$iv" > "$out/server-$scheme.log" 2>&1 &
server=$!
trap 'kill "$server" 2>/dev/null || true' EXIT
for attempt in {1..100}; do
if curl -fsS http://localhost:18080/enc.mp4 -o "$out/$scheme.mp4" 2>/dev/null; then break; fi
sleep .05
done
kill "$server"; wait "$server" || true
trap - EXIT
"$out/decrypt" -key "$key" "$out/$scheme.mp4" "$out/$scheme-clear.mp4"
done
python3 - <<'PY'
from pathlib import Path
import struct
def mdats(path):
data, pos, result = Path(path).read_bytes(), 0, []
while pos < len(data):
size, kind = struct.unpack_from('>I4s', data, pos)
assert size >= 8 and pos+size <= len(data)
if kind == b'mdat': result.append(data[pos+8:pos+size])
pos += size
return result
original = mdats('mp4/testdata/v300_multiple_segments.mp4')
for scheme in ('cbcs', 'cenc'):
got = mdats('/tmp/cbcs-iv-repro/'+scheme+'-clear.mp4')
print(scheme, 'recovered payloads:', sum(a == b for a,b in zip(original,got)),
'/', len(original), 'output payloads:', len(got))
PY
"$out/encrypt" -scheme cbcs -key "$key" -kid "$key" -iv "$iv" mp4/testdata/init.mp4 "$out/init.mp4"
cat mp4/testdata/init.mp4 mp4/testdata/1.m4s > "$out/clear.mp4"
ffmpeg -nostdin -v error -y -i "$out/clear.mp4" -f framemd5 "$out/clear.md5"
for mode in matching differing; do
supplied=$iv
if [ "$mode" = differing ]; then supplied=$bad; fi
"$out/encrypt" -key "$key" -iv "$supplied" -init "$out/init.mp4" mp4/testdata/1.m4s "$out/$mode.m4s"
echo "$mode encrypt exit: $?"
"$out/decrypt" -key "$key" -init "$out/init.mp4" "$out/$mode.m4s" "$out/$mode-clear.m4s"
echo "$mode decrypt exit: $?"
cat "$out/init.mp4" "$out/$mode.m4s" > "$out/$mode.mp4"
ffmpeg -nostdin -v error -y -decryption_key "$key" -i "$out/$mode.mp4" -f framemd5 "$out/$mode.md5"
done
python3 - <<'PY'
from pathlib import Path
def frames(name):
return [line.split(',')[-1].strip() for line in
Path('/tmp/cbcs-iv-repro/'+name+'.md5').read_text().splitlines()
if line.strip() and not line.startswith('#')]
original = frames('clear')
for name in ('matching', 'differing'):
got = frames(name)
print(name, 'equal frames:', sum(a == b for a,b in zip(original,got)),
'/', len(original), 'decoded frames:', len(got))
PY
How should CBCS IVs be handled in these APIs and the streaming example, including init segments without a constant IV? Would you like a pull request with a fix?
Made by Perfloop. Human sponsor: Tomás Senart.
- Dominant language
- Go
- Stars
- 667
- Forks
- 132
- Avg merge
- 20h 4m
- Merged PRs (30d)
- 54
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Eyevinn/mp4ff
-
enhancement
Difficulty 4/5 3-5 days Newbie friendliness 18/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 30/100
Maintainers usually reply within 1 day
Similar issues
-
bug Web API
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
kubevela/terraform-controller#423 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 90/100
fosrl/windows-linux#91 ·
Maintainers usually reply within 1 day