shadowsocks/shadowsocks-rust · error

EIH key length mismatch

Error message

EIH key length mismatch

What it means

The EIH block passed to decrypt_block_b2b must be exactly one 16-byte AES Block; the TryFrom<&[u8]> conversion is unwrapped with expect("EIH key length mismatch"). It panics when the eih slice is not exactly 16 bytes, meaning the received EIH header length did not match the expected identity-hash block size for the configured method.

Source

Thrown at crates/shadowsocks/src/relay/tcprelay/aead_2022.rs:319

            match self.user_manager {
                Some(ref user_manager) => {
                    // Assume we have at least 1 EIH
                    if header_chunk.len() < 16 {
                        error!("expecting EIH, but header chunk len: {}", header_chunk.len());
                        return Err(ProtocolError::MissingExtendedIdentityHeader).into();
                    }

                    let (eih, remain_header_chunk) = header_chunk.split_at_mut(16);
                    header_chunk = remain_header_chunk;

                    let key_material = [key, salt].concat();
                    let identity_sub_key = blake3::derive_key(AEAD2022_EIH_SUBKEY_CONTEXT, &key_material);
                    let mut user_hash = Block::from([0u8; 16]);
                    match self.method {
                        CipherKind::AEAD2022_BLAKE3_AES_128_GCM => {
                            let cipher = Aes128::new_from_slice(&identity_sub_key[0..16]).expect("AES-128");
                            cipher.decrypt_block_b2b(
                                <&Block as TryFrom<&[u8]>>::try_from(eih).expect("EIH key length mismatch"),
                                &mut user_hash,
                            );
                        }
                        CipherKind::AEAD2022_BLAKE3_AES_256_GCM => {
                            let cipher = Aes256::new_from_slice(&identity_sub_key[0..32]).expect("AES-256");
                            cipher.decrypt_block_b2b(
                                <&Block as TryFrom<&[u8]>>::try_from(eih).expect("EIH key length mismatch"),
                                &mut user_hash,
                            );
                        }
                        _ => unreachable!("{} doesn't support EIH", self.method),
                    }

                    let user_hash = user_hash.as_slice();
                    trace!(
                        "server EIH {:?}, hash: {:?}",
                        ByteStr::new(eih),
                        ByteStr::new(user_hash)

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Upgrade shadowsocks-rust: newer versions validate EIH length and return a ProtocolError instead of panicking
  2. Ensure client and server use matching versions and the same AEAD2022 method
  3. Validate the EIH length before decrypt_block_b2b and reject short/long headers with an io/protocol error

Example fix

// before
<&Block as TryFrom<&[u8]>>::try_from(eih).expect("EIH key length mismatch"),
// after
let block = <&Block as TryFrom<&[u8]>>::try_from(eih)
    .map_err(|_| ProtocolError::EihLengthMismatch)?;
Defensive patterns

Strategy: validation

Validate before calling

if eih.len() != 16 { return Err(ProtocolError::EihLengthMismatch.into()); }

Type guard

fn is_valid_eih(eih: &[u8]) -> bool { eih.len() == 16 }

Try / catch

let block = <&Block as TryFrom<&[u8]>>::try_from(eih)
    .map_err(|_| ProtocolError::EihLengthMismatch)?;

Prevention

When it happens

Trigger: Server receives an AEAD-2022 connection whose extended identity header chunk is not 16 bytes (truncated packet, attacker-supplied malformed header, or a method/EIH-count mismatch in header parsing) with AES-128 configured.

Common situations: Malformed or hostile client traffic against an AEAD-2022 server; version mismatch between client and server EIH formats; proxy chaining where an intermediate strips/adds EIH bytes.

Related errors


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