tonhowtf/omniget · error · anyhow::Error

AES decrypt: {:?}

Error message

AES decrypt: {:?}

What it means

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.

Solutions

  1. Verify the segment download completed fully (check Content-Length / resp bytes length) before decrypting; re-download truncated segments.
  2. 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).
  3. Ensure payload_start trimming keeps the ciphertext block-aligned (multiple of 16 bytes for AES-128-CBC).
  4. Log the segment URL, key URI, and IV alongside this error to identify which segment/key pairing is wrong.

Example fix

// before
let decrypted = decryptor
    .decrypt_padded_mut::<Pkcs7>(&mut buf)
    .map_err(|e| anyhow::anyhow!("AES decrypt: {:?}", e))?;
// after
if buf.len() == 0 || buf.len() % 16 != 0 {
    anyhow::bail!(
        "segment {} ciphertext not block-aligned ({} bytes); re-downloading",
        seg_url, buf.len()
    );
}
let decrypted = decryptor
    .decrypt_padded_mut::<Pkcs7>(&mut buf)
    .with_context(|| format!("AES decrypt failed for segment {} (len {})", seg_url, buf.len()))?;
Defensive patterns

Strategy: try-catch

Validate before calling

// before decrypting
if enc.key_bytes.len() != 16 {
    return Err(anyhow::anyhow!("AES-128 key must be 16 bytes, got {}", enc.key_bytes.len()));
}
if iv.len() != 16 {
    return Err(anyhow::anyhow!("AES-128 IV must be 16 bytes, got {}", iv.len()));
}
if buf.len() == 0 || buf.len() % 16 != 0 {
    return Err(anyhow::anyhow!("ciphertext not block-aligned: {} bytes (truncated segment?)", buf.len()));
}

Type guard

fn is_block_aligned(buf: &[u8]) -> bool { !buf.is_empty() && buf.len() % 16 == 0 }

Try / catch

match decryptor.decrypt_padded_mut::<Pkcs7>(&mut buf) {
    Ok(decrypted) => file.write_all(decrypted)?,
    Err(e) => {
        log::warn!("decrypt failed ({}), re-fetching segment once", e);
        // re-download the segment, then retry decrypt; bail if it fails again
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/6bf80abcdc727a6a. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:680

        while let Some(segment_data) = pending.remove(&next_expected) {
            // The image wrapper, when present, sits outside the encryption:
            // it has to come off before the AES-128 block decryption runs.
            let payload_start = image_wrapper_offset(&segment_data);

            if let Some(enc) = encryption {
                use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
                type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;

                let iv = compute_iv(enc, next_expected, media_sequence);
                let mut buf = segment_data;
                if payload_start > 0 {
                    buf.drain(..payload_start);
                }
                let decryptor = Aes128CbcDec::new_from_slices(&enc.key_bytes, &iv)
                    .map_err(|e| anyhow::anyhow!("AES init: {:?}", e))?;
                let decrypted = decryptor
                    .decrypt_padded_mut::<Pkcs7>(&mut buf)
                    .map_err(|e| anyhow::anyhow!("AES decrypt: {:?}", e))?;
                file.write_all(decrypted)?;
            } else {
                file.write_all(&segment_data[payload_start..])?;
            }
            next_expected += 1;
        }
    }

    file.flush()?;

    if next_expected < total_segments {
        anyhow::bail!(
            "Only {} of {} segments were written",
            next_expected,
            total_segments
        );
    }

View on GitHub (pinned to 8600b91f42)