BLAKE3 Security-Focused API Documentation
Version: 1.0
Last Updated: 2025-07-05
**Security Classification: PUBLIC
Author: Phantom (phantom@metamui.id)
Overview
BLAKE3 is a cryptographic hash function that combines the security of BLAKE2 with significantly improved performance through parallelism. It supports arbitrary output lengths, keyed hashing (MAC), key derivation, and extensible output (XOF). BLAKE3 is the fastest secure hash function on modern hardware.
Security Level: 128-bit (collision), 256-bit (preimage)
Default Output Size: 32 bytes (configurable)
Performance: >5 GB/s on modern CPUs
Security Warnings ⚠️
- Not for Passwords: Use Argon2 for password hashing, not BLAKE3
- Key Size: When using keyed mode, keys MUST be exactly 32 bytes
- Context Strings: Use domain separation to prevent cross-protocol attacks
- Parallel Safety: Internal parallelism, safe for concurrent use
- No Length Hiding: Output length can reveal input length in some modes
API Functions
hash(data: bytes, length: int = 32) -> bytes
Security Contract:
- Preconditions:
datacan be any lengthlengthspecifies output size (default 32 bytes)- No practical limit on output length
- Postconditions:
- Returns cryptographically secure hash
- Deterministic output
- No internal state retained
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Collision | ✅ | 2^128 operations minimum | | Preimage | ✅ | 2^min(256,8*outlen) operations | | Second Preimage | ✅ | 2^256 operations | | Length Extension | ✅ | Tree structure prevents | | Timing Attack | ✅ | Constant time operations | | Parallel Collision | ✅ | Tree mode prevents |
Secure Usage Example:
# SECURE: Content addressing with verification
def store_content(data: bytes) -> str:
# Generate content ID
content_hash = blake3.hash(data)
content_id = content_hash.hex()
# Store with integrity
storage[content_id] = {
'data': data,
'hash': content_hash,
'algorithm': 'blake3',
'timestamp': time.time()
}
return content_id
def retrieve_content(content_id: str) -> bytes:
stored = storage[content_id]
# Verify integrity
computed_hash = blake3.hash(stored['data'])
if not constant_time_compare(computed_hash, stored['hash']):
raise IntegrityError("Content corrupted")
return stored['data']
# SECURE: Variable-length output for KDF
def derive_keys(master_secret: bytes) -> dict:
# Generate multiple keys from one hash
extended = blake3.hash(master_secret, length=96)
return {
'encryption_key': extended[0:32],
'mac_key': extended[32:64],
'iv': extended[64:80],
'session_id': extended[80:96]
}
Common Mistakes:
# INSECURE: Using for passwords
password_hash = blake3.hash(password.encode()) # NO!
# INSECURE: Short outputs reduce security
short_hash = blake3.hash(data, length=8) # Only 64 bits!
# INSECURE: No domain separation
key1 = blake3.hash(master + b"encryption")
key2 = blake3.hash(master + b"mac") # Use derive_key instead!
keyed_hash(key: bytes[32], data: bytes, length: int = 32) -> bytes
Security Contract:
- Preconditions:
keyMUST be exactly 32 byteskeyshould have 256 bits of entropydatacan be any length
- Postconditions:
- Returns MAC tag of specified length
- Provides authentication and integrity
- Different keys produce independent outputs
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Forgery | ✅ | 2^128 operations minimum | | Key Recovery | ✅ | 2^256 operations | | Collision | ✅ | Under same key: 2^128 | | Timing Attack | ✅ | Constant time | | Multi-target | ✅ | Security per key maintained |
Security Requirements:
- Keys MUST be generated with cryptographic RNG
- Keys should be unique per use case
- Never reuse keys across different protocols
- Rotate keys periodically
Secure Usage Example:
// SECURE: Message authentication
struct MessageAuthenticator {
key: [u8; 32],
}
impl MessageAuthenticator {
fn new() -> Result<Self, Error> {
let key = generate_random_key()?;
Ok(Self { key })
}
fn create_authenticated_message(&self, msg: &[u8]) -> Vec<u8> {
// Generate MAC
let mac = blake3::keyed_hash(&self.key, msg);
// Append MAC to message
[msg, &mac].concat()
}
fn verify_message(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
if data.len() < 32 {
return Err(Error::InvalidMessage);
}
// Split message and MAC
let (msg, received_mac) = data.split_at(data.len() - 32);
// Verify MAC
let computed_mac = blake3::keyed_hash(&self.key, msg);
if constant_time_eq(&computed_mac, received_mac) {
Ok(msg.to_vec())
} else {
Err(Error::AuthenticationFailed)
}
}
}
// SECURE: Key rotation
fn rotate_key(old_key: &[u8; 32]) -> Result<[u8; 32], Error> {
// Derive new key from old (one-way)
let new_key = blake3::derive_key("key-rotation-v1", old_key);
// Securely erase old key
secure_zero(old_key);
Ok(new_key)
}
derive_key(context: str, material: bytes, length: int = 32) -> bytes
Security Contract:
- Preconditions:
contextis a domain separation stringmaterialcontains key material (any length)contextshould be hardcoded/constant
- Postconditions:
- Returns derived key material
- Different contexts produce independent keys
- One-way derivation (cannot recover input)
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Cross-protocol | ✅ | Context separation | | Related-key | ✅ | Independent derivation | | Weak material | ⚠️ | Output limited by input entropy | | Side-channel | ✅ | Constant time |
Best Practices:
- Use descriptive, unique contexts
- Include version in context string
- Never let users control context
- Validate material has sufficient entropy
Secure Usage Example:
// SECURE: Hierarchical key derivation
class KeyHierarchy {
private masterKey: Uint8Array;
constructor(masterKey: Uint8Array) {
if (masterKey.length !== 32) {
throw new Error("Master key must be 32 bytes");
}
this.masterKey = masterKey;
}
deriveKey(purpose: string, index: number): Uint8Array {
// Create unique context with purpose and index
const context = `MyApp-v1-${purpose}-${index}`;
// Derive purpose-specific key
return blake3.deriveKey(context, this.masterKey, 32);
}
// Derive keys for different purposes
getEncryptionKey(userId: string): Uint8Array {
const userBytes = new TextEncoder().encode(userId);
const combined = concatenate(this.masterKey, userBytes);
return blake3.deriveKey("encryption-per-user-v1", combined, 32);
}
getSigningKey(documentId: string): Uint8Array {
const docBytes = new TextEncoder().encode(documentId);
const combined = concatenate(this.masterKey, docBytes);
return blake3.deriveKey("signing-per-document-v1", combined, 32);
}
}
// SECURE: Protocol-specific derivation
function deriveProtocolKeys(sharedSecret: Uint8Array): ProtocolKeys {
// Each protocol gets independent keys
return {
clientEncrypt: blake3.deriveKey(
"MyProtocol-v2-client-encrypt",
sharedSecret,
32
),
serverEncrypt: blake3.deriveKey(
"MyProtocol-v2-server-encrypt",
sharedSecret,
32
),
clientMAC: blake3.deriveKey(
"MyProtocol-v2-client-mac",
sharedSecret,
32
),
serverMAC: blake3.deriveKey(
"MyProtocol-v2-server-mac",
sharedSecret,
32
),
};
}
create_hasher(key?: bytes[32]) -> Blake3Hasher
Security Contract:
- Preconditions:
keyis optional, exactly 32 bytes if provided- Creates streaming hash context
- Postconditions:
- Returns hasher for incremental hashing
- Can process unlimited data
- Thread-safe hasher instance
Streaming Benefits:
- Process files larger than memory
- Hash data as it arrives
- Update hash incrementally
- Clone hasher state for branching
Secure Usage Example:
# SECURE: Streaming large files with progress
def hash_large_file_with_progress(filepath: str, callback=None):
hasher = blake3.create_hasher()
total_size = os.path.getsize(filepath)
bytes_processed = 0
with open(filepath, 'rb') as f:
while chunk := f.read(1024 * 1024): # 1MB chunks
hasher.update(chunk)
bytes_processed += len(chunk)
if callback:
callback(bytes_processed / total_size)
return hasher.finalize()
# SECURE: Tree hashing for parallel processing
class ParallelTreeHasher:
def __init__(self):
self.chunk_size = 1024 * 1024 # 1MB
def hash_file_parallel(self, filepath: str) -> bytes:
# Split file into chunks
chunks = self._split_file(filepath)
# Hash chunks in parallel
with ThreadPoolExecutor() as executor:
chunk_hashes = executor.map(blake3.hash, chunks)
# Combine chunk hashes
tree_hasher = blake3.create_hasher()
for chunk_hash in chunk_hashes:
tree_hasher.update(chunk_hash)
return tree_hasher.finalize()
update(data: bytes) -> void
Security Contract:
- Preconditions:
- Hasher must be created first
- Can be called multiple times
- Order matters for final hash
- Postconditions:
- Internal state updated
- No output produced yet
- Can continue adding data
Security Notes:
- Total data unlimited
- Each update affects final hash
- Cannot remove data once added
- Thread-safe per instance
finalize(length: int = 32) -> bytes
Security Contract:
- Preconditions:
- Hasher must have been created
- Can be called multiple times
- Length specifies output size
- Postconditions:
- Returns hash of requested length
- Hasher remains valid for more finalize() calls
- Can finalize with different lengths
XOF Properties:
- Extensible output function
- Each output byte equally secure
- Different lengths incompatible
- Useful for key derivation
Secure Usage Example:
// SECURE: Using XOF for multiple outputs
fn generate_crypto_values(seed: &[u8]) -> CryptoValues {
let mut hasher = Blake3::new();
hasher.update(b"MyApp-CryptoValues-v1");
hasher.update(seed);
// Generate different lengths for different purposes
CryptoValues {
session_key: hasher.finalize_xof(32), // 256-bit key
nonce: hasher.finalize_xof(12), // 96-bit nonce
auth_tag: hasher.finalize_xof(16), // 128-bit tag
commitment: hasher.finalize_xof(32), // 256-bit commitment
}
}
Security Best Practices
Domain Separation
class DomainSeparatedHasher:
"""Prevent cross-protocol attacks with domain separation"""
def __init__(self, app_name: str, version: str):
self.domain = f"{app_name}-{version}"
def hash_user_data(self, user_id: str, data: bytes) -> bytes:
hasher = blake3.create_hasher()
hasher.update(f"{self.domain}-user-data".encode())
hasher.update(user_id.encode())
hasher.update(data)
return hasher.finalize()
def hash_system_data(self, component: str, data: bytes) -> bytes:
hasher = blake3.create_hasher()
hasher.update(f"{self.domain}-system-data".encode())
hasher.update(component.encode())
hasher.update(data)
return hasher.finalize()
Keyed Hashing Patterns
/// Secure token generation with BLAKE3
pub struct TokenGenerator {
key: [u8; 32],
counter: AtomicU64,
}
impl TokenGenerator {
pub fn new() -> Result<Self, Error> {
Ok(Self {
key: generate_random_bytes()?,
counter: AtomicU64::new(0),
})
}
pub fn generate_token(&self, purpose: &str) -> String {
let count = self.counter.fetch_add(1, Ordering::SeqCst);
let mut data = Vec::new();
data.extend_from_slice(purpose.as_bytes());
data.extend_from_slice(&count.to_le_bytes());
data.extend_from_slice(¤t_timestamp().to_le_bytes());
let token = blake3::keyed_hash(&self.key, &data);
base64url::encode(&token)
}
pub fn verify_token(&self, token: &str, purpose: &str) -> bool {
// In practice, store issued tokens for verification
// This is a simplified example
true
}
}
Performance Optimization
Parallel Processing
def parallel_merkle_tree(leaves: List[bytes]) -> bytes:
"""Build Merkle tree using BLAKE3's parallelism"""
if not leaves:
return blake3.hash(b"empty-tree")
# First level: hash leaves in parallel
# BLAKE3 automatically uses SIMD/threading
level = [blake3.hash(leaf) for leaf in leaves]
while len(level) > 1:
next_level = []
# Process pairs in parallel
for i in range(0, len(level), 2):
if i + 1 < len(level):
combined = level[i] + level[i + 1]
else:
combined = level[i]
next_level.append(blake3.hash(combined))
level = next_level
return level[0]
Performance Metrics
| Operation | Input Size | Single-thread | Multi-thread | Notes |
|---|---|---|---|---|
| hash() | 1 KB | 200 MB/s | N/A | Small data |
| hash() | 1 MB | 3 GB/s | 8 GB/s | Parallel kicks in |
| hash() | 1 GB | 3 GB/s | 15 GB/s | Full parallelism |
| keyed_hash() | Any | ~Same as hash() | ~Same | Minimal overhead |
| derive_key() | Any | ~Same as hash() | N/A | Single-threaded |
Platform-Specific Security Notes
Python
- Official
blake3package uses Rust backend - Automatic parallelism for large inputs
- GIL released during hashing
Rust
- Use
blake3crate withrayonfeature - Zero-copy hashing with slices
- SIMD automatically enabled
TypeScript/JavaScript
- WASM implementation available
- Node.js native bindings faster
- Browser compatibility varies
Swift
- Use CryptoKit where available
- Custom implementation needed
- Consider performance requirements
Kotlin
- JVM: Use JNI bindings
- Native: Use C implementation
- Multiplatform considerations
Common Integration Patterns
Content-Defined Chunking
def content_defined_chunks(data: bytes, target_size: int = 1024*1024):
"""Split data using BLAKE3 rolling hash"""
window_size = 48
chunks = []
start = 0
hasher = blake3.create_hasher()
for i in range(len(data)):
# Update rolling hash
if i >= window_size:
# BLAKE3 doesn't support rolling, so rehash window
window = data[i-window_size+1:i+1]
h = blake3.hash(window, length=8)
# Check if this is a chunk boundary
threshold = (1 << 64) // target_size
if int.from_bytes(h, 'little') < threshold:
chunks.append(data[start:i+1])
start = i + 1
# Don't forget the last chunk
if start < len(data):
chunks.append(data[start:])
return chunks
Commitment Schemes
pub struct Blake3Commitment {
commitment: [u8; 32],
}
impl Blake3Commitment {
pub fn commit(value: &[u8]) -> (Self, [u8; 32]) {
// Generate random nonce
let nonce: [u8; 32] = random();
// Create commitment
let mut hasher = Blake3::new();
hasher.update(&nonce);
hasher.update(value);
let commitment = hasher.finalize();
(Self { commitment: commitment.into() }, nonce)
}
pub fn verify(&self, value: &[u8], nonce: &[u8; 32]) -> bool {
let mut hasher = Blake3::new();
hasher.update(nonce);
hasher.update(value);
let check = hasher.finalize();
constant_time_eq(&self.commitment, check.as_bytes())
}
}
Security Auditing
Verification Checklist
- Using keyed mode for MACs (not plain hash)
- Keys are exactly 32 bytes from secure RNG
- Domain separation for different uses
- Not using for password hashing
- Output length appropriate for security needs
- Constant-time comparison for verification
Code Review Patterns
# ✅ GOOD: Keyed hash for MAC
mac = blake3.keyed_hash(key, message)
# ❌ BAD: Hash without key for MAC
mac = blake3.hash(key + message)
# ✅ GOOD: Domain separation
key1 = blake3.derive_key("app-v1-encrypt", master)
key2 = blake3.derive_key("app-v1-sign", master)
# ❌ BAD: No domain separation
key1 = blake3.hash(master + b"1")
key2 = blake3.hash(master + b"2")
Security Analysis
Threat Model: BLAKE3 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.
References
Support
Security Issues: security@metamui.id
Documentation Updates: phantom@metamui.id
Vulnerability Disclosure: See SECURITY.md
Document Version: 1.0
Review Cycle: Quarterly
Next Review: 2025-04-05
Classification: PUBLIC