shadowsocks/shadowsocks-rust · error
ipsk length mismatch
Error message
ipsk length mismatch
What it means
Panic from `<&Block as TryFrom<&[u8]>>::try_from(ipsk_plain_text).expect("ipsk length mismatch")` in `make_eih`. The input is `blake3::hash(ipsk).as_bytes()[0..16]`, so it only fails if the sliced hash is not 16 bytes — which happens when the slice bounds are wrong or, practically, when the code path is fed an unexpected buffer; the slice here is hard-coded to 16 bytes, so a panic indicates a corrupted/short ipsk source or a modified slice length.
Source
Thrown at crates/shadowsocks/src/relay/tcprelay/aead_2022.rs:570
) -> Self {
// nonce should be sent with the first packet
let mut buffer = BytesMut::with_capacity(nonce.len() + identity_keys.len() * 16);
buffer.put(nonce);
// Extensible Identity Headers
// https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md
#[inline]
fn make_eih(method: CipherKind, sub_key: &[u8], ipsk: &[u8], buffer: &mut BytesMut) {
let ipsk_hash = blake3::hash(ipsk);
let ipsk_plain_text = &ipsk_hash.as_bytes()[0..16];
match method {
CipherKind::AEAD2022_BLAKE3_AES_128_GCM => {
let enc_key = &sub_key[0..16];
let cipher = Aes128::new_from_slice(enc_key).expect("AES-128");
let ipsk_plain_text =
<&Block as TryFrom<&[u8]>>::try_from(ipsk_plain_text).expect("ipsk length mismatch");
let mut block = Block::from([0u8; 16]);
cipher.encrypt_block_b2b(ipsk_plain_text, &mut block);
trace!(
"client EIH {:?}, hash: {:?}",
ByteStr::new(block.as_slice()),
ByteStr::new(ipsk_plain_text)
);
buffer.put(block.as_slice());
}
CipherKind::AEAD2022_BLAKE3_AES_256_GCM => {
let enc_key = &sub_key[0..32];
let cipher = Aes256::new_from_slice(enc_key).expect("AES-256");
let ipsk_plain_text =
<&Block as TryFrom<&[u8]>>::try_from(ipsk_plain_text).expect("ipsk length mismatch");
let mut block = Block::from([0u8; 16]);
cipher.encrypt_block_b2b(ipsk_plain_text, &mut block);View on GitHub (pinned to 8eb0f0a65b)
Solutions
- Use the standard library code path: hash the ipsk with blake3 and slice `[0..16]` exactly as upstream does.
- Verify the ipsk bytes passed to `with_identity` are the full pre-shared key (raw 16/32 bytes), not a truncated or base64-string slice.
- Remove local patches that change the slice bounds of `ipsk_plain_text`.
- If writing your own block conversion, check `ipsk_plain_text.len() == 16` before `try_from` and return an error instead of panicking.
Example fix
// before
let ipsk_plain_text = <&Block as TryFrom<&[u8]>>::try_from(ipsk_plain_text).expect("ipsk length mismatch");
// after
let ipsk_plain_text = <&Block as TryFrom<&[u8]>>::try_from(ipsk_plain_text)
.expect("ipsk plain text must be 16 bytes (blake3 hash slice)"); Defensive patterns
Strategy: validation
Validate before calling
let hash = blake3::hash(ipsk).as_bytes()[0..16].to_vec();
if hash.len() != 16 { return Err("ipsk plain text must be 16 bytes".into()); } Type guard
fn is_block_sized(b: &[u8]) -> bool { b.len() == 16 } Prevention
- Use upstream `make_eih` unchanged; do not alter the [0..16] slice.
- Pass full raw ipsk bytes into `with_identity`, never pre-truncated buffers.
- Avoid vendoring/forking the crate internals.
When it happens
Trigger: Calling `make_eih` (via `with_identity`) where the blake3 hash slice used as `ipsk_plain_text` is not exactly 16 bytes — practically only when the slice expression was changed, or `ipsk` handling produced a non-standard buffer in a patched/forked build.
Common situations: Custom forks or vendored patches altering the `[0..16]` slice; misuse of a lower-level helper expecting a 16-byte block but handed the raw ipsk; upstream/downstream version mismatch in the EIH construction API.
Related errors
AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09).
Data as JSON: /api/errors/de489f276665c87a.
Report an issue: GitHub.