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
- Security Contract
- Attack Resistance Matrix
- Secure Usage Examples
- Common Mistakes
- Performance vs Security
- Platform-Specific Notes
- Compliance Information
Security Contract
siphash64(key: Uint8Array, message: Uint8Array): bigint
Preconditions:
keyMUST be exactly 16 bytes (128 bits)keyMUST be kept secretmessagecan be 0 to 2^64 - 1 bytes- Both inputs MUST be valid byte arrays
Postconditions:
- Returns 64-bit hash as bigint
- Same key+message always produces same output
- Output indistinguishable from random without key
- No timing variations based on message content
Side Effects:
- No key material leaked through timing
- Memory allocation for internal state only
siphash128(key: Uint8Array, message: Uint8Array): Uint8Array
Preconditions:
keyMUST be exactly 16 bytes (128 bits)keyMUST be kept secretmessagecan be 0 to 2^64 - 1 bytes
Postconditions:
- Returns 16-byte (128-bit) hash
- Provides stronger collision resistance than 64-bit
- Same security properties as siphash64
Side Effects:
- Slightly more computation than 64-bit variant
- No observable timing variations
verify(key: Uint8Array, message: Uint8Array, tag: bigint | Uint8Array): boolean
Preconditions:
keyMUST match key used for generationmessageMUST be exact original messagetagMUST be previously generated tag
Postconditions:
- Returns
trueif tag is valid - Returns
falseif tag is invalid - Constant-time comparison
- No timing leaks about tag differences
Side Effects:
- No information leaked through timing
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
- Output Size: 64-bit variant vulnerable to birthday attacks at 2^32 messages
- Not Encryption: Does not provide confidentiality
- Single Purpose: Designed for short messages and hash tables
- 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
- Batch Processing: Process multiple messages with same key
- Key Caching: Precompute key schedule if possible
- Output Format: Use bigint for 64-bit, avoid conversions
- Memory Allocation: Reuse buffers for high-frequency operations
Platform-Specific Notes
JavaScript/TypeScript
- BigInt native support for 64-bit operations
- Use
crypto.getRandomValues()for key generation - Consider WASM for performance-critical paths
Python
- Use
int.from_bytes()for 64-bit representation secrets.token_bytes(16)for key generation- NumPy can vectorize for batch operations
Rust
u64native type for 64-bit variant- Zero-copy with slice operations
- SIMD optimizations possible
Go
uint64for 64-bit operations- Constant-time comparison in
subtlepackage - Good compiler optimizations for ARX operations
C/C++
- Original reference implementation available
- Compiler intrinsics for rotations
- Cache-friendly implementation
Compliance Information
Standards and Specifications
- Original Paper: “SipHash: a fast short-input PRF” (2012)
- Authors: Jean-Philippe Aumasson and Daniel J. Bernstein
- Standardization: Used in Python, Ruby, Rust hash tables
- Patent Status: Public domain, no patents
Security Analysis
- Extensively analyzed in academic literature
- No practical attacks on full SipHash-2-4
- Reduced-round attacks not applicable to full version
- Proven security bounds for PRF properties
Recommended Use Cases
✅ Ideal for:
- Hash table protection against DoS
- Short message authentication
- Network packet authentication
- Deduplication and fingerprinting
- Cache key generation
❌ Not suitable for:
- Password hashing (use Argon2)
- Key derivation (use HKDF)
- General-purpose hashing (use SHA-256/BLAKE3)
- Digital signatures (use Ed25519)
- Long-term security (consider 128-bit variant)
Implementation Requirements
- Must use constant-time operations
- Must validate key size (exactly 16 bytes)
- Must not leak key through side channels
- Should offer both 64-bit and 128-bit variants
- Should follow reference implementation structure
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:
- Algorithm-specific attack vectors
- Implementation vulnerabilities
- Side-channel considerations
- Quantum resistance analysis (where applicable)
- Deployment recommendations
For complete security analysis and risk assessment, see the dedicated threat model documentation.