1+ interface DetailedEncryptionResult {
2+ vault : string ;
3+ exportedKeyString : string ;
4+ }
5+
16interface EncryptionResult {
27 data : string ;
38 iv : string ;
49 salt ?: string ;
510}
611
12+ interface DetailedDecryptResult {
13+ exportedKeyString : string ;
14+ vault : unknown ;
15+ salt : string ;
16+ }
17+
18+ const EXPORT_FORMAT = 'jwk' ;
19+ const DERIVED_KEY_FORMAT = 'AES-GCM' ;
20+ const STRING_ENCODING = 'utf-8' ;
21+
722/**
823 * Encrypts a data object that can be any serializable value using
924 * a provided password.
1025 *
1126 * @param {string } password - password to use for encryption
1227 * @param {R } dataObj - data to encrypt
28+ * @param {CryptoKey } key - a CryptoKey instance
29+ * @param {string } salt - salt used to encrypt
1330 * @returns {Promise<string> } cypher text
1431 */
1532export async function encrypt < R > (
1633 password : string ,
1734 dataObj : R ,
35+ key ?: CryptoKey ,
36+ salt : string = generateSalt ( ) ,
1837) : Promise < string > {
19- const salt = generateSalt ( ) ;
20-
21- const passwordDerivedKey = await keyFromPassword ( password , salt ) ;
22- const payload = await encryptWithKey ( passwordDerivedKey , dataObj ) ;
38+ const cryptoKey = key || ( await keyFromPassword ( password , salt ) ) ;
39+ const payload = await encryptWithKey ( cryptoKey , dataObj ) ;
2340 payload . salt = salt ;
2441 return JSON . stringify ( payload ) ;
2542}
2643
44+ /**
45+ * Encrypts a data object that can be any serializable value using
46+ * a provided password.
47+ *
48+ * @param {string } password - password to use for encryption
49+ * @param {R } dataObj - data to encrypt
50+ * @returns {Promise<DetailedEncryptionResult> } object with vault and exportedKeyString
51+ */
52+ export async function encryptWithDetail < R > (
53+ password : string ,
54+ dataObj : R ,
55+ ) : Promise < DetailedEncryptionResult > {
56+ const salt = generateSalt ( ) ;
57+ const key = await keyFromPassword ( password , salt ) ;
58+ const exportedKeyString = await exportKey ( key ) ;
59+ const vault = await encrypt ( password , dataObj , key , salt ) ;
60+
61+ return {
62+ vault,
63+ exportedKeyString,
64+ } ;
65+ }
66+
2767/**
2868 * Encrypts the provided serializable javascript object using the
2969 * provided CryptoKey and returns an object containing the cypher text and
@@ -37,12 +77,12 @@ export async function encryptWithKey<R>(
3777 dataObj : R ,
3878) : Promise < EncryptionResult > {
3979 const data = JSON . stringify ( dataObj ) ;
40- const dataBuffer = Buffer . from ( data , 'utf-8' ) ;
80+ const dataBuffer = Buffer . from ( data , STRING_ENCODING ) ;
4181 const vector = global . crypto . getRandomValues ( new Uint8Array ( 16 ) ) ;
4282
4383 const buf = await global . crypto . subtle . encrypt (
4484 {
45- name : 'AES-GCM' ,
85+ name : DERIVED_KEY_FORMAT ,
4686 iv : vector ,
4787 } ,
4888 key ,
@@ -63,12 +103,45 @@ export async function encryptWithKey<R>(
63103 * the resulting value
64104 * @param {string } password - password to decrypt with
65105 * @param {string } text - cypher text to decrypt
106+ * @param {CryptoKey } key - a key to use for decrypting
107+ * @returns {object }
66108 */
67- export async function decrypt < R > ( password : string , text : string ) : Promise < R > {
109+ export async function decrypt (
110+ password : string ,
111+ text : string ,
112+ key ?: CryptoKey ,
113+ ) : Promise < unknown > {
114+ const payload = JSON . parse ( text ) ;
115+ const { salt } = payload ;
116+
117+ const cryptoKey = key || ( await keyFromPassword ( password , salt ) ) ;
118+
119+ const result = await decryptWithKey ( cryptoKey , payload ) ;
120+ return result ;
121+ }
122+
123+ /**
124+ * Given a password and a cypher text, decrypts the text and returns
125+ * the resulting value, keyString, and salt
126+ * @param {string } password - password to decrypt with
127+ * @param {string } text - cypher text to decrypt
128+ * @returns {object }
129+ */
130+ export async function decryptWithDetail (
131+ password : string ,
132+ text : string ,
133+ ) : Promise < DetailedDecryptResult > {
68134 const payload = JSON . parse ( text ) ;
69135 const { salt } = payload ;
70136 const key = await keyFromPassword ( password , salt ) ;
71- return await decryptWithKey ( key , payload ) ;
137+ const exportedKeyString = await exportKey ( key ) ;
138+ const vault = await decrypt ( password , text , key ) ;
139+
140+ return {
141+ exportedKeyString,
142+ vault,
143+ salt,
144+ } ;
72145}
73146
74147/**
@@ -87,13 +160,13 @@ export async function decryptWithKey<R>(
87160 let decryptedObj ;
88161 try {
89162 const result = await crypto . subtle . decrypt (
90- { name : 'AES-GCM' , iv : vector } ,
163+ { name : DERIVED_KEY_FORMAT , iv : vector } ,
91164 key ,
92165 encryptedData ,
93166 ) ;
94167
95168 const decryptedData = new Uint8Array ( result ) ;
96- const decryptedStr = Buffer . from ( decryptedData ) . toString ( 'utf-8' ) ;
169+ const decryptedStr = Buffer . from ( decryptedData ) . toString ( STRING_ENCODING ) ;
97170 decryptedObj = JSON . parse ( decryptedStr ) ;
98171 } catch ( e ) {
99172 throw new Error ( 'Incorrect password' ) ;
@@ -102,6 +175,36 @@ export async function decryptWithKey<R>(
102175 return decryptedObj ;
103176}
104177
178+ /**
179+ * Receives an exported CryptoKey string and creates a key
180+ * @param {string } keyString - keyString to import
181+ * @returns {CryptoKey }
182+ */
183+ export async function createKeyFromString (
184+ keyString : string ,
185+ ) : Promise < CryptoKey > {
186+ const key = await window . crypto . subtle . importKey (
187+ EXPORT_FORMAT ,
188+ JSON . parse ( keyString ) ,
189+ DERIVED_KEY_FORMAT ,
190+ true ,
191+ [ 'encrypt' , 'decrypt' ] ,
192+ ) ;
193+
194+ return key ;
195+ }
196+
197+ /**
198+ * Receives an exported CryptoKey string, creates a key,
199+ * and decrypts cipher text with the reconstructed key
200+ * @param {CryptoKey } key - key to export
201+ * @returns {string }
202+ */
203+ async function exportKey ( key : CryptoKey ) : Promise < string > {
204+ const exportedKey = await window . crypto . subtle . exportKey ( EXPORT_FORMAT , key ) ;
205+ return JSON . stringify ( exportedKey ) ;
206+ }
207+
105208/**
106209 * Generate a CryptoKey from a password and random salt
107210 * @param {string } password - The password to use to generate key
@@ -111,7 +214,7 @@ export async function keyFromPassword(
111214 password : string ,
112215 salt : string ,
113216) : Promise < CryptoKey > {
114- const passBuffer = Buffer . from ( password , 'utf-8' ) ;
217+ const passBuffer = Buffer . from ( password , STRING_ENCODING ) ;
115218 const saltBuffer = Buffer . from ( salt , 'base64' ) ;
116219
117220 const key = await global . crypto . subtle . importKey (
@@ -130,8 +233,8 @@ export async function keyFromPassword(
130233 hash : 'SHA-256' ,
131234 } ,
132235 key ,
133- { name : 'AES-GCM' , length : 256 } ,
134- false ,
236+ { name : DERIVED_KEY_FORMAT , length : 256 } ,
237+ true ,
135238 [ 'encrypt' , 'decrypt' ] ,
136239 ) ;
137240
0 commit comments