Voting Service Security Best Practices
Overview
This document provides security guidelines for using the VotingService in production environments. The ECIES-to-Paillier bridge is cryptographically sound, but proper usage is critical for maintaining security.
Key Security Properties
What is Protected
✅ One-way derivation: Cannot recover ECDH keys from Paillier keys
✅ Collision resistance: Different ECDH keys → different Paillier keys
✅ Domain separation: Keys bound to voting purpose via HKDF
✅ Deterministic recovery: Same ECDH keys → same Paillier keys
✅ Semantic security: Paillier encryption is probabilistic
Security Level
- Symmetric equivalent: 128 bits
- ECDH: secp256k1 (Node) / P-256 (Web)
- Paillier: 3072-bit modulus
- NIST compliance: Meets SP 800-57 requirements
Best Practices
1. Key Generation
✅ DO:
// Use cryptographically secure random for initial ECDH key generation
import { randomBytes } from 'crypto';
const seed = randomBytes(32); // 256 bits of entropy
// OR use secure mnemonic generation
const mnemonic = eciesService.generateNewMnemonic();
❌ DON’T:
// Never use weak random sources
const weakSeed = Math.random(); // INSECURE!
// Never use predictable seeds
const predictableSeed = Buffer.from('my-password'); // INSECURE!
2. Private Key Storage
✅ DO:
// Encrypt private keys before storing
const votingService = VotingService.getInstance();
const privateKeyBuffer = votingService.votingPrivateKeyToBuffer(privateKey);
// Encrypt with another key before storage
const encryptedKey = await eciesService.encrypt(
privateKeyBuffer,
storagePublicKey
);
// Store encrypted version only
await secureStorage.save(encryptedKey);
❌ DON’T:
// Never store private keys in plaintext
localStorage.setItem('privateKey', privateKeyBuffer.toString('hex')); // INSECURE!
// Never log private keys
console.log('Private key:', privateKey); // INSECURE!
// Never transmit private keys over insecure channels
fetch('http://example.com/keys', { // INSECURE!
method: 'POST',
body: JSON.stringify({ privateKey })
});
3. Memory Management
✅ DO:
// Clear sensitive data from memory when done
import { SecureBuffer } from '@digitaldefiance/node-ecies-lib';
const privateKeyBuffer = new SecureBuffer(privateKeyBytes);
try {
// Use the key
await doSomethingWithKey(privateKeyBuffer);
} finally {
// Always clear
privateKeyBuffer.dispose();
}
❌ DON’T:
// Don't keep private keys in memory longer than needed
class MyApp {
private privateKey: PrivateKey; // Persists in memory!
// Better: Load only when needed, clear immediately after
}
4. Key Rotation
✅ DO:
// Rotate keys periodically
const KEY_ROTATION_INTERVAL = 90 * 24 * 60 * 60 * 1000; // 90 days
if (Date.now() - member.dateCreated.getTime() > KEY_ROTATION_INTERVAL) {
// Generate new member with fresh keys
const { member: newMember, mnemonic } = Member.newMember(
type,
name,
email
);
// Migrate data to new keys
await migrateVotesToNewKeys(oldMember, newMember);
// Securely delete old keys
oldMember.dispose();
}
5. Timing Attack Mitigation
✅ DO:
// Use constant-time comparison for sensitive values
function constantTimeEqual(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;
}
⚠️ NOTE:
The voting service includes timing attack mitigations:
- Fixed iteration count for prime generation (10,000 attempts)
- Constant-time completion (continues after finding prime)
- Input validation to prevent information leakage
6. Input Validation
✅ DO:
// Always validate inputs before key derivation
function deriveVotingKeys(ecdhPrivKey: Uint8Array, ecdhPubKey: Uint8Array) {
// Validate key lengths
if (ecdhPrivKey.length !== 32) {
throw new Error('Invalid private key length');
}
if (ecdhPubKey.length !== 64 && ecdhPubKey.length !== 65) {
throw new Error('Invalid public key length');
}
// Validate key is on curve (if possible)
// ... curve validation ...
return votingService.deriveVotingKeysFromECDH(ecdhPrivKey, ecdhPubKey);
}
7. Error Handling
✅ DO:
// Handle errors without leaking information
try {
const keys = deriveVotingKeysFromECDH(privKey, pubKey);
} catch (error) {
// Log minimal information
logger.error('Key derivation failed', {
timestamp: Date.now(),
// Don't log keys or sensitive details!
});
// Return generic error to user
throw new Error('Key derivation failed');
}
❌ DON’T:
// Don't expose internal details
catch (error) {
throw new Error(`Failed with keys: ${privKey} ${pubKey}`); // INSECURE!
}
8. Testing in Production
✅ DO:
// Use test vectors for validation
const TEST_VECTORS = {
ecdhPrivKey: Buffer.from('...known test key...'),
ecdhPubKey: Buffer.from('...known test key...'),
expectedPaillierN: BigInt('...expected value...')
};
// Verify implementation matches expected behavior
const result = deriveVotingKeysFromECDH(
TEST_VECTORS.ecdhPrivKey,
TEST_VECTORS.ecdhPubKey
);
if (result.publicKey.n !== TEST_VECTORS.expectedPaillierN) {
throw new Error('Implementation changed! Do not deploy!');
}
9. Homomorphic Operation Security
✅ DO:
// Verify ballots are properly encrypted
function submitVote(vote: bigint, votingPublicKey: PublicKey) {
// Encrypt vote
const encryptedVote = votingPublicKey.encrypt(vote);
// Verify encryption is non-deterministic (different each time)
const testEncryption = votingPublicKey.encrypt(vote);
if (encryptedVote === testEncryption) {
throw new Error('Encryption is deterministic - SECURITY ISSUE!');
}
return encryptedVote;
}
⚠️ REMEMBER:
- Paillier encryption is probabilistic (uses random r)
- Same vote → different ciphertexts (semantic security)
- Homomorphic addition is safe: E(a) + E(b) = E(a+b)
- Never reuse randomness between encryptions
10. Web-Specific Security
✅ DO:
// Use Content Security Policy
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-eval'">
// Store keys in IndexedDB, not localStorage
const db = await openDB('voting-keys', 1);
await db.put('keys', encryptedKeys, 'voting');
// Use HTTPS only
if (window.location.protocol !== 'https:') {
throw new Error('Voting requires HTTPS');
}
❌ DON’T:
// Never use localStorage for keys
localStorage.setItem('votingKeys', JSON.stringify(keys)); // INSECURE!
// Never allow HTTP
if (window.location.protocol === 'http:') {
// Still proceed... // INSECURE!
}
Threat Model
What We Protect Against
- Factorization Attacks: 3072-bit modulus resists GNFS
- Weak Primes: Miller-Rabin with 256 rounds (error < 2^-512)
- Collision Attacks: Birthday bound requires ~2^128 operations
- Timing Attacks: Constant-time operations where possible
- Key Recovery: One-way derivation (HKDF, ECDH hardness)
What We Cannot Protect Against
- Quantum Attacks: Shor’s algorithm breaks RSA-type systems
- Mitigation: Monitor post-quantum crypto developments
- Timeline: Practical quantum computers 10+ years away
- Side-Channel Attacks: Power analysis, cache timing, etc.
- Mitigation: Use hardware security modules (HSM) for production
- Mitigation: Constant-time crypto libraries
- Insider Threats: Compromised private keys
- Mitigation: Multi-signature schemes, key splitting
- Mitigation: Hardware security, key ceremonies
- Implementation Bugs: Software vulnerabilities
- Mitigation: Regular security audits
- Mitigation: Formal verification (future work)
Production Checklist
Before deploying to production:
- Use cryptographically secure random for ECDH key generation
- Encrypt private keys before storage
- Implement key rotation policy (90-180 days)
- Use HTTPS/TLS for all communications
- Implement proper error handling (no information leakage)
- Add security monitoring and logging
- Conduct security audit of integration code
- Test with NIST test vectors
- Implement rate limiting for key derivation
- Set up key backup and recovery procedures
- Document incident response procedures
- Train operators on security procedures
- Consider HSM for production key storage
- Implement multi-signature for critical operations
Monitoring and Logging
What to Log (Safe)
✅ Timestamp of key derivation
✅ Number of votes encrypted
✅ Failed authentication attempts
✅ Key rotation events
✅ Error types (without sensitive details)
What NOT to Log (Sensitive)
❌ Private keys (any part)
❌ ECDH private keys
❌ Paillier private keys (lambda, mu)
❌ Decrypted vote values
❌ Intermediate cryptographic values
Incident Response
If Private Key Compromised
- Immediate Actions:
- Revoke compromised keys
- Generate new keys for affected members
- Notify affected parties
- Invalidate affected votes/data
- Investigation:
- Determine scope of compromise
- Identify attack vector
- Assess data exposure
- Recovery:
- Migrate to new keys
- Implement additional controls
- Update security procedures
- Post-Incident:
- Security audit
- Update threat model
- Training and awareness
Resources
- Security Analysis:
docs/SECURITY_ANALYSIS_ECIES_PAILLIER_BRIDGE.md - Test Suite:
voting.service.spec.ts - NIST Standards:
- SP 800-57 (Key Management)
- SP 800-90A (DRBG)
- FIPS 186-4 (DSS)
- RFCs:
- RFC 5869 (HKDF)
- RFC 6979 (Deterministic ECDSA)
Support
For security-related questions or to report vulnerabilities:
- Email: security@digitaldefiance.io
- Security Policy:
SECURITY.md - Bug Bounty: [To be announced]
Document Version: 1.0
Last Updated: December 25, 2025
Next Review: March 25, 2026