SipHash Security-Focused API Documentation

Algorithm: SipHash-2-4
Type: Pseudorandom Function (PRF) / Message Authentication Code (MAC)
Security Level: 64-bit or 128-bit output
Key Size: 128 bits (16 bytes)
Design: ARX (Add-Rotate-XOR) construction
Standard: Reference specification by Aumasson & Bernstein

Table of Contents

  1. Security Contract
  2. Attack Resistance Matrix
  3. Secure Usage Examples
  4. Common Mistakes
  5. Performance vs Security
  6. Platform-Specific Notes
  7. Compliance Information

Security Contract

siphash64(key: Uint8Array, message: Uint8Array): bigint

Preconditions:

Postconditions:

Side Effects:

siphash128(key: Uint8Array, message: Uint8Array): Uint8Array

Preconditions:

Postconditions:

Side Effects:

verify(key: Uint8Array, message: Uint8Array, tag: bigint | Uint8Array): boolean

Preconditions:

Postconditions:

Side Effects:

Attack Resistance Matrix

✅ Attacks Prevented

Attack Type Protection Mechanism Security Level
Forgery without key PRF security 2^128 key space
Collision (64-bit) Birthday bound 2^32 messages
Collision (128-bit) Birthday bound 2^64 messages
Length Extension Not vulnerable (uses key) Complete protection
Timing Attacks Constant-time operations No key leakage
Differential Cryptanalysis ARX design > 2^64 complexity
Key Recovery No known attacks 2^128 operations
State Recovery Forward security Cannot recover key

❌ Attacks NOT Prevented

Attack Type Reason Mitigation Required
Replay Attacks No built-in freshness Add nonce/timestamp
Short Tag Forgery 64-bit may be insufficient Use 128-bit variant
Multi-collision Birthday paradox applies Limit messages per key
Fault Injection No error detection Hardware protection
Side-Channel (Power) Implementation dependent Use masked implementation

⚠️ Security Limitations

  1. Output Size: 64-bit variant vulnerable to birthday attacks at 2^32 messages
  2. Not Encryption: Does not provide confidentiality
  3. Single Purpose: Designed for short messages and hash tables
  4. No Key Derivation: Not suitable for password hashing

Secure Usage Examples

Hash Table Protection

import { SipHash } from '@metamui/siphash';
import { randomBytes } from '@metamui/random';

class SecureHashTable<T> {
  private readonly key: Uint8Array;
  private readonly buckets: Map<bigint, T[]>;
  
  constructor() {
    // Generate random key for this hash table instance
    this.key = randomBytes(16);
    this.buckets = new Map();
  }
  
  set(key: string, value: T): void {
    const hash = SipHash.siphash64(
      this.key,
      new TextEncoder().encode(key)
    );
    
    const bucket = hash % 1024n; // 1024 buckets
    
    if (!this.buckets.has(bucket)) {
      this.buckets.set(bucket, []);
    }
    
    this.buckets.get(bucket)!.push(value);
  }
  
  get(key: string): T | undefined {
    const hash = SipHash.siphash64(
      this.key,
      new TextEncoder().encode(key)
    );
    
    const bucket = hash % 1024n;
    const items = this.buckets.get(bucket);
    
    // Linear search within bucket
    // (In practice, store key-value pairs)
    return items?.[0];
  }
}

Message Authentication

class MessageAuthenticator {
  constructor(private readonly key: Uint8Array) {
    if (key.length !== 16) {
      throw new Error('Key must be 16 bytes');
    }
  }
  
  authenticate(message: Uint8Array): {
    message: Uint8Array;
    tag: Uint8Array;
    timestamp: number;
  } {
    const timestamp = Date.now();
    
    // Include timestamp to prevent replay
    const timestampBytes = new Uint8Array(8);
    new DataView(timestampBytes.buffer).setBigUint64(0, BigInt(timestamp));
    
    // Concatenate timestamp and message
    const data = new Uint8Array(timestampBytes.length + message.length);
    data.set(timestampBytes, 0);
    data.set(message, timestampBytes.length);
    
    // Generate 128-bit tag for better security
    const tag = SipHash.siphash128(this.key, data);
    
    return { message, tag, timestamp };
  }
  
  verify(
    message: Uint8Array,
    tag: Uint8Array,
    timestamp: number,
    maxAgeMs: number = 300000 // 5 minutes
  ): boolean {
    // Check timestamp freshness
    const now = Date.now();
    if (now - timestamp > maxAgeMs) {
      return false; // Message too old
    }
    
    // Reconstruct data
    const timestampBytes = new Uint8Array(8);
    new DataView(timestampBytes.buffer).setBigUint64(0, BigInt(timestamp));
    
    const data = new Uint8Array(timestampBytes.length + message.length);
    data.set(timestampBytes, 0);
    data.set(message, timestampBytes.length);
    
    // Verify tag
    return SipHash.verify(this.key, data, tag);
  }
}

Request Deduplication

class RequestDeduplicator {
  private readonly key: Uint8Array;
  private readonly seen: Set<string>;
  private readonly maxSize: number;
  
  constructor(maxSize: number = 10000) {
    this.key = randomBytes(16);
    this.seen = new Set();
    this.maxSize = maxSize;
  }
  
  isDuplicate(request: any): boolean {
    // Serialize request deterministically
    const serialized = JSON.stringify(request, Object.keys(request).sort());
    const requestBytes = new TextEncoder().encode(serialized);
    
    // Generate compact fingerprint
    const fingerprint = SipHash.siphash64(this.key, requestBytes);
    const fingerprintHex = fingerprint.toString(16);
    
    if (this.seen.has(fingerprintHex)) {
      return true; // Duplicate request
    }
    
    // Add to seen set
    this.seen.add(fingerprintHex);
    
    // Prevent unbounded growth
    if (this.seen.size > this.maxSize) {
      // Remove oldest entries (convert to LRU for production)
      const toRemove = this.seen.size - this.maxSize;
      const iterator = this.seen.values();
      for (let i = 0; i < toRemove; i++) {
        this.seen.delete(iterator.next().value);
      }
    }
    
    return false;
  }
}

Network Protocol Authentication

class ProtocolAuthenticator {
  private readonly key: Uint8Array;
  
  constructor(sharedSecret: Uint8Array) {
    // Derive SipHash key from shared secret
    this.key = sharedSecret.slice(0, 16);
  }
  
  createPacket(
    command: string,
    payload: Uint8Array,
    sequenceNumber: number
  ): Uint8Array {
    // Create packet structure
    const header = new Uint8Array(24); // 8 cmd + 8 seq + 8 tag
    
    // Write command (padded to 8 bytes)
    const cmdBytes = new TextEncoder().encode(command.padEnd(8, '\0'));
    header.set(cmdBytes.slice(0, 8), 0);
    
    // Write sequence number
    new DataView(header.buffer).setBigUint64(8, BigInt(sequenceNumber));
    
    // Calculate tag over header + payload
    const data = new Uint8Array(16 + payload.length);
    data.set(header.slice(0, 16), 0);
    data.set(payload, 16);
    
    const tag = SipHash.siphash64(this.key, data);
    new DataView(header.buffer).setBigUint64(16, tag);
    
    // Return complete packet
    const packet = new Uint8Array(header.length + payload.length);
    packet.set(header, 0);
    packet.set(payload, header.length);
    
    return packet;
  }
  
  verifyPacket(packet: Uint8Array): {
    valid: boolean;
    command?: string;
    payload?: Uint8Array;
    sequenceNumber?: number;
  } {
    if (packet.length < 24) {
      return { valid: false };
    }
    
    // Extract components
    const header = packet.slice(0, 24);
    const payload = packet.slice(24);
    
    const command = new TextDecoder().decode(header.slice(0, 8)).trim();
    const sequenceNumber = Number(new DataView(header.buffer).getBigUint64(8));
    const receivedTag = new DataView(header.buffer).getBigUint64(16);
    
    // Verify tag
    const data = new Uint8Array(16 + payload.length);
    data.set(header.slice(0, 16), 0);
    data.set(payload, 16);
    
    const expectedTag = SipHash.siphash64(this.key, data);
    
    if (expectedTag !== receivedTag) {
      return { valid: false };
    }
    
    return {
      valid: true,
      command,
      payload,
      sequenceNumber
    };
  }
}

Common Mistakes

❌ Using as General-Purpose Hash

// NEVER DO THIS - SipHash requires secret key!
function insecureHash(data: string): string {
  const key = new Uint8Array(16); // All zeros - NOT SECRET!
  const hash = SipHash.siphash64(key, new TextEncoder().encode(data));
  return hash.toString(16);
}

// CORRECT: Use proper hash function for non-keyed hashing
import { SHA256 } from '@metamui/sha256';

function properHash(data: string): string {
  return SHA256.hash(new TextEncoder().encode(data));
}

❌ Weak Key Generation

// BAD: Predictable key
const weakKey = new TextEncoder().encode('my-secret-key-123').slice(0, 16);

// BAD: Key derived from low entropy
const badKey = new Uint8Array(16);
for (let i = 0; i < 16; i++) {
  badKey[i] = i * 17; // Predictable pattern
}

// GOOD: Cryptographically random key
const strongKey = crypto.getRandomValues(new Uint8Array(16));

❌ Insufficient Output Size

// RISKY: 64-bit may be too small for some applications
function riskyAuth(key: Uint8Array, message: Uint8Array): string {
  const tag = SipHash.siphash64(key, message);
  return tag.toString(16).slice(0, 8); // Only 32 bits!
}

// BETTER: Use full output or 128-bit variant
function betterAuth(key: Uint8Array, message: Uint8Array): string {
  const tag = SipHash.siphash128(key, message);
  return Buffer.from(tag).toString('hex'); // Full 128 bits
}

❌ Key Reuse Across Contexts

// BAD: Same key for different purposes
class InsecureSystem {
  private key = randomBytes(16);
  
  hashPassword(password: string): bigint {
    // WRONG: SipHash not suitable for passwords!
    return SipHash.siphash64(this.key, new TextEncoder().encode(password));
  }
  
  authenticateMessage(message: Uint8Array): bigint {
    // WRONG: Same key as password hashing!
    return SipHash.siphash64(this.key, message);
  }
}

// CORRECT: Different keys for different contexts
class SecureSystem {
  private messageKey = randomBytes(16);
  private tableKey = randomBytes(16);
  
  async hashPassword(password: string): Promise<Uint8Array> {
    // Use proper password hashing
    return Argon2.hash(password, randomBytes(16));
  }
  
  authenticateMessage(message: Uint8Array): Uint8Array {
    // Separate key for messages
    return SipHash.siphash128(this.messageKey, message);
  }
}

Performance vs Security

Variant Selection

Variant Output Size Security Performance Use Case
SipHash-2-4 (64-bit) 64 bits Standard Fastest Hash tables, short-lived MACs
SipHash-2-4 (128-bit) 128 bits Enhanced ~1.5x slower Long-term MACs, protocols
SipHash-4-8 64/128 bits Maximum ~2x slower High-security applications

Performance Characteristics

// Benchmark results (approximate)
// Platform: Intel i7-10700K @ 3.8GHz
// 
// Small messages (< 64 bytes): ~20-30 ns
// Medium messages (1 KB): ~200-300 ns
// Large messages (1 MB): ~200-300 μs
// 
// Throughput: ~3-4 GB/s for large messages

Optimization Tips

  1. Batch Processing: Process multiple messages with same key
  2. Key Caching: Precompute key schedule if possible
  3. Output Format: Use bigint for 64-bit, avoid conversions
  4. Memory Allocation: Reuse buffers for high-frequency operations

Platform-Specific Notes

JavaScript/TypeScript

Python

Rust

Go

C/C++

Compliance Information

Standards and Specifications

Security Analysis

Ideal for:

Not suitable for:

Implementation Requirements


Security Notice: SipHash is designed for specific use cases requiring a fast PRF. Always use appropriate algorithms for your security requirements. For questions, contact security@metamui.id

Last Updated: 2025-07-06
**Version
: 3.0.0
License: BSL-1.1

Security Analysis

Threat Model: SipHash Threat Model

The comprehensive threat analysis covers:

For complete security analysis and risk assessment, see the dedicated threat model documentation.