BIP85KMS is a proof-of-concept deterministic key management service. It prioritizes:
- Reproducibility: Same inputs always produce same keys
- Simplicity: Minimal moving parts, easy to audit
- Transparency: No hidden key storage, all derivation is deterministic
However, it intentionally omits many security features required for production use:
- No authentication or authorization
- No rate limiting or abuse protection
- No audit logging
- No key revocation mechanisms
- No defense against side-channel attacks
Use this project to learn about deterministic key derivation. Do not use it in production without significant security hardening.
-
Key Storage Vulnerabilities
- Threat: Database breach exposing stored keys
- Protection: No keys are stored; all are derived on-demand from a single mnemonic
-
Key Synchronization Issues
- Threat: Keys getting out of sync across systems
- Protection: Deterministic derivation ensures identical keys everywhere
-
Key Backup Complexity
- Threat: Complex key management requiring multiple backups
- Protection: Single mnemonic backup secures all derived keys
-
Mnemonic Compromise
- If the master mnemonic is exposed, all derived keys are compromised
- There is no mechanism to revoke or rotate individual keys
- Mitigation requires generating a new mnemonic and re-encrypting all data
-
Unauthorized API Access
- No authentication means anyone with the endpoint can request keys
- An attacker can enumerate all possible keys by trying different parameters
- Critical: Must implement authentication before any production use
-
Replay Attacks
- No request uniqueness validation (nonces, timestamps)
- An attacker can replay captured requests to retrieve the same keys
- Mitigation requires request signing or authentication tokens
-
Side-Channel Attacks
- No protection against timing attacks, power analysis, or cache attacks
- Key derivation operations may leak information through timing
- Not suitable for environments where attackers have physical access
-
Denial of Service
- No rate limiting allows resource exhaustion
- Cloudflare Workers have some built-in DDoS protection, but not application-level
-
Man-in-the-Middle Attacks
- While Cloudflare Workers use HTTPS, no certificate pinning or extra validation
- Rely on TLS/SSL for transport security
- Trusted Execution Environment: The Cloudflare Worker runtime is trusted
- TLS Security: HTTPS/TLS provides adequate transport encryption
- Mnemonic Secrecy: The
MNEMONIC_SECRETenvironment variable is kept secret - Client Security: Clients requesting keys are trusted (no malicious clients)
Attack: An attacker obtains the MNEMONIC_SECRET through:
- Cloudflare account compromise
- Insider threat
- Backup exposure
- Code repository leak (if hardcoded)
Impact: CRITICAL - All past, present, and future keys are compromised
Mitigation:
- Store mnemonic in Cloudflare Workers secrets (encrypted at rest)
- Never commit mnemonic to version control
- Use hardware security modules (HSMs) for mnemonic storage in high-security scenarios
- Implement key rotation strategy requiring re-encryption of all data
- Consider multi-party computation (MPC) for mnemonic splits
Recovery:
- Generate new mnemonic immediately
- Deploy new Worker with new mnemonic
- Re-encrypt all data with newly derived keys
- Revoke access to old endpoint
- Audit logs to determine scope of exposure
Attack: An attacker discovers the Worker endpoint and requests private keys
Impact: HIGH - Attacker can decrypt specific files if they know parameters
Current Vulnerability: No authentication, any requester can set getPrivateKey: true
Mitigation Options:
Option A: Mutual TLS (mTLS)
// Validate client certificate
if (!request.cf?.tlsClientAuth?.certVerified) {
return new Response("Unauthorized", { status: 401 });
}Option B: JWT Authentication
import { verify } from '@tsndr/cloudflare-worker-jwt';
async function validateJWT(request: Request): Promise<boolean> {
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) return false;
const token = authHeader.substring(7);
return await verify(token, env.JWT_SECRET);
}Option C: HMAC Request Signing
import { hmac } from '@noble/hashes/hmac';
import { sha256 } from '@noble/hashes/sha256';
function verifyRequestSignature(request: Request, body: string, signature: string): boolean {
const expectedSig = hmac(sha256, env.HMAC_SECRET, body);
const providedSig = hexToBytes(signature);
return timingSafeEqual(expectedSig, providedSig);
}Option D: IP Allowlisting
const ALLOWED_IPS = ['192.168.1.0/24', '10.0.0.0/8'];
function isAllowedIP(ip: string): boolean {
return ALLOWED_IPS.some(range => ipInRange(ip, range));
}Option E: Cloudflare Access
- Configure Cloudflare Access for identity-based authentication
- Integrates with IdP (Google, GitHub, Okta, etc.)
- Provides audit logs and session management
Attack: Attacker systematically requests keys for all possible parameter combinations
Impact: MEDIUM to HIGH - Depends on keyspace size and attacker's knowledge
Current Vulnerability: No rate limiting, unlimited requests allowed
Mitigation:
Rate Limiting with Durable Objects:
// Track requests per IP
const rateLimiter = await env.RATE_LIMITER.get(clientIP);
if (rateLimiter.isExceeded()) {
return new Response("Too Many Requests", { status: 429 });
}Cloudflare Rate Limiting Rules:
- Configure at Cloudflare Dashboard
- Limit requests per IP or per endpoint
- Apply progressive delays or blocking
Application-Level Throttling:
// Add artificial delay to slow brute force
await new Promise(resolve => setTimeout(resolve, 100));Context: BIP85KMS uses filename-based IV derivation
IV Derivation: sha256(filename)[:12]
This is intentional and correct for the following reasons:
- Consistency with core model: All derived material comes from filename
- Pre-allocation support: IV can be derived before file exists
- Age compatibility: Age encryption (primary use case) handles nonces internally, so this IV is auxiliary
For AES-GCM users:
IV reuse with the same key breaks AES-GCM security. Since same filename = same IV:
| Scenario | Risk | Mitigation |
|---|---|---|
| Same file content, re-encrypted with same keyVersion | Low | Same ciphertext (acceptable for unchanged content) |
| Different content, same filename with same keyVersion | Medium | Increment keyVersion to get new key |
Recommendation: Use Age encryption for all file encryption. Age correctly handles nonce generation internally.
Security Implications:
The current implementation uses:
iv: bufferToHex(sha256(filename).slice(0, 12))This means:
- Same filename always produces same IV
- IV is not a secret (derived from known filename)
- Pattern analysis could reveal which ciphertexts correspond to same files
When Deterministic IVs Are Acceptable:
- File content changes between encryptions (different data each time)
- Content-addressable encryption (deterministic by design)
- Use with stream ciphers where IV uniqueness is not critical (with proper key rotation)
Important Notes:
-
Identical Plaintext Detection: If the same file content is encrypted multiple times with the same key and IV, the ciphertexts will be identical, revealing that the same data was encrypted.
-
Chosen-Plaintext Attacks: If an attacker can influence the plaintext and observe ciphertexts, deterministic IVs may leak information.
-
Not Recommended for AES-GCM: Modern AEAD schemes like AES-GCM require unique IVs for each encryption with the same key. Reusing IVs catastrophically breaks security.
Mitigation Strategies:
For Age encryption: Age handles nonce generation internally, the iv field is for auxiliary use.
For symmetric encryption (AES, ChaCha20):
- Option 1: Include a random component in the filename (e.g.,
file-{uuid}.txt) - Option 2: Append a counter or timestamp to ensure unique IVs
- Option 3: Use a different IV derivation:
SHA-256(filename || keyVersion || timestamp) - Option 4: Increment
keyVersionwhen content changes to get a new key
Attack: Attacker accesses keys without detection
Impact: MEDIUM - No forensic evidence of compromise
Current State: No logging implemented
Recommended Logging Strategy:
What TO Log:
interface AuditLog {
timestamp: string;
clientIP: string;
requestId: string;
appId: string;
filename: string; // May need redaction depending on sensitivity
keyVersion: number;
requestedPrivateKey: boolean;
responseStatus: number;
userAgent?: string;
}What NOT TO Log:
- Never log the mnemonic
- Never log derived private keys
- Never log raw entropy
- Be careful with filename if it contains PII
Implementation Options:
Cloudflare Logpush:
// Worker automatically logs to Cloudflare
// Access via Dashboard → Analytics → LogsExternal Logging Service:
await fetch('https://logs.example.com/api/events', {
method: 'POST',
body: JSON.stringify(auditLog),
headers: { 'Authorization': `Bearer ${env.LOG_TOKEN}` }
});Durable Objects for Log Storage:
const logStore = await env.AUDIT_LOGS.get(logId);
await logStore.append(auditLog);24-word mnemonic:
- Entropy: 256 bits
- Possible combinations: 2^256 ≈ 1.16 × 10^77
- Verdict: Cryptographically secure if generated properly
12-word mnemonic:
- Entropy: 128 bits
- Possible combinations: 2^128 ≈ 3.4 × 10^38
- Verdict: Adequate for most use cases, secure for non-quantum adversaries
Recommendation: Use 24-word mnemonics for high-security applications.
Algorithm: BIP32 uses HMAC-SHA512 for child key derivation
Hardened Derivation: All indexes use hardened derivation (')
- Prevents parent public key from deriving child keys
- Necessary when exposing public keys
Security Properties:
- Forward security: Parent key compromise doesn't reveal child keys
- Backward security: Child key compromise doesn't reveal parent or sibling keys (with hardened derivation)
Process:
- Derive child at
m/83696968'/{index}' - SHA-256 hash the private key
Security:
- Entropy quality: As strong as SHA-256 (256-bit collision resistance)
- Deterministic: Same index always produces same entropy
- Index isolation: Different indexes produce independent entropy
Concern: Current implementation only uses keyVersion (indexes[0]) for entropy
appIdandfilenameonly affect derivation path string, not entropy- Same
keyVersionproduces same entropy across different files/apps - Recommendation: Incorporate all three parameters into entropy derivation
Algorithm: X25519 (Curve25519) for ECDH
Key Generation:
const rawSecret = hmac(sha256, entropy, indexBytes); // 32-byte secret scalar
const pubBytes = x25519.getPublicKey(rawSecret); // Curve25519 pointSecurity Properties:
- X25519 provides ~128-bit security against quantum computers (best known attacks)
- ~256-bit security against classical computers
- Key indistinguishability: Derived keys are computationally indistinguishable from random
Age Specification: https://age-encryption.org/v1
Single Mnemonic Design:
- All applications share the same root mnemonic
- Isolation achieved through different derivation paths
- If mnemonic is compromised, all applications are compromised
Isolation Levels:
-
Application-level: Different
appIdvalues- Derivation paths differ in the
appIdHashcomponent - Keys are cryptographically independent
- Limitation: Same entropy if same
keyVersionis used
- Derivation paths differ in the
-
File-level: Different
filenamevalues- Derivation paths differ in
filenameHashcomponent - Limitation: Same entropy if same
keyVersionis used
- Derivation paths differ in
-
Version-level: Different
keyVersionvalues- Actually affects entropy derivation
- Best practice: Use different versions for true key isolation
If you need to support multiple tenants or isolated environments:
Option 1: Multiple Workers
- Deploy separate Workers with different mnemonics
- Each tenant gets their own Worker instance
- Pros: Complete isolation, independent security boundaries
- Cons: Management overhead, higher costs
Option 2: Mnemonic-per-Tenant Storage
// Store mnemonics in KV or Durable Objects
const mnemonic = await env.TENANT_MNEMONICS.get(tenantId);
const result = deriveFromMnemonic(mnemonic, keyVersion, appId, filename);- Pros: Single Worker, logical isolation
- Cons: More complex, mnemonic storage security critical
Option 3: Tenant-specific Key Derivation
// Include tenantId in derivation
const tenantHash = sha256(new TextEncoder().encode(tenantId));
const tenantIndex = intFromBytes(tenantHash.slice(0, 4)) & 0x7fffffff;
// Derive tenant-specific master node
const tenantNode = masterNode.derive(`m/${tenantIndex}'`);
// Then derive keys from tenantNode- Pros: Single mnemonic, cryptographic isolation
- Cons: All tenants compromised if mnemonic leaks
Before deploying to production, implement the following:
- Implement client authentication (mTLS, JWT, HMAC, or Access)
- Restrict
getPrivateKey: trueto authorized clients only - Consider separate endpoints for public vs private key retrieval
- Implement role-based access control (RBAC)
- Implement rate limiting per IP address
- Implement rate limiting per appId/tenant
- Set up Cloudflare rate limiting rules
- Add exponential backoff for failed requests
- Implement comprehensive audit logging
- Set up alerting for suspicious patterns:
- High request volumes
- Multiple
getPrivateKeyrequests - Requests from unusual geographic locations
- Failed authentication attempts
- Log to secure, append-only storage
- Implement log retention policies
- Document mnemonic backup procedures
- Implement mnemonic rotation strategy
- Store mnemonic in secure key management service (not just Workers secrets)
- Consider multi-party computation (MPC) for mnemonic splits
- Test disaster recovery procedures
- Configure Content Security Policy (CSP)
- Set strict CORS policies
- Implement request size limits
- Add security headers (HSTS, X-Frame-Options, etc.)
- Run security audit (SAST/DAST)
- Enable dependency scanning
- Implement input validation and sanitization
- Use timing-safe comparison for secrets
- Review and test error handling (avoid information leakage)
- Implement health checks and monitoring
- Set up incident response procedures
- Document security policies and procedures
- Regular security reviews and penetration testing
- Principle of least privilege for Worker permissions
-
No Authentication on Private Key Endpoint
- Risk: Anyone can request private keys
- Status: By design for PoC, must fix for production
- Mitigation: Implement authentication (see Scenario 2)
-
Single Point of Failure (Mnemonic)
- Risk: Mnemonic compromise exposes all keys forever
- Status: Architectural limitation
- Mitigation: Secure mnemonic storage, consider HSM or MPC
-
No Rate Limiting
- Risk: Brute-force enumeration, DoS attacks
- Status: Not implemented
- Mitigation: Implement rate limiting
-
No Audit Logging
- Risk: No forensic evidence of attacks
- Status: Not implemented
- Mitigation: Implement comprehensive logging
-
Key Entropy Limited to keyVersion
- Risk: Same keyVersion produces same entropy across files/apps
- Status: Design limitation requiring code change
- Mitigation: Modify
deriveKeyAndIVto incorporate all parameters
-
Deterministic IV Generation
- Risk: Information leakage in specific scenarios
- Status: By design, has trade-offs
- Mitigation: Document limitations, recommend Age (which handles nonces internally)
-
No Request Replay Protection
- Risk: Captured requests can be replayed
- Status: No nonce or timestamp validation
- Mitigation: Implement request signing with nonces
Acceptable:
- No authentication (if running locally)
- Demo mnemonics
- No audit logging
- Public
getPrivateKeyaccess
Still Recommended:
- Use HTTPS even in development
- Never commit mnemonics to git
- Test authentication implementations
Required:
- Authentication implementation
- Unique mnemonic (not demo)
- Basic audit logging
- Rate limiting
Recommended:
- Monitor for anomalies
- Test disaster recovery
- Security scanning
Absolutely Required:
- Strong authentication and authorization
- Secure mnemonic storage (secrets management service or HSM)
- Comprehensive audit logging with alerting
- Rate limiting and DoS protection
- Regular security audits
- Incident response plan
- Backup and recovery procedures tested
Strongly Recommended:
- Multi-factor authentication for administrative access
- Penetration testing
- Bug bounty program
- Regular threat model reviews
- Security training for team
-
Never Reuse Keys Across Security Boundaries
- Use different
appIdfor different applications - Use different
keyVersionfor key rotation
- Use different
-
Properly Handle Key Material
- Never log private keys
- Clear keys from memory after use (if possible in your language)
- Use secure key storage on clients (keychain, TPM)
-
Use Age Correctly
- Age handles nonces/IVs internally, use its built-in security
- Don't roll your own encryption schemes
- Follow Age best practices: https://age-encryption.org/
-
Validate Derived Keys
- Verify public key matches private key
- Check derivation path matches expectations
- Detect key rollover/rotation
-
Use Constant-Time Comparisons
function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean { if (a.length !== b.length) return false; let result = 0; for (let i = 0; i < a.length; i++) { result |= a[i] ^ b[i]; } return result === 0; }
-
Validate All Inputs
if (typeof keyVersion !== 'number' || keyVersion < 0 || keyVersion > 0x7fffffff) { throw new Error('Invalid keyVersion'); }
-
Fail Securely
- Generic error messages (avoid leaking derivation details)
- Return early on authentication failures
- Log security-relevant events
-
Keep Dependencies Updated
- Monitor for vulnerabilities in crypto libraries
- Use
npm auditregularly - Pin versions but update regularly
BIP85KMS demonstrates powerful deterministic key derivation concepts but lacks critical security features for production use. It serves as:
- ✅ Educational tool for learning about BIP39/BIP32/BIP85
- ✅ Reference implementation for deterministic key derivation
- ✅ Prototype for key management architectures
- ❌ Production KMS without significant security hardening
Bottom Line: Understand the risks, implement the recommended mitigations, and conduct thorough security review before any production deployment.
- BIP39: Mnemonic code for generating deterministic keys
- BIP32: Hierarchical Deterministic Wallets
- BIP85: Deterministic Entropy From BIP32 Keychains
- Age Encryption Specification
- NIST SP 800-57: Key Management Recommendations
- OWASP API Security Top 10
Document Version: 1.0
Last Updated: 2026-02-16
Status: Initial Release