shadowsocks/shadowsocks-rust · error

EIH key length mismatch

Error message

EIH key length mismatch

What it means

While decrypting the Extensible Identity Header, the 16-byte EIH slice is converted to an aes::Block via TryFrom<&mut [u8]>; only a slice of exactly 16 bytes converts successfully, otherwise .expect("EIH key length mismatch") panics. This is an internal invariant: EIH blocks are fixed at 16 bytes by the 2022 EIH spec, so failure means the packet buffer layout is wrong.

Source

Thrown at crates/shadowsocks/src/relay/udprelay/aead_2022.rs:351

                    // Extensible Identity Header
                    // https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md

                    let (eih, remain_message) = message.split_at_mut(16);
                    message = remain_message;

                    let session_id_packet_id = &packet_header[0..16];

                    trace!(
                        "server EIH {:?}, session_id_packet_id: {:?}",
                        ByteStr::new(eih),
                        ByteStr::new(session_id_packet_id)
                    );

                    match method {
                        CipherKind::AEAD2022_BLAKE3_AES_128_GCM => {
                            let cipher = Aes128::new_from_slice(key).expect("AES-128 init");
                            cipher.decrypt_block(
                                <&mut Block as TryFrom<&mut [u8]>>::try_from(eih).expect("EIH key length mismatch"),
                            );
                        }
                        CipherKind::AEAD2022_BLAKE3_AES_256_GCM => {
                            let cipher = Aes256::new_from_slice(key).expect("AES-256 init");
                            cipher.decrypt_block(
                                <&mut Block as TryFrom<&mut [u8]>>::try_from(eih).expect("EIH key length mismatch"),
                            )
                        }
                        _ => unreachable!("{} doesn't support EIH", method),
                    }

                    for i in 0..16 {
                        eih[i] ^= session_id_packet_id[i];
                    }

                    match user_manager.clone_user_by_hash(eih) {
                        None => {
                            error!("user with identity {:?} not found", ByteStr::new(eih));

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Before decryption, check packet.len() >= method.tag_len() + 16 + 16*eih_count so header plus every EIH block fits; drop short packets.
  2. Verify the client's EIH implementation emits exactly 16 bytes per identity header per the shadowsocks-2022 EIH spec.
  3. Only enable EIH-capable methods with clients that actually support EIH; mismatched implementations produce malformed packets.
  4. Patch the expect to return ProtocolError::DecryptPayloadError so malformed packets are dropped instead of crashing the relay task.

Example fix

// before
decrypt_server_payload_aead_2022(ctx, method, key, &mut buf) // buf may be truncated
// after
let eih_count = ident_psks.len();
if buf.len() < method.tag_len() + 16 + 16 * eih_count {
    return; // drop malformed packet
}
decrypt_server_payload_aead_2022(ctx, method, key, &mut buf)
Defensive patterns

Strategy: validation

Validate before calling

// ensure packet fits header + all EIH blocks before decrypt
let eih_count = ident_psks.len();
if packet.len() < method.tag_len() + 16 + 16 * eih_count {
    return Ok(()); // drop malformed packet
}

Type guard

fn fits_eih(method: CipherKind, packet: &[u8], eih_count: usize) -> bool {
    packet.len() >= method.tag_len() + 16 + 16 * eih_count
}

Try / catch

// drop on decrypt failure instead of panicking:
match decrypt_server_payload_aead_2022(ctx, method, key, &mut buf) {
    Err(_) => { /* drop malformed EIH packet */ }
    Ok(_) => { /* proceed */ }
}

Prevention

When it happens

Trigger: decrypt_client_payload_aead_2022 / decrypt_server_payload_aead_2022 on a packet where the bytes following the 16-byte header are fewer than 16 (message too short to contain the EIH), so eih = message.split_at_mut(16) cannot yield a full block — the split itself panics — or a shorter slice is fed into try_from(eih).

Common situations: Truncated or malformed client UDP packets reaching the EIH branch, a client implementing EIH incorrectly (EIH length not 16), or fuzzing/scanner traffic on an EIH-enabled server.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09). Data as JSON: /api/errors/035efb961d887bd7. Report an issue: GitHub.