@@ -14,153 +14,13 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
17
- import { decodeBase64 , encodeBase64 } from "../base64.ts" ;
18
-
19
- // salt for HKDF, with 8 bytes of zeros
20
- const zeroSalt = new Uint8Array ( 8 ) ;
21
-
22
- export interface IEncryptedPayload {
23
- [ key : string ] : any ; // extensible
24
- /** the initialization vector in base64 */
25
- iv : string ;
26
- /** the ciphertext in base64 */
27
- ciphertext : string ;
28
- /** the HMAC in base64 */
29
- mac : string ;
30
- }
31
-
32
- /**
33
- * Encrypt a string using AES-CTR.
34
- *
35
- * @param data - the plaintext to encrypt
36
- * @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key for
37
- * encryption. Obviously, the same key must be provided when decrypting.
38
- * @param name - the name of the secret. Used as an input to the HKDF operation which is used to derive the AES key,
39
- * so again the same value must be provided when decrypting.
40
- * @param ivStr - the base64-encoded initialization vector to use. If not supplied, a random one will be generated.
41
- *
42
- * @returns The encrypted result, including the ciphertext itself, the initialization vector (as supplied in `ivStr`,
43
- * or generated), and an HMAC on the ciphertext — all base64-encoded.
44
- */
45
- export async function encryptAES (
46
- data : string ,
47
- key : Uint8Array ,
48
- name : string ,
49
- ivStr ?: string ,
50
- ) : Promise < IEncryptedPayload > {
51
- let iv : Uint8Array ;
52
- if ( ivStr ) {
53
- iv = decodeBase64 ( ivStr ) ;
54
- } else {
55
- iv = new Uint8Array ( 16 ) ;
56
- globalThis . crypto . getRandomValues ( iv ) ;
57
-
58
- // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
59
- // (which would mean we wouldn't be able to decrypt on Android). The loss
60
- // of a single bit of iv is a price we have to pay.
61
- iv [ 8 ] &= 0x7f ;
62
- }
63
-
64
- const [ aesKey , hmacKey ] = await deriveKeys ( key , name ) ;
65
- const encodedData = new TextEncoder ( ) . encode ( data ) ;
66
-
67
- const ciphertext = await globalThis . crypto . subtle . encrypt (
68
- {
69
- name : "AES-CTR" ,
70
- counter : iv ,
71
- length : 64 ,
72
- } ,
73
- aesKey ,
74
- encodedData ,
75
- ) ;
76
-
77
- const hmac = await globalThis . crypto . subtle . sign ( { name : "HMAC" } , hmacKey , ciphertext ) ;
78
-
79
- return {
80
- iv : encodeBase64 ( iv ) ,
81
- ciphertext : encodeBase64 ( ciphertext ) ,
82
- mac : encodeBase64 ( hmac ) ,
83
- } ;
84
- }
85
-
86
- /**
87
- * Decrypt an AES-encrypted string.
88
- *
89
- * @param data - the encrypted data, returned by {@link encryptAES}.
90
- * @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key. Must
91
- * be the same as provided to {@link encryptAES}.
92
- * @param name - the name of the secret. Also used as an input to the HKDF operation which is used to derive the AES
93
- * key, so again must be the same as provided to {@link encryptAES}.
94
- */
95
- export async function decryptAES ( data : IEncryptedPayload , key : Uint8Array , name : string ) : Promise < string > {
96
- const [ aesKey , hmacKey ] = await deriveKeys ( key , name ) ;
97
-
98
- const ciphertext = decodeBase64 ( data . ciphertext ) ;
99
-
100
- if ( ! ( await globalThis . crypto . subtle . verify ( { name : "HMAC" } , hmacKey , decodeBase64 ( data . mac ) , ciphertext ) ) ) {
101
- throw new Error ( `Error decrypting secret ${ name } : bad MAC` ) ;
102
- }
103
-
104
- const plaintext = await globalThis . crypto . subtle . decrypt (
105
- {
106
- name : "AES-CTR" ,
107
- counter : decodeBase64 ( data . iv ) ,
108
- length : 64 ,
109
- } ,
110
- aesKey ,
111
- ciphertext ,
112
- ) ;
113
-
114
- return new TextDecoder ( ) . decode ( new Uint8Array ( plaintext ) ) ;
115
- }
116
-
117
- async function deriveKeys ( key : Uint8Array , name : string ) : Promise < [ CryptoKey , CryptoKey ] > {
118
- const hkdfkey = await globalThis . crypto . subtle . importKey ( "raw" , key , { name : "HKDF" } , false , [ "deriveBits" ] ) ;
119
- const keybits = await globalThis . crypto . subtle . deriveBits (
120
- {
121
- name : "HKDF" ,
122
- salt : zeroSalt ,
123
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
124
- // @ts -ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/879
125
- info : new TextEncoder ( ) . encode ( name ) ,
126
- hash : "SHA-256" ,
127
- } ,
128
- hkdfkey ,
129
- 512 ,
130
- ) ;
131
-
132
- const aesKey = keybits . slice ( 0 , 32 ) ;
133
- const hmacKey = keybits . slice ( 32 ) ;
134
-
135
- const aesProm = globalThis . crypto . subtle . importKey ( "raw" , aesKey , { name : "AES-CTR" } , false , [
136
- "encrypt" ,
137
- "decrypt" ,
138
- ] ) ;
139
-
140
- const hmacProm = globalThis . crypto . subtle . importKey (
141
- "raw" ,
142
- hmacKey ,
143
- {
144
- name : "HMAC" ,
145
- hash : { name : "SHA-256" } ,
146
- } ,
147
- false ,
148
- [ "sign" , "verify" ] ,
149
- ) ;
150
-
151
- return Promise . all ( [ aesProm , hmacProm ] ) ;
152
- }
153
-
154
- // string of zeroes, for calculating the key check
155
- const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ;
156
-
157
- /** Calculate the MAC for checking the key.
158
- *
159
- * @param key - the key to use
160
- * @param iv - The initialization vector as a base64-encoded string.
161
- * If omitted, a random initialization vector will be created.
162
- * @returns An object that contains, `mac` and `iv` properties.
163
- */
164
- export function calculateKeyCheck ( key : Uint8Array , iv ?: string ) : Promise < IEncryptedPayload > {
165
- return encryptAES ( ZERO_STR , key , "" , iv ) ;
166
- }
17
+ import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts" ;
18
+ import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts" ;
19
+ import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts" ;
20
+
21
+ // Export for backwards compatibility
22
+ export type { AESEncryptedSecretStoragePayload as IEncryptedPayload } ;
23
+ // Export with new names instead of using `as` to not break react-sdk tests
24
+ export const encryptAES = encryptAESSecretStorageItem ;
25
+ export const decryptAES = decryptAESSecretStorageItem ;
26
+ export { calculateKeyCheck } from "../calculateKeyCheck.ts" ;
0 commit comments