diff --git a/dist/index.js b/dist/index.js index c681337..eb073b4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -10,14 +10,19 @@ const STRING_ENCODING = 'utf-8'; * @param {R} dataObj - data to encrypt * @returns {Promise} cypher text */ -async function encrypt(password, dataObj) { - const salt = generateSalt(); - const passwordDerivedKey = await keyFromPassword(password, salt); - const payload = await encryptWithKey(passwordDerivedKey, dataObj); +async function encrypt(password, dataObj, key, salt = generateSalt()) { + const cryptoKey = key || (await keyFromPassword(password, salt)); + const payload = await encryptWithKey(cryptoKey, dataObj); payload.salt = salt; - const extractedKeyString = await exportKey(passwordDerivedKey); + return JSON.stringify(payload); +} +async function encryptWithDetail(password, dataObj) { + const salt = generateSalt(); + const key = await keyFromPassword(password, salt); + const extractedKeyString = await exportKey(key); + const vault = await encrypt(password, dataObj, key, salt); return { - vault: JSON.stringify(payload), + vault, extractedKeyString, }; } @@ -52,12 +57,19 @@ async function encryptWithKey(key, dataObj) { * @param {string} text - cypher text to decrypt * @returns {DecryptResult} */ -async function decrypt(password, text) { +async function decrypt(password, text, key) { + const payload = JSON.parse(text); + const { salt } = payload; + const cryptoKey = key || (await keyFromPassword(password, salt)); + const result = await decryptWithKey(cryptoKey, payload); + return result; +} +async function decryptWithDetail(password, text) { const payload = JSON.parse(text); const { salt } = payload; const key = await keyFromPassword(password, salt); const extractedKeyString = await exportKey(key); - const vault = await decryptWithKey(key, payload); + const vault = decrypt(password, text, key); return { extractedKeyString, vault, @@ -66,7 +78,8 @@ async function decrypt(password, text) { } async function decryptWithEncryptedKeyString(keyString, data) { const key = await createKeyFromString(keyString); - return await decryptWithKey(key, JSON.parse(data)); + const payload = await decryptWithKey(key, JSON.parse(data)); + return payload; } async function createKeyFromString(keyString) { const key = await window.crypto.subtle.importKey(EXPORT_FORMAT, JSON.parse(keyString), DERIVED_KEY_FORMAT, true, ['encrypt', 'decrypt']); @@ -178,6 +191,8 @@ module.exports = { keyFromPassword, encryptWithKey, decryptWithKey, + encryptWithDetail, + decryptWithDetail, createKeyFromString, decryptWithEncryptedKeyString, // Buffer <-> Hex string methods diff --git a/dist/index.js.map b/dist/index.js.map index c11cb76..bba9c1a 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["interface EncryptReturn {\n vault: string;\n extractedKeyString: string;\n}\n\ninterface EncryptionResult {\n data: string;\n iv: string;\n salt?: string;\n}\n\ninterface DecryptResult {\n extractedKeyString: string;\n vault: unknown;\n salt: string;\n}\n\nconst EXPORT_FORMAT = 'jwk';\nconst DERIVED_KEY_FORMAT = 'AES-GCM';\nconst STRING_ENCODING = 'utf-8';\n\n/**\n * Encrypts a data object that can be any serializable value using\n * a provided password.\n *\n * @param {string} password - password to use for encryption\n * @param {R} dataObj - data to encrypt\n * @returns {Promise} cypher text\n */\nasync function encrypt(\n password: string,\n dataObj: R,\n): Promise {\n const salt = generateSalt();\n\n const passwordDerivedKey = await keyFromPassword(password, salt);\n const payload = await encryptWithKey(passwordDerivedKey, dataObj);\n payload.salt = salt;\n\n const extractedKeyString = await exportKey(passwordDerivedKey);\n\n return {\n vault: JSON.stringify(payload),\n extractedKeyString,\n };\n}\n\n/**\n * Encrypts the provided serializable javascript object using the\n * provided CryptoKey and returns an object containing the cypher text and\n * the initialization vector used.\n * @param {CryptoKey} key - CryptoKey to encrypt with\n * @param {R} dataObj - Serializable javascript object to encrypt\n * @returns {EncryptionResult}\n */\nasync function encryptWithKey(\n key: CryptoKey,\n dataObj: R,\n): Promise {\n const data = JSON.stringify(dataObj);\n const dataBuffer = Buffer.from(data, STRING_ENCODING);\n const vector = global.crypto.getRandomValues(new Uint8Array(16));\n\n const buf = await global.crypto.subtle.encrypt(\n {\n name: DERIVED_KEY_FORMAT,\n iv: vector,\n },\n key,\n dataBuffer,\n );\n\n const buffer = new Uint8Array(buf);\n const vectorStr = Buffer.from(vector).toString('base64');\n const vaultStr = Buffer.from(buffer).toString('base64');\n return {\n data: vaultStr,\n iv: vectorStr,\n };\n}\n\n/**\n * Given a password and a cypher text, decrypts the text and returns\n * the resulting value\n * @param {string} password - password to decrypt with\n * @param {string} text - cypher text to decrypt\n * @returns {DecryptResult}\n */\nasync function decrypt(password: string, text: string): Promise {\n const payload = JSON.parse(text);\n const { salt } = payload;\n const key = await keyFromPassword(password, salt);\n\n const extractedKeyString = await exportKey(key);\n const vault = await decryptWithKey(key, payload);\n\n return {\n extractedKeyString,\n vault,\n salt,\n };\n}\n\nasync function decryptWithEncryptedKeyString(keyString: string, data: string) {\n const key = await createKeyFromString(keyString);\n\n return await decryptWithKey(key, JSON.parse(data));\n}\n\nasync function createKeyFromString(keyString: string): Promise {\n const key = await window.crypto.subtle.importKey(\n EXPORT_FORMAT,\n JSON.parse(keyString),\n DERIVED_KEY_FORMAT,\n true,\n ['encrypt', 'decrypt'],\n );\n\n return key;\n}\n\nasync function exportKey(key: CryptoKey): Promise {\n const exportedKey = await window.crypto.subtle.exportKey(EXPORT_FORMAT, key);\n return JSON.stringify(exportedKey);\n}\n\n/**\n * Given a CryptoKey and an EncryptionResult object containing the initialization\n * vector (iv) and data to decrypt, return the resulting decrypted value.\n * @param {CryptoKey} key - CryptoKey to decrypt with\n * @param {EncryptionResult} payload - payload returned from an encryption method\n */\nasync function decryptWithKey(\n key: CryptoKey,\n payload: EncryptionResult,\n): Promise {\n const encryptedData = Buffer.from(payload.data, 'base64');\n const vector = Buffer.from(payload.iv, 'base64');\n\n let decryptedObj;\n try {\n const result = await crypto.subtle.decrypt(\n { name: DERIVED_KEY_FORMAT, iv: vector },\n key,\n encryptedData,\n );\n\n const decryptedData = new Uint8Array(result);\n const decryptedStr = Buffer.from(decryptedData).toString(STRING_ENCODING);\n decryptedObj = JSON.parse(decryptedStr);\n } catch (e) {\n throw new Error('Incorrect password');\n }\n\n return decryptedObj;\n}\n\n/**\n * Generate a CryptoKey from a password and random salt\n * @param {string} password - The password to use to generate key\n * @param {string} salt - The salt string to use in key derivation\n */\nasync function keyFromPassword(\n password: string,\n salt: string,\n): Promise {\n const passBuffer = Buffer.from(password, STRING_ENCODING);\n const saltBuffer = Buffer.from(salt, 'base64');\n\n const key = await global.crypto.subtle.importKey(\n 'raw',\n passBuffer,\n { name: 'PBKDF2' },\n false,\n ['deriveBits', 'deriveKey'],\n );\n\n const derivedKey = await global.crypto.subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: saltBuffer,\n iterations: 10000,\n hash: 'SHA-256',\n },\n key,\n { name: DERIVED_KEY_FORMAT, length: 256 },\n true,\n ['encrypt', 'decrypt'],\n );\n\n return derivedKey;\n}\n\n/**\n * Converts a hex string into a buffer.\n * @param {string} str - hex encoded string\n * @returns {Uint8Array}\n */\nfunction serializeBufferFromStorage(str: string): Uint8Array {\n const stripStr = str.slice(0, 2) === '0x' ? str.slice(2) : str;\n const buf = new Uint8Array(stripStr.length / 2);\n for (let i = 0; i < stripStr.length; i += 2) {\n const seg = stripStr.substr(i, 2);\n buf[i / 2] = parseInt(seg, 16);\n }\n return buf;\n}\n\n/**\n * Converts a buffer into a hex string ready for storage\n * @param {Uint8Array} buffer - Buffer to serialize\n * @returns {string} hex encoded string\n */\nfunction serializeBufferForStorage(buffer: Uint8Array): string {\n let result = '0x';\n const len = buffer.length || buffer.byteLength;\n for (let i = 0; i < len; i++) {\n result += unprefixedHex(buffer[i]);\n }\n return result;\n}\n\n/**\n * Converts a number into hex value, and ensures proper leading 0\n * for single characters strings.\n * @param {number} num - number to convert to string\n * @returns {string} hex string\n */\nfunction unprefixedHex(num: number): string {\n let hex = num.toString(16);\n while (hex.length < 2) {\n hex = `0${hex}`;\n }\n return hex;\n}\n\n/**\n * Generates a random string for use as a salt in CryptoKey generation\n * @param {number} byteCount - Number of bytes to generate\n * @returns {string} randomly generated string\n */\nfunction generateSalt(byteCount = 32): string {\n const view = new Uint8Array(byteCount);\n global.crypto.getRandomValues(view);\n // Uint8Array is a fixed length array and thus does not have methods like pop, etc\n // so TypeScript complains about casting it to an array. Array.from() works here for\n // getting the proper type, but it results in a functional difference. In order to\n // cast, you have to first cast view to unknown then cast the unknown value to number[]\n // TypeScript ftw: double opt in to write potentially type-mismatched code.\n const b64encoded = btoa(\n String.fromCharCode.apply(null, view as unknown as number[]),\n );\n return b64encoded;\n}\n\nexport = {\n // Simple encryption methods:\n encrypt,\n decrypt,\n\n // More advanced encryption methods:\n keyFromPassword,\n encryptWithKey,\n decryptWithKey,\n createKeyFromString,\n decryptWithEncryptedKeyString,\n\n // Buffer <-> Hex string methods\n serializeBufferForStorage,\n serializeBufferFromStorage,\n\n generateSalt,\n};\n"]} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["interface DetailedEncryptionResult {\n vault: string;\n extractedKeyString: string;\n}\n\ninterface EncryptionResult {\n data: string;\n iv: string;\n salt?: string;\n}\n\ninterface DecryptResult {\n extractedKeyString: string;\n vault: unknown;\n salt: string;\n}\n\nconst EXPORT_FORMAT = 'jwk';\nconst DERIVED_KEY_FORMAT = 'AES-GCM';\nconst STRING_ENCODING = 'utf-8';\n\n/**\n * Encrypts a data object that can be any serializable value using\n * a provided password.\n *\n * @param {string} password - password to use for encryption\n * @param {R} dataObj - data to encrypt\n * @returns {Promise} cypher text\n */\nasync function encrypt(\n password: string,\n dataObj: R,\n key: CryptoKey,\n salt: string = generateSalt(),\n): Promise {\n const cryptoKey = key || (await keyFromPassword(password, salt));\n\n const payload = await encryptWithKey(cryptoKey, dataObj);\n payload.salt = salt;\n\n return JSON.stringify(payload);\n}\n\nasync function encryptWithDetail(\n password: string,\n dataObj: R,\n): Promise {\n const salt = generateSalt();\n const key = await keyFromPassword(password, salt);\n const extractedKeyString = await exportKey(key);\n const vault = await encrypt(password, dataObj, key, salt);\n\n return {\n vault,\n extractedKeyString,\n };\n}\n\n/**\n * Encrypts the provided serializable javascript object using the\n * provided CryptoKey and returns an object containing the cypher text and\n * the initialization vector used.\n * @param {CryptoKey} key - CryptoKey to encrypt with\n * @param {R} dataObj - Serializable javascript object to encrypt\n * @returns {EncryptionResult}\n */\nasync function encryptWithKey(\n key: CryptoKey,\n dataObj: R,\n): Promise {\n const data = JSON.stringify(dataObj);\n const dataBuffer = Buffer.from(data, STRING_ENCODING);\n const vector = global.crypto.getRandomValues(new Uint8Array(16));\n\n const buf = await global.crypto.subtle.encrypt(\n {\n name: DERIVED_KEY_FORMAT,\n iv: vector,\n },\n key,\n dataBuffer,\n );\n\n const buffer = new Uint8Array(buf);\n const vectorStr = Buffer.from(vector).toString('base64');\n const vaultStr = Buffer.from(buffer).toString('base64');\n return {\n data: vaultStr,\n iv: vectorStr,\n };\n}\n\n/**\n * Given a password and a cypher text, decrypts the text and returns\n * the resulting value\n * @param {string} password - password to decrypt with\n * @param {string} text - cypher text to decrypt\n * @returns {DecryptResult}\n */\nasync function decrypt(\n password: string,\n text: string,\n key: CryptoKey,\n): Promise {\n const payload = JSON.parse(text);\n const { salt } = payload;\n\n const cryptoKey = key || (await keyFromPassword(password, salt));\n\n const result = await decryptWithKey(cryptoKey, payload);\n return result;\n}\n\nasync function decryptWithDetail(\n password: string,\n text: string,\n): Promise {\n const payload = JSON.parse(text);\n const { salt } = payload;\n const key = await keyFromPassword(password, salt);\n const extractedKeyString = await exportKey(key);\n\n const vault = decrypt(password, text, key);\n\n return {\n extractedKeyString,\n vault,\n salt,\n };\n}\n\nasync function decryptWithEncryptedKeyString(keyString: string, data: string) {\n const key = await createKeyFromString(keyString);\n const payload = await decryptWithKey(key, JSON.parse(data));\n return payload;\n}\n\nasync function createKeyFromString(keyString: string): Promise {\n const key = await window.crypto.subtle.importKey(\n EXPORT_FORMAT,\n JSON.parse(keyString),\n DERIVED_KEY_FORMAT,\n true,\n ['encrypt', 'decrypt'],\n );\n\n return key;\n}\n\nasync function exportKey(key: CryptoKey): Promise {\n const exportedKey = await window.crypto.subtle.exportKey(EXPORT_FORMAT, key);\n return JSON.stringify(exportedKey);\n}\n\n/**\n * Given a CryptoKey and an EncryptionResult object containing the initialization\n * vector (iv) and data to decrypt, return the resulting decrypted value.\n * @param {CryptoKey} key - CryptoKey to decrypt with\n * @param {EncryptionResult} payload - payload returned from an encryption method\n */\nasync function decryptWithKey(\n key: CryptoKey,\n payload: EncryptionResult,\n): Promise {\n const encryptedData = Buffer.from(payload.data, 'base64');\n const vector = Buffer.from(payload.iv, 'base64');\n\n let decryptedObj;\n try {\n const result = await crypto.subtle.decrypt(\n { name: DERIVED_KEY_FORMAT, iv: vector },\n key,\n encryptedData,\n );\n\n const decryptedData = new Uint8Array(result);\n const decryptedStr = Buffer.from(decryptedData).toString(STRING_ENCODING);\n decryptedObj = JSON.parse(decryptedStr);\n } catch (e) {\n throw new Error('Incorrect password');\n }\n\n return decryptedObj;\n}\n\n/**\n * Generate a CryptoKey from a password and random salt\n * @param {string} password - The password to use to generate key\n * @param {string} salt - The salt string to use in key derivation\n */\nasync function keyFromPassword(\n password: string,\n salt: string,\n): Promise {\n const passBuffer = Buffer.from(password, STRING_ENCODING);\n const saltBuffer = Buffer.from(salt, 'base64');\n\n const key = await global.crypto.subtle.importKey(\n 'raw',\n passBuffer,\n { name: 'PBKDF2' },\n false,\n ['deriveBits', 'deriveKey'],\n );\n\n const derivedKey = await global.crypto.subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: saltBuffer,\n iterations: 10000,\n hash: 'SHA-256',\n },\n key,\n { name: DERIVED_KEY_FORMAT, length: 256 },\n true,\n ['encrypt', 'decrypt'],\n );\n\n return derivedKey;\n}\n\n/**\n * Converts a hex string into a buffer.\n * @param {string} str - hex encoded string\n * @returns {Uint8Array}\n */\nfunction serializeBufferFromStorage(str: string): Uint8Array {\n const stripStr = str.slice(0, 2) === '0x' ? str.slice(2) : str;\n const buf = new Uint8Array(stripStr.length / 2);\n for (let i = 0; i < stripStr.length; i += 2) {\n const seg = stripStr.substr(i, 2);\n buf[i / 2] = parseInt(seg, 16);\n }\n return buf;\n}\n\n/**\n * Converts a buffer into a hex string ready for storage\n * @param {Uint8Array} buffer - Buffer to serialize\n * @returns {string} hex encoded string\n */\nfunction serializeBufferForStorage(buffer: Uint8Array): string {\n let result = '0x';\n const len = buffer.length || buffer.byteLength;\n for (let i = 0; i < len; i++) {\n result += unprefixedHex(buffer[i]);\n }\n return result;\n}\n\n/**\n * Converts a number into hex value, and ensures proper leading 0\n * for single characters strings.\n * @param {number} num - number to convert to string\n * @returns {string} hex string\n */\nfunction unprefixedHex(num: number): string {\n let hex = num.toString(16);\n while (hex.length < 2) {\n hex = `0${hex}`;\n }\n return hex;\n}\n\n/**\n * Generates a random string for use as a salt in CryptoKey generation\n * @param {number} byteCount - Number of bytes to generate\n * @returns {string} randomly generated string\n */\nfunction generateSalt(byteCount = 32): string {\n const view = new Uint8Array(byteCount);\n global.crypto.getRandomValues(view);\n // Uint8Array is a fixed length array and thus does not have methods like pop, etc\n // so TypeScript complains about casting it to an array. Array.from() works here for\n // getting the proper type, but it results in a functional difference. In order to\n // cast, you have to first cast view to unknown then cast the unknown value to number[]\n // TypeScript ftw: double opt in to write potentially type-mismatched code.\n const b64encoded = btoa(\n String.fromCharCode.apply(null, view as unknown as number[]),\n );\n return b64encoded;\n}\n\nexport = {\n // Simple encryption methods:\n encrypt,\n decrypt,\n\n // More advanced encryption methods:\n keyFromPassword,\n encryptWithKey,\n decryptWithKey,\n\n encryptWithDetail,\n decryptWithDetail,\n createKeyFromString,\n decryptWithEncryptedKeyString,\n\n // Buffer <-> Hex string methods\n serializeBufferForStorage,\n serializeBufferFromStorage,\n\n generateSalt,\n};\n"]} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 43ee039..f5f74f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -interface EncryptReturn { +interface DetailedEncryptionResult { vault: string; extractedKeyString: string; } @@ -9,7 +9,7 @@ interface EncryptionResult { salt?: string; } -interface DecryptResult { +interface DetailedDecryptResult { extractedKeyString: string; vault: unknown; salt: string; @@ -25,22 +25,43 @@ const STRING_ENCODING = 'utf-8'; * * @param {string} password - password to use for encryption * @param {R} dataObj - data to encrypt + * @param {CryptoKey} key - a CryptoKey instance + * @param {string} salt - salt used to encrypt * @returns {Promise} cypher text */ async function encrypt( password: string, dataObj: R, -): Promise { - const salt = generateSalt(); + key: CryptoKey, + salt: string = generateSalt(), +): Promise { + const cryptoKey = key || (await keyFromPassword(password, salt)); - const passwordDerivedKey = await keyFromPassword(password, salt); - const payload = await encryptWithKey(passwordDerivedKey, dataObj); + const payload = await encryptWithKey(cryptoKey, dataObj); payload.salt = salt; - const extractedKeyString = await exportKey(passwordDerivedKey); + return JSON.stringify(payload); +} + +/** + * Encrypts a data object that can be any serializable value using + * a provided password. + * + * @param {string} password - password to use for encryption + * @param {R} dataObj - data to encrypt + * @returns {Promise} object with vault and extractedKeyString + */ +async function encryptWithDetail( + password: string, + dataObj: R, +): Promise { + const salt = generateSalt(); + const key = await keyFromPassword(password, salt); + const extractedKeyString = await exportKey(key); + const vault = await encrypt(password, dataObj, key, salt); return { - vault: JSON.stringify(payload), + vault, extractedKeyString, }; } @@ -84,15 +105,40 @@ async function encryptWithKey( * the resulting value * @param {string} password - password to decrypt with * @param {string} text - cypher text to decrypt - * @returns {DecryptResult} + * @param {CryptoKey} key - a key to use for decrypting + * @returns {object} */ -async function decrypt(password: string, text: string): Promise { +async function decrypt( + password: string, + text: string, + key: CryptoKey, +): Promise { const payload = JSON.parse(text); const { salt } = payload; - const key = await keyFromPassword(password, salt); + const cryptoKey = key || (await keyFromPassword(password, salt)); + + const result = await decryptWithKey(cryptoKey, payload); + return result; +} + +/** + * Given a password and a cypher text, decrypts the text and returns + * the resulting value, keyString, and salt + * @param {string} password - password to decrypt with + * @param {string} text - cypher text to decrypt + * @returns {object} + */ +async function decryptWithDetail( + password: string, + text: string, +): Promise { + const payload = JSON.parse(text); + const { salt } = payload; + const key = await keyFromPassword(password, salt); const extractedKeyString = await exportKey(key); - const vault = await decryptWithKey(key, payload); + + const vault = decrypt(password, text, key); return { extractedKeyString, @@ -101,12 +147,24 @@ async function decrypt(password: string, text: string): Promise { }; } +/** + * Receives an exported CryptoKey string, creates a key, + * and decrypts cipher text with the reconstructed key + * @param {string} password - password to decrypt with + * @param {string} text - cypher text to decrypt + * @returns {object} + */ async function decryptWithEncryptedKeyString(keyString: string, data: string) { const key = await createKeyFromString(keyString); - - return await decryptWithKey(key, JSON.parse(data)); + const payload = await decryptWithKey(key, JSON.parse(data)); + return payload; } +/** + * Receives an exported CryptoKey string and creates a key + * @param {string} keyString - keyString to import + * @returns {CryptoKey} + */ async function createKeyFromString(keyString: string): Promise { const key = await window.crypto.subtle.importKey( EXPORT_FORMAT, @@ -119,6 +177,12 @@ async function createKeyFromString(keyString: string): Promise { return key; } +/** + * Receives an exported CryptoKey string, creates a key, + * and decrypts cipher text with the reconstructed key + * @param {CryptoKey} key - key to export + * @returns {string} + */ async function exportKey(key: CryptoKey): Promise { const exportedKey = await window.crypto.subtle.exportKey(EXPORT_FORMAT, key); return JSON.stringify(exportedKey); @@ -262,6 +326,9 @@ export = { keyFromPassword, encryptWithKey, decryptWithKey, + + encryptWithDetail, + decryptWithDetail, createKeyFromString, decryptWithEncryptedKeyString,