tonhowtf/omniget · error · anyhow::Error
HTTP fetching AES key
Error message
HTTP {} fetching AES key What it means
fetch_key_with_retry downloads the AES-128 encryption key referenced by EXT-X-KEY from its URI. Non-success HTTP statuses are recorded as this error and retried with backoff; the key is required to decrypt segments, so this failure aborts decryption.
Solutions
- Send the same Referer/User-Agent/Cookie headers used for the playlist when fetching the key.
- Re-fetch the playlist (and its key URI) if the key URL is session-bound or expired.
- Check status: 403 -> headers/geo; 404 -> regenerate playlist URL; 429 -> back off and retry later.
- Fetch the key once and cache it (EXT-X-KEY usually reuses the same URI across segments) to reduce rate-limit risk.
Example fix
// before
let key = fetch_key_with_retry(client, key_url, referer, 3).await?;
// after
let key = fetch_key_with_retry(client, key_url, referer, 5)
.await
.with_context(|| format!("fetching AES key {key_url}"))?; Defensive patterns
Strategy: retry
Validate before calling
// Only attempt decryption if the key looks like a 16-byte key
let key = fetch_key_with_retry(client, key_url, referer, 5).await?;
if key.len() != 16 { anyhow::bail!("key server returned {} bytes", key.len()); } Try / catch
match fetch_key_with_retry(client, &key_uri, referer, 5).await {
Ok(k) if k.len() == 16 => k,
Ok(k) => bail!("AES key wrong size: {} bytes", k.len()),
Err(e) if e.to_string().contains("HTTP 40") =>
bail!("key denied: mirror cookies/referer from playlist request"),
Err(e) => return Err(e.into()),
}; Prevention
- Send identical headers/cookies to the key endpoint as to the playlist.
- Cache the key per EXT-X-KEY URI instead of refetching per segment.
- Refresh the playlist when key URLs are session-bound or one-time.
- Back off on 429 and honor Retry-After.
When it happens
Trigger: Key server returns 403 (missing Referer/cookies), 404 (key URI expired or session-bound), 429, or 5xx while fetching the AES key URL.
Common situations: Key URIs tied to the playlist request's session/IP/cookies; expired one-time key tokens; geo-blocking of the key endpoint while segments are served openly.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Failed to fetch AES key after
- HTTP fetching playlist
- AES init
- AES decrypt
- HTTP (fatal) downloading segment
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/58b587b66345cf70.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:476
}
}
Ok(None)
}
async fn fetch_key_with_retry(
&self,
url: &str,
referer: &str,
max_retries: u32,
) -> anyhow::Result<Vec<u8>> {
let mut last_err = None;
for attempt in 0..max_retries {
let req = apply_referer_headers(self.client.get(url), referer)
.header("User-Agent", self.effective_user_agent());
match req.send().await {
Ok(resp) => {
if !resp.status().is_success() {
last_err = Some(anyhow::anyhow!("HTTP {} fetching AES key", resp.status()));
} else {
match resp.bytes().await {
Ok(bytes) => return Ok(bytes.to_vec()),
Err(e) => last_err = Some(anyhow::anyhow!(e)),
}
}
}
Err(e) => last_err = Some(anyhow::anyhow!(e)),
}
if attempt < max_retries - 1 {
let base = 500 * (attempt as u64 + 1);
let jitter = rand::random::<u64>() % (base / 2 + 1);
tokio::time::sleep(Duration::from_millis(base + jitter)).await;
}
}
Err(last_err.unwrap_or_else(|| {
anyhow::anyhow!("Failed to fetch AES key after {} attempts", max_retries)
}))View on GitHub (pinned to 8600b91f42)