Skip to main content
Sigvex

Hash Collision Remediation

How to fix hash collision vulnerabilities caused by truncation, weak algorithms, or small inputs.

Hash Collision Remediation

Overview

Related Detector: Hash Collision

Hash collision vulnerabilities arise when programs truncate hash outputs, use weak algorithms, or hash small inputs. The fix is to use full-length outputs from secure hash functions (SHA-256, Keccak-256, Blake3) and add domain separation to prevent cross-context collisions.

Before (Vulnerable)

pub fn create_identifier(data: &[u8]) -> u32 {
    let hash = solana_program::hash::hash(data);
    // VULNERABLE: truncating 256-bit hash to 32 bits
    u32::from_le_bytes(hash.to_bytes()[..4].try_into().unwrap())
}

After (Fixed)

pub fn create_identifier(data: &[u8]) -> [u8; 32] {
    // FIXED: use full 256-bit hash output
    solana_program::hash::hash(data).to_bytes()
}

Alternative Mitigations

1. Domain separation for cross-context safety

Prefix hash inputs with a domain tag to prevent collisions across different uses:

pub fn hash_for_merkle(leaf: &[u8]) -> [u8; 32] {
    let mut input = Vec::with_capacity(leaf.len() + 7);
    input.extend_from_slice(b"MERKLE:");
    input.extend_from_slice(leaf);
    solana_program::hash::hash(&input).to_bytes()
}

pub fn hash_for_dedup(tx_data: &[u8]) -> [u8; 32] {
    let mut input = Vec::with_capacity(tx_data.len() + 6);
    input.extend_from_slice(b"DEDUP:");
    input.extend_from_slice(tx_data);
    solana_program::hash::hash(&input).to_bytes()
}

2. Use HKDF for derived identifiers

When shorter identifiers are needed, use a key derivation function rather than truncation:

use hmac::{Hmac, Mac};
use sha2::Sha256;

fn derive_short_id(key: &[u8], data: &[u8], length: usize) -> Vec<u8> {
    let mut mac = Hmac::<Sha256>::new_from_slice(key)
        .expect("HMAC key length");
    mac.update(data);
    let result = mac.finalize().into_bytes();
    result[..length].to_vec()
}

3. Add entropy to small inputs

pub fn hash_with_nonce(small_data: &[u8], nonce: &[u8; 32]) -> [u8; 32] {
    let mut input = Vec::with_capacity(small_data.len() + 32);
    input.extend_from_slice(nonce);
    input.extend_from_slice(small_data);
    solana_program::hash::hash(&input).to_bytes()
}

Common Mistakes

Mistake 1: Using bitwise AND for “short IDs”

// WRONG: masking reduces output space to 2^N bits
let short_id = full_hash & 0xFFFF;  // Only 65,536 possible values

Use the full hash for security-critical operations. If short IDs are needed for display, do not use them for security decisions.

Mistake 2: Using CRC32 or custom hash functions

// WRONG: CRC32 is not a cryptographic hash
let checksum = crc32::checksum_ieee(data);

Always use Solana’s built-in cryptographic hash syscalls: sol_sha256, sol_keccak256, or sol_blake3.

Mistake 3: Comparing truncated hashes for equality

// WRONG: comparing only first 8 bytes
if computed_hash[..8] == expected_hash[..8] {
    authorize_access()?;
}

Compare the full hash output for security decisions.

References