diff --git a/.prettierignore b/.prettierignore index 600ac94ee..0069b6a62 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,4 @@ src/wallet.ts -src/encryption/wallet.ts src/encryption/index.ts src/encryption/ec.ts src/errors.ts diff --git a/src/encryption/wallet.ts b/src/encryption/wallet.ts index d70baefb4..75de463c6 100644 --- a/src/encryption/wallet.ts +++ b/src/encryption/wallet.ts @@ -1,10 +1,10 @@ -import { validateMnemonic, mnemonicToEntropy, entropyToMnemonic } from 'bip39' -import { randomBytes, GetRandomBytes } from './cryptoRandom' -import { createSha2Hash } from './sha2Hash' -import { createHmacSha256 } from './hmacSha256' -import { createCipher } from './aesCipher' -import { createPbkdf2 } from './pbkdf2' -import { TriplesecDecryptSignature } from './cryptoUtils' +import { validateMnemonic, mnemonicToEntropy, entropyToMnemonic } from 'bip39'; +import { randomBytes, GetRandomBytes } from './cryptoRandom'; +import { createSha2Hash } from './sha2Hash'; +import { createHmacSha256 } from './hmacSha256'; +import { createCipher } from './aesCipher'; +import { createPbkdf2 } from './pbkdf2'; +import { TriplesecDecryptSignature } from './cryptoUtils'; /** * Encrypt a raw mnemonic phrase to be password protected @@ -12,115 +12,132 @@ import { TriplesecDecryptSignature } from './cryptoUtils' * @param {string} password - Password to encrypt mnemonic with * @return {Promise} The encrypted phrase * @private - * @ignore + * @ignore * */ -export async function encryptMnemonic(phrase: string, password: string, opts?: { - getRandomBytes?: GetRandomBytes -}): Promise { +export async function encryptMnemonic( + phrase: string, + password: string, + opts?: { + getRandomBytes?: GetRandomBytes; + } +): Promise { // hex encoded mnemonic string - let mnemonicEntropy: string + let mnemonicEntropy: string; try { // must be bip39 mnemonic - mnemonicEntropy = mnemonicToEntropy(phrase) + mnemonicEntropy = mnemonicToEntropy(phrase); } catch (error) { - console.error('Invalid mnemonic phrase provided') - console.error(error) - throw new Error('Not a valid bip39 mnemonic') + console.error('Invalid mnemonic phrase provided'); + console.error(error); + throw new Error('Not a valid bip39 mnemonic'); } - + // normalize plaintext to fixed length byte string - const plaintextNormalized = Buffer.from(mnemonicEntropy, 'hex') + const plaintextNormalized = Buffer.from(mnemonicEntropy, 'hex'); // AES-128-CBC with SHA256 HMAC - const pbkdf2 = await createPbkdf2() - let salt: Buffer + const pbkdf2 = await createPbkdf2(); + let salt: Buffer; if (opts && opts.getRandomBytes) { - salt = opts.getRandomBytes(16) + salt = opts.getRandomBytes(16); } else { - salt = randomBytes(16) + salt = randomBytes(16); } - const keysAndIV = await pbkdf2.derive(password, salt, 100000, 48, 'sha512') - const encKey = keysAndIV.slice(0, 16) - const macKey = keysAndIV.slice(16, 32) - const iv = keysAndIV.slice(32, 48) - - const cipher = await createCipher() - const cipherText = await cipher.encrypt('aes-128-cbc', encKey, iv, plaintextNormalized) - - const hmacPayload = Buffer.concat([salt, cipherText]) - const hmacSha256 = await createHmacSha256() - const hmacDigest = await hmacSha256.digest(macKey, hmacPayload) - - const payload = Buffer.concat([salt, hmacDigest, cipherText]) - return payload + const keysAndIV = await pbkdf2.derive(password, salt, 100000, 48, 'sha512'); + const encKey = keysAndIV.slice(0, 16); + const macKey = keysAndIV.slice(16, 32); + const iv = keysAndIV.slice(32, 48); + + const cipher = await createCipher(); + const cipherText = await cipher.encrypt( + 'aes-128-cbc', + encKey, + iv, + plaintextNormalized + ); + + const hmacPayload = Buffer.concat([salt, cipherText]); + const hmacSha256 = await createHmacSha256(); + const hmacDigest = await hmacSha256.digest(macKey, hmacPayload); + + const payload = Buffer.concat([salt, hmacDigest, cipherText]); + return payload; } // Used to distinguish bad password during decrypt vs invalid format -class PasswordError extends Error { } +class PasswordError extends Error {} /** -* @ignore -*/ -async function decryptMnemonicBuffer(dataBuffer: Buffer, password: string): Promise { - const salt = dataBuffer.slice(0, 16) - const hmacSig = dataBuffer.slice(16, 48) // 32 bytes - const cipherText = dataBuffer.slice(48) - const hmacPayload = Buffer.concat([salt, cipherText]) - - const pbkdf2 = await createPbkdf2() - const keysAndIV = await pbkdf2.derive(password, salt, 100000, 48, 'sha512') - const encKey = keysAndIV.slice(0, 16) - const macKey = keysAndIV.slice(16, 32) - const iv = keysAndIV.slice(32, 48) - - const decipher = await createCipher() - const decryptedResult = await decipher.decrypt('aes-128-cbc', encKey, iv, cipherText); - - const hmacSha256 = await createHmacSha256() - const hmacDigest = await hmacSha256.digest(macKey, hmacPayload) + * @ignore + */ +async function decryptMnemonicBuffer( + dataBuffer: Buffer, + password: string +): Promise { + const salt = dataBuffer.slice(0, 16); + const hmacSig = dataBuffer.slice(16, 48); // 32 bytes + const cipherText = dataBuffer.slice(48); + const hmacPayload = Buffer.concat([salt, cipherText]); + + const pbkdf2 = await createPbkdf2(); + const keysAndIV = await pbkdf2.derive(password, salt, 100000, 48, 'sha512'); + const encKey = keysAndIV.slice(0, 16); + const macKey = keysAndIV.slice(16, 32); + const iv = keysAndIV.slice(32, 48); + + const decipher = await createCipher(); + const decryptedResult = await decipher.decrypt( + 'aes-128-cbc', + encKey, + iv, + cipherText + ); + + const hmacSha256 = await createHmacSha256(); + const hmacDigest = await hmacSha256.digest(macKey, hmacPayload); // hash both hmacSig and hmacDigest so string comparison time // is uncorrelated to the ciphertext - const sha2Hash = await createSha2Hash() - const hmacSigHash = await sha2Hash.digest(hmacSig) - const hmacDigestHash = await sha2Hash.digest(hmacDigest) + const sha2Hash = await createSha2Hash(); + const hmacSigHash = await sha2Hash.digest(hmacSig); + const hmacDigestHash = await sha2Hash.digest(hmacDigest); if (!hmacSigHash.equals(hmacDigestHash)) { // not authentic - throw new PasswordError('Wrong password (HMAC mismatch)') + throw new PasswordError('Wrong password (HMAC mismatch)'); } - let mnemonic: string + let mnemonic: string; try { - mnemonic = entropyToMnemonic(decryptedResult) + mnemonic = entropyToMnemonic(decryptedResult); } catch (error) { - console.error('Error thrown by `entropyToMnemonic`') - console.error(error) - throw new PasswordError('Wrong password (invalid plaintext)') + console.error('Error thrown by `entropyToMnemonic`'); + console.error(error); + throw new PasswordError('Wrong password (invalid plaintext)'); } if (!validateMnemonic(mnemonic)) { - throw new PasswordError('Wrong password (invalid plaintext)') + throw new PasswordError('Wrong password (invalid plaintext)'); } - return mnemonic + return mnemonic; } - /** * Decrypt legacy triplesec keys * @param {Buffer} dataBuffer - The encrypted key * @param {String} password - Password for data * @return {Promise} Decrypted seed * @private - * @ignore + * @ignore */ -function decryptLegacy(dataBuffer: Buffer, - password: string, - triplesecDecrypt: TriplesecDecryptSignature +function decryptLegacy( + dataBuffer: Buffer, + password: string, + triplesecDecrypt: TriplesecDecryptSignature ): Promise { return new Promise((resolve, reject) => { if (!triplesecDecrypt) { - reject(new Error('The `triplesec.decrypt` function must be provided')) + reject(new Error('The `triplesec.decrypt` function must be provided')); } triplesecDecrypt( { @@ -129,34 +146,34 @@ function decryptLegacy(dataBuffer: Buffer, }, (err, plaintextBuffer) => { if (!err) { - resolve(plaintextBuffer) + resolve(plaintextBuffer); } else { - reject(err) + reject(err); } } - ) - }) + ); + }); } /** - * Decrypt an encrypted mnemonic phrase with a password. - * Legacy triplesec encrypted payloads are also supported. + * Decrypt an encrypted mnemonic phrase with a password. + * Legacy triplesec encrypted payloads are also supported. * @param data - Buffer or hex-encoded string of the encrypted mnemonic * @param password - Password for data * @return the raw mnemonic phrase * @private - * @ignore + * @ignore */ export async function decryptMnemonic( data: string | Buffer, - password: string, + password: string, triplesecDecrypt?: TriplesecDecryptSignature ) { const dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'hex'); try { return await decryptMnemonicBuffer(dataBuffer, password); } catch (err) { - if (err instanceof PasswordError) { + if (err instanceof PasswordError) { throw err; } const data = await decryptLegacy(dataBuffer, password, triplesecDecrypt);