SEA.work()
Perform proof-of-work computations using PBKDF2 (Password-Based Key Derivation Function 2) or compute SHA hashes. Primarily used for password hardening and key derivation.
Syntax
const result = await SEA.work(data, salt)
SEA.work(data, salt, callback)
SEA.work(data, salt, callback, options)
Parameters
- data (string|any): Data to process (password, string, or any JSON-serializable value)
- salt (string|buffer, optional): Salt for PBKDF2 (random if not provided)
- callback (function, optional): Called with result
- options (object, optional):
name: Algorithm name (default: ‘PBKDF2’, or ‘SHA-256’, ‘SHA-1’)
iterations: PBKDF2 iterations (default: 100,000)
length: Output length in bits (default: 512)
hash: Hash algorithm for PBKDF2 (default: SHA-256)
encode: Output encoding (default: ‘base64’)
salt: Alternative way to specify salt
Returns
A Promise that resolves to a base64-encoded string (or custom encoding).
Basic Usage
Proof-of-Work (PBKDF2)
// Hash a password with random salt
const proof = await SEA.work('my-password');
console.log(proof); // Base64-encoded PBKDF2 output
With Specific Salt
const salt = 'random-salt-value';
const proof = await SEA.work('my-password', salt);
SHA-256 Hashing
const hash = await SEA.work('data', null, null, { name: 'SHA-256' });
console.log(hash); // Base64-encoded SHA-256 hash
With Callback
SEA.work('password', 'salt', (proof) => {
console.log('Proof:', proof);
});
How It Works
Reference: ~/workspace/source/sea/work.js:9-40
PBKDF2 Mode (Default)
// 1. Generate random salt (if not provided)
const salt = providedSalt || randomBytes(9);
// 2. Import password as key material
const keyMaterial = await crypto.subtle.importKey(
'raw',
textEncoder.encode(password),
{ name: 'PBKDF2' },
false,
['deriveBits']
);
// 3. Derive bits using PBKDF2
const derived = await crypto.subtle.deriveBits(
{
name: 'PBKDF2',
iterations: 100000,
salt: textEncoder.encode(salt),
hash: 'SHA-256'
},
keyMaterial,
512 // Output 512 bits
);
// 4. Encode as base64
return base64Encode(derived);
SHA Mode
// 1. Hash the data
const hash = await crypto.subtle.digest(
{ name: 'SHA-256' },
textEncoder.encode(data)
);
// 2. Encode as base64
return base64Encode(hash);
Use Cases
Password Hashing
const hashPassword = async (password, salt) => {
salt = salt || SEA.random(9).toString('base64');
const proof = await SEA.work(password, salt);
return {
proof: proof,
salt: salt
};
};
const { proof, salt } = await hashPassword('user-password');
// Store both proof and salt
Password Verification
const verifyPassword = async (password, storedProof, storedSalt) => {
const proof = await SEA.work(password, storedSalt);
return proof === storedProof;
};
const isValid = await verifyPassword('user-input', proof, salt);
Key Derivation for Encryption
// Derive encryption key from password
const deriveEncryptionKey = async (password, salt) => {
return await SEA.work(password, salt, null, {
iterations: 100000,
length: 256 // 256-bit key for AES
});
};
const key = await deriveEncryptionKey('master-password', salt);
const encrypted = await SEA.encrypt(data, key);
Content Hashing
// Create content hash for integrity checking
const contentHash = await SEA.work(content, null, null, {
name: 'SHA-256'
});
store(content, { hash: contentHash });
// Later, verify integrity
const newHash = await SEA.work(retrievedContent, null, null, {
name: 'SHA-256'
});
if (newHash === contentHash) {
console.log('Content integrity verified');
}
User Authentication
This is how SEA uses work() internally for user creation:
Reference: ~/workspace/source/sea/create.js:41-42
// During user creation
const salt = String.random(64);
const proof = await SEA.work(password, salt);
// Proof is used to encrypt private keys
const encrypted = await SEA.encrypt(privateKeys, proof);
// Store salt and encrypted keys
user.put({
auth: JSON.stringify({
ek: encrypted,
s: salt
})
});
Options
Custom Iterations
Increase security (slower) or decrease for faster operations:
// High security (slower)
const proof = await SEA.work('password', salt, null, {
iterations: 500000 // 5x default
});
// Faster (less secure)
const proof = await SEA.work('password', salt, null, {
iterations: 10000
});
Custom Key Length
const proof = await SEA.work('password', salt, null, {
length: 256 // 256 bits instead of default 512
});
Different Hash Functions
// SHA-256 hash
const sha256 = await SEA.work(data, null, null, { name: 'SHA-256' });
// SHA-1 hash (not recommended for security)
const sha1 = await SEA.work(data, null, null, { name: 'SHA-1' });
// SHA-512 (via custom options)
const sha512 = await SEA.work(data, null, null, {
name: 'SHA-512'
});
Custom Encoding
// Hex encoding
const hex = await SEA.work('password', salt, null, {
encode: 'hex'
});
// UTF-8 encoding (not recommended)
const utf8 = await SEA.work('password', salt, null, {
encode: 'utf8'
});
PBKDF2 Details
Algorithm Parameters
Default PBKDF2 configuration:
Reference: ~/workspace/source/sea/settings.js:218
{
name: 'PBKDF2',
iterations: 100000, // 100k iterations
hash: 'SHA-256', // PRF hash function
length: 512 // 512-bit output (64 bytes)
}
Security Level
- Iterations: 100,000 (as of 2024, OWASP recommends 600,000+ for passwords)
- Hash: SHA-256 (PRF - Pseudorandom Function)
- Salt: Minimum 9 bytes random
- Output: 64 bytes (512 bits)
Why PBKDF2?
- Slow by design: Computationally expensive to brute force
- Salting: Prevents rainbow table attacks
- Iteration count: Increases computational cost for attackers
- Standard: NIST approved (SP 800-132)
Timing
const start = Date.now();
const proof = await SEA.work('password', salt);
console.log('Took', Date.now() - start, 'ms');
// Typically 50-200ms depending on device
Iteration Impact
- 10,000 iterations: ~10ms
- 100,000 iterations: ~50-100ms (default)
- 500,000 iterations: ~250-500ms
- 1,000,000 iterations: ~500ms-1s
Higher iteration counts provide more security but increase authentication time. Balance security needs with user experience.
Security Considerations
Salt Requirements
- Uniqueness: Each password should have unique salt
- Randomness: Use cryptographically secure random
- Length: Minimum 8 bytes, 9+ recommended
- Storage: Store salt alongside hashed password
// Good: unique random salt per password
const salt1 = SEA.random(9);
const salt2 = SEA.random(9); // Different salt
// Bad: reusing same salt
const sameSalt = 'fixed-salt'; // Don't do this!
Password Strength
PBKDF2 hardens weak passwords but doesn’t make them strong:
// Still weak
const proof = await SEA.work('password123', salt);
// Better
const proof = await SEA.work('Tr0ub4dor&3', salt);
// Best: high entropy
const proof = await SEA.work('correct-horse-battery-staple', salt);
Rainbow Tables
Salting prevents rainbow table attacks:
// Each user has unique salt
const user1 = await SEA.work('password', salt1);
const user2 = await SEA.work('password', salt2);
// user1 !== user2 (different salts)
Timing Attacks
PBKDF2 is designed to be constant-time for the same iteration count.
Common Patterns
Secure Password Storage
const secureStorePassword = async (password) => {
const salt = SEA.random(16).toString('base64');
const hash = await SEA.work(password, salt, null, {
iterations: 600000 // OWASP 2023 recommendation
});
return {
hash: hash,
salt: salt,
algorithm: 'PBKDF2-SHA256',
iterations: 600000
};
};
Progressive Strengthening
Increase iterations over time:
const currentIterations = 100000;
const targetIterations = 600000;
const rehashIfNeeded = async (password, storedHash, storedIterations) => {
if (storedIterations < targetIterations) {
// Re-hash with higher iteration count
const newHash = await SEA.work(password, salt, null, {
iterations: targetIterations
});
updateStoredHash(newHash, targetIterations);
}
};
Key Stretching
const stretchKey = async (weakKey) => {
// Strengthen a weak key
return await SEA.work(weakKey, null, null, {
iterations: 100000
});
};
const strongKey = await stretchKey('user-input-key');
Data Integrity
const createChecksum = async (data) => {
return await SEA.work(JSON.stringify(data), null, null, {
name: 'SHA-256'
});
};
const checksum = await createChecksum(importantData);
store(importantData, { checksum });
Comparison with Alternatives
vs. bcrypt
- PBKDF2: NIST approved, Web Crypto native, widely supported
- bcrypt: Designed for passwords, memory-hard, not in Web Crypto API
vs. scrypt
- PBKDF2: Less memory-intensive, faster
- scrypt: Memory-hard, better against hardware attacks, not in Web Crypto API
vs. Argon2
- PBKDF2: Older, well-tested, native browser support
- Argon2: Modern, winner of password hashing competition, requires library
Error Handling
try {
const proof = await SEA.work(password, salt);
console.log('Success:', proof);
} catch (err) {
console.error('PBKDF2 failed:', err);
// Possible issues:
// - Web Crypto API not available
// - Invalid parameters
// - Out of memory
}
Browser Compatibility
Requires Web Crypto API support:
- Chrome/Edge 37+
- Firefox 34+
- Safari 11+
- Node.js with
@peculiar/webcrypto polyfill
PBKDF2 requires HTTPS in browsers (Web Crypto API security requirement).