| … | … | @@ -6,7 +6,7 @@ |
| 6 | 6 | * |
| 7 | 7 | * PURPOSE: |
| 8 | 8 | * Password-based encryption for mnemonics at rest |
| 9 | | * Uses scrypt for key derivation and AES-256-GCM for encryption |
| 9 | * Uses PBKDF2 for key derivation and AES-256-GCM for encryption |
| 10 | 10 | * |
| 11 | 11 | * USED BY: |
| 12 | 12 | * - wallet-server.cjs (mnemonic encryption/decryption) |
| … | … | @@ -17,20 +17,21 @@ |
| 17 | 17 | * - decrypt(encrypted, password) - Decrypt with password |
| 18 | 18 | * |
| 19 | 19 | * SECURITY: |
| 20 | | * - scrypt for key derivation (N=16384, r=8, p=1) |
| 20 | * - PBKDF2-SHA256 for key derivation (100k iterations, bare-crypto compatible) |
| 21 | 21 | * - AES-256-GCM authenticated encryption |
| 22 | 22 | * - Random salt and IV per encryption |
| 23 | 23 | * - Auth tag prevents tampering |
| 24 | * |
| 25 | * NOTE: Using PBKDF2 instead of scrypt due to bare-crypto limitations |
| 24 | 26 | */ |
| 25 | 27 | |
| 26 | 28 | const crypto = require('bare-crypto') |
| 27 | 29 | const b4a = require('b4a') |
| 28 | 30 | |
| 29 | | // Scrypt parameters (recommended by RFC 7914) |
| 30 | | const SCRYPT_N = 16384 // CPU/memory cost |
| 31 | | const SCRYPT_R = 8 // Block size |
| 32 | | const SCRYPT_P = 1 // Parallelization |
| 33 | | const SCRYPT_KEYLEN = 32 // 256-bit key |
| 31 | // PBKDF2 parameters (bare-crypto compatible) |
| 32 | const PBKDF2_ITERATIONS = 100000 // NIST recommended minimum |
| 33 | const PBKDF2_KEYLEN = 32 // 256-bit key |
| 34 | const PBKDF2_DIGEST = 'sha256' // Hash algorithm |
| 34 | 35 | |
| 35 | 36 | // AES-GCM parameters |
| 36 | 37 | const IV_LENGTH = 12 // 96-bit IV (recommended for GCM) |
| … | … | @@ -38,27 +39,22 @@ const SALT_LENGTH = 32 // 256-bit salt |
| 38 | 39 | const TAG_LENGTH = 16 // 128-bit auth tag |
| 39 | 40 | |
| 40 | 41 | /** |
| 41 | | * Derive encryption key from password using scrypt |
| 42 | * Derive encryption key from password using PBKDF2 |
| 43 | * (bare-crypto compatible - scrypt not available) |
| 42 | 44 | * |
| 43 | 45 | * @param {string} password - User password |
| 44 | 46 | * @param {Buffer} salt - Random salt |
| 45 | 47 | * @returns {Buffer} - 256-bit derived key |
| 46 | 48 | */ |
| 47 | 49 | function deriveKey(password, salt) { |
| 48 | | // Convert password to buffer |
| 49 | | const passwordBuffer = b4a.from(password, 'utf8') |
| 50 | | |
| 51 | | // Derive key using scrypt |
| 50 | // Derive key using PBKDF2 |
| 52 | 51 | // Note: This is CPU-intensive (intentional security feature) |
| 53 | | const key = crypto.scrypt( |
| 54 | | passwordBuffer, |
| 52 | const key = crypto.pbkdf2Sync( |
| 53 | password, |
| 55 | 54 | salt, |
| 56 | | SCRYPT_KEYLEN, |
| 57 | | { |
| 58 | | N: SCRYPT_N, |
| 59 | | r: SCRYPT_R, |
| 60 | | p: SCRYPT_P |
| 61 | | } |
| 55 | PBKDF2_ITERATIONS, |
| 56 | PBKDF2_KEYLEN, |
| 57 | PBKDF2_DIGEST |
| 62 | 58 | ) |
| 63 | 59 | |
| 64 | 60 | return key |
| … | … | @@ -162,9 +158,9 @@ module.exports = { |
| 162 | 158 | isValidFormat, |
| 163 | 159 | |
| 164 | 160 | // Export constants for testing |
| 165 | | SCRYPT_N, |
| 166 | | SCRYPT_R, |
| 167 | | SCRYPT_P, |
| 161 | PBKDF2_ITERATIONS, |
| 162 | PBKDF2_KEYLEN, |
| 163 | PBKDF2_DIGEST, |
| 168 | 164 | IV_LENGTH, |
| 169 | 165 | SALT_LENGTH, |
| 170 | 166 | TAG_LENGTH |