zeroclaw-labs/zeroclaw · error · anyhow::Error

media aes_key must decode to 16 raw bytes or 32 hex chars, g

Error message

media aes_key must decode to 16 raw bytes or 32 hex chars, got {} bytes

What it means

parse_aes_key in the WeChat channel decodes the aes_key attached to an inbound media message before AES-128-ECB decrypting the file. It accepts exactly three shapes: 32 ASCII hex chars; base64 decoding to 16 raw bytes; or base64 of 32 ASCII hex chars (nested hex). This error fires when the base64 decode succeeded but produced a byte length that is neither 16 nor 32-hex — so the key arrived, but in a malformed or unexpected encoding.

Source

Thrown at crates/zeroclaw-channels/src/wechat.rs:424

                "media nested hex aes_key invalid"
            );
            anyhow::Error::msg(format!("media nested hex aes_key invalid: {e}"))
        })?;
        return <[u8; 16]>::try_from(bytes.as_slice()).map_err(|_| {
            ::zeroclaw_log::record!(
                WARN,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(
                        ::serde_json::json!({"key_kind": "nested_hex", "expected_bytes": 16})
                    ),
                "wechat: media nested hex aes_key has wrong byte length"
            );
            anyhow::Error::msg("media nested hex aes_key must be 16 bytes")
        });
    }

    anyhow::bail!(
        "media aes_key must decode to 16 raw bytes or 32 hex chars, got {} bytes",
        decoded.len()
    )
}

fn https_base_url(
    field_name: &str,
    value: Option<String>,
    default: &str,
) -> anyhow::Result<String> {
    let url = value.unwrap_or_else(|| default.to_string());
    let url = url.trim().trim_end_matches('/').to_string();
    if !url.starts_with("https://") {
        anyhow::bail!("{field_name} must use https://, got {url}");
    }
    Ok(url)
}

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Dump the raw aes_key string from the inbound event and check its length: 32 chars hex, or base64 that decodes to exactly 16 bytes.
  2. Confirm the value is taken verbatim from the attachment payload (no quotes, escaping, or trimming artifacts introduced by your pipeline).
  3. If WeChat/iLink changed the encoding shape, update the channel to map the new format to [u8; 16].
  4. For test payloads, generate a real key: openssl rand -hex 16 (32 hex chars) — never paste a raw 24/32-byte string.

Example fix

// before — placeholder key of the wrong length
let key = parse_aes_key("YWJjZGVmZ2hpamtsbW5vcA==")?; // base64 -> 16 bytes? no: 17 bytes -> error

// after — supply a genuine 128-bit key in one of the accepted shapes
let hex_key = "5a1f2b3c4d5e6f708192a3b4c5d6e7f8";      // 32 hex chars
let key = parse_aes_key(hex_key)?;                       // Ok([u8; 16])
Defensive patterns

Strategy: validation

Validate before calling

// Validate an inbound aes_key before attempting the attachment download.
fn aes_key_shape_ok(raw: &str) -> bool {
    let t = raw.trim();
    if t.len() == 32 && t.bytes().all(|b| b.is_ascii_hexdigit()) {
        return true; // 32 hex chars
    }
    match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, t) {
        Ok(bytes) => bytes.len() == 16
            || (bytes.len() == 32 && bytes.iter().all(u8::is_ascii_hexdigit)),
        Err(_) => false,
    }
}
anyhow::ensure!(aes_key_shape_ok(&event.aes_key), "malformed aes_key: wrong length/encoding");

Try / catch

match channel.download_inbound_attachment(&event).await {
    Ok(bytes) => { /* persist media */ }
    Err(e) if e.to_string().contains("media aes_key must decode to") => {
        // Upstream payload problem, not transient: log the key length/encoding,
        // skip this attachment, and never retry the identical payload.
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: download_inbound_attachment receives an event whose aes_key (1) was copied/serialized with extra characters so base64 decodes to 17/24/33... bytes; (2) is base64 of a 16-byte key with padding or whitespace mangled by upstream JSON handling; (3) comes from a WeChat iLink API change that altered key encoding (e.g. raw bytes length different than 16, or a new wrapper). Every length-check branch above logs a WARN with key_kind before this fallthrough bail.

Common situations: WeChat backend rolling out a new media-message format while older channel code parses it; a proxy/transformer in the event pipeline re-encoding the key; hand-crafted test payloads using a 24-byte (192-bit) or 32-byte raw key instead of the required 128-bit key; config/tutorial examples showing a placeholder key of the wrong length.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/0956ef1b98f4e549. Report an issue: GitHub.