{"record":{"id":"6bf80abcdc727a6a","repo":"tonhowtf/omniget","slug":"aes-decrypt","errorCode":null,"errorMessage":"AES decrypt: {:?}","messagePattern":"AES decrypt: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":680,"sourceCode":"        while let Some(segment_data) = pending.remove(&next_expected) {\n            // The image wrapper, when present, sits outside the encryption:\n            // it has to come off before the AES-128 block decryption runs.\n            let payload_start = image_wrapper_offset(&segment_data);\n\n            if let Some(enc) = encryption {\n                use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};\n                type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;\n\n                let iv = compute_iv(enc, next_expected, media_sequence);\n                let mut buf = segment_data;\n                if payload_start > 0 {\n                    buf.drain(..payload_start);\n                }\n                let decryptor = Aes128CbcDec::new_from_slices(&enc.key_bytes, &iv)\n                    .map_err(|e| anyhow::anyhow!(\"AES init: {:?}\", e))?;\n                let decrypted = decryptor\n                    .decrypt_padded_mut::<Pkcs7>(&mut buf)\n                    .map_err(|e| anyhow::anyhow!(\"AES decrypt: {:?}\", e))?;\n                file.write_all(decrypted)?;\n            } else {\n                file.write_all(&segment_data[payload_start..])?;\n            }\n            next_expected += 1;\n        }\n    }\n\n    file.flush()?;\n\n    if next_expected < total_segments {\n        anyhow::bail!(\n            \"Only {} of {} segments were written\",\n            next_expected,\n            total_segments\n        );\n    }\n","sourceCodeStart":662,"sourceCodeEnd":698,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L662-L698","documentation":"This error is raised in write_segments_ordered when the HLS segment decryptor fails at the decryption step (AES-128-CBC with PKCS7 padding via decrypt_padded_mut). Initialization succeeded (key/IV slices were valid lengths), but the ciphertext did not decrypt cleanly — most often because the padded data length is not a multiple of 16 bytes or the PKCS7 padding is invalid. It wraps the underlying cipher error with anyhow to give context that the failure happened during AES segment decryption.","triggerScenarios":"A media playlist segment has EXT-X-KEY with METHOD=AES-128; the segment bytes were truncated (not a multiple of 16 bytes), or the wrong IV/key was used, or the payload_start offset cut the buffer mid-block, so PKCS7 unpadding fails.","commonSituations":"Live/HLS streams where segments are downloaded partially or the playlist rotated and segment URLs return an error page instead of encrypted media; encrypting key rotated between playlist fetches; custom IV (EXT-X-KEY IV attribute) missing so a wrong default IV is derived from the media sequence number; corrupt or HTML error body written into buf instead of ciphertext.","solutions":["Verify the segment download completed fully (check Content-Length / resp bytes length) before decrypting; re-download truncated segments.","Confirm the correct EXT-X-KEY URI is fetched and the IV matches the playlist (use the IV attribute or media sequence number exactly as specified).","Ensure payload_start trimming keeps the ciphertext block-aligned (multiple of 16 bytes for AES-128-CBC).","Log the segment URL, key URI, and IV alongside this error to identify which segment/key pairing is wrong."],"exampleFix":"// before\nlet decrypted = decryptor\n    .decrypt_padded_mut::<Pkcs7>(&mut buf)\n    .map_err(|e| anyhow::anyhow!(\"AES decrypt: {:?}\", e))?;\n// after\nif buf.len() == 0 || buf.len() % 16 != 0 {\n    anyhow::bail!(\n        \"segment {} ciphertext not block-aligned ({} bytes); re-downloading\",\n        seg_url, buf.len()\n    );\n}\nlet decrypted = decryptor\n    .decrypt_padded_mut::<Pkcs7>(&mut buf)\n    .with_context(|| format!(\"AES decrypt failed for segment {} (len {})\", seg_url, buf.len()))?;","handlingStrategy":"try-catch","validationCode":"// before decrypting\nif enc.key_bytes.len() != 16 {\n    return Err(anyhow::anyhow!(\"AES-128 key must be 16 bytes, got {}\", enc.key_bytes.len()));\n}\nif iv.len() != 16 {\n    return Err(anyhow::anyhow!(\"AES-128 IV must be 16 bytes, got {}\", iv.len()));\n}\nif buf.len() == 0 || buf.len() % 16 != 0 {\n    return Err(anyhow::anyhow!(\"ciphertext not block-aligned: {} bytes (truncated segment?)\", buf.len()));\n}","typeGuard":"fn is_block_aligned(buf: &[u8]) -> bool { !buf.is_empty() && buf.len() % 16 == 0 }","tryCatchPattern":"match decryptor.decrypt_padded_mut::<Pkcs7>(&mut buf) {\n    Ok(decrypted) => file.write_all(decrypted)?,\n    Err(e) => {\n        log::warn!(\"decrypt failed ({}), re-fetching segment once\", e);\n        // re-download the segment, then retry decrypt; bail if it fails again\n    }\n}","preventionTips":["Always validate segment payload length is a nonzero multiple of 16 before AES-CBC decrypt.","Verify key fetch (EXT-X-KEY URI) succeeded and returned exactly 16 bytes.","Honor the playlist's IV attribute or derive it from media sequence exactly per the HLS spec.","Check the segment body isn't an HTML/JSON error page before treating it as ciphertext."],"tags":["hls","aes","decryption","streaming"],"backgroundTag":"checksum-mismatch","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}