@@ -14,137 +14,47 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
17
- import type { BinaryLike } from "crypto" ;
18
- import { getCrypto } from '../utils' ;
19
17
import { decodeBase64 , encodeBase64 } from './olmlib' ;
20
-
21
- const subtleCrypto = ( typeof window !== "undefined" && window . crypto ) ?
22
- ( window . crypto . subtle || window . crypto . webkitSubtle ) : null ;
18
+ import { subtleCrypto , crypto , TextEncoder } from "./crypto" ;
23
19
24
20
// salt for HKDF, with 8 bytes of zeros
25
21
const zeroSalt = new Uint8Array ( 8 ) ;
26
22
27
23
export interface IEncryptedPayload {
28
24
[ key : string ] : any ; // extensible
29
- iv ? : string ;
30
- ciphertext ? : string ;
31
- mac ? : string ;
25
+ iv : string ;
26
+ ciphertext : string ;
27
+ mac : string ;
32
28
}
33
29
34
30
/**
35
- * encrypt a string in Node.js
31
+ * encrypt a string
36
32
*
37
33
* @param {string } data the plaintext to encrypt
38
34
* @param {Uint8Array } key the encryption key to use
39
35
* @param {string } name the name of the secret
40
36
* @param {string } ivStr the initialization vector to use
41
37
*/
42
- async function encryptNode ( data : string , key : Uint8Array , name : string , ivStr ?: string ) : Promise < IEncryptedPayload > {
43
- const crypto = getCrypto ( ) ;
44
- if ( ! crypto ) {
45
- throw new Error ( "No usable crypto implementation" ) ;
46
- }
47
-
48
- let iv ;
49
- if ( ivStr ) {
50
- iv = decodeBase64 ( ivStr ) ;
51
- } else {
52
- iv = crypto . randomBytes ( 16 ) ;
53
-
54
- // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
55
- // (which would mean we wouldn't be able to decrypt on Android). The loss
56
- // of a single bit of iv is a price we have to pay.
57
- iv [ 8 ] &= 0x7f ;
58
- }
59
-
60
- const [ aesKey , hmacKey ] = deriveKeysNode ( key , name ) ;
61
-
62
- const cipher = crypto . createCipheriv ( "aes-256-ctr" , aesKey , iv ) ;
63
- const ciphertext = Buffer . concat ( [
64
- cipher . update ( data , "utf8" ) ,
65
- cipher . final ( ) ,
66
- ] ) ;
67
-
68
- const hmac = crypto . createHmac ( "sha256" , hmacKey )
69
- . update ( ciphertext ) . digest ( "base64" ) ;
70
-
71
- return {
72
- iv : encodeBase64 ( iv ) ,
73
- ciphertext : ciphertext . toString ( "base64" ) ,
74
- mac : hmac ,
75
- } ;
76
- }
77
-
78
- /**
79
- * decrypt a string in Node.js
80
- *
81
- * @param {object } data the encrypted data
82
- * @param {string } data.ciphertext the ciphertext in base64
83
- * @param {string } data.iv the initialization vector in base64
84
- * @param {string } data.mac the HMAC in base64
85
- * @param {Uint8Array } key the encryption key to use
86
- * @param {string } name the name of the secret
87
- */
88
- async function decryptNode ( data : IEncryptedPayload , key : Uint8Array , name : string ) : Promise < string > {
89
- const crypto = getCrypto ( ) ;
90
- if ( ! crypto ) {
91
- throw new Error ( "No usable crypto implementation" ) ;
92
- }
93
-
94
- const [ aesKey , hmacKey ] = deriveKeysNode ( key , name ) ;
95
-
96
- const hmac = crypto . createHmac ( "sha256" , hmacKey )
97
- . update ( Buffer . from ( data . ciphertext , "base64" ) )
98
- . digest ( "base64" ) . replace ( / = + $ / g, '' ) ;
99
-
100
- if ( hmac !== data . mac . replace ( / = + $ / g, '' ) ) {
101
- throw new Error ( `Error decrypting secret ${ name } : bad MAC` ) ;
102
- }
103
-
104
- const decipher = crypto . createDecipheriv (
105
- "aes-256-ctr" , aesKey , decodeBase64 ( data . iv ) ,
106
- ) ;
107
- return decipher . update ( data . ciphertext , "base64" , "utf8" )
108
- + decipher . final ( "utf8" ) ;
109
- }
110
-
111
- function deriveKeysNode ( key : BinaryLike , name : string ) : [ Buffer , Buffer ] {
112
- const crypto = getCrypto ( ) ;
113
- const prk = crypto . createHmac ( "sha256" , zeroSalt ) . update ( key ) . digest ( ) ;
114
-
115
- const b = Buffer . alloc ( 1 , 1 ) ;
116
- const aesKey = crypto . createHmac ( "sha256" , prk )
117
- . update ( name , "utf8" ) . update ( b ) . digest ( ) ;
118
- b [ 0 ] = 2 ;
119
- const hmacKey = crypto . createHmac ( "sha256" , prk )
120
- . update ( aesKey ) . update ( name , "utf8" ) . update ( b ) . digest ( ) ;
121
-
122
- return [ aesKey , hmacKey ] ;
123
- }
124
-
125
- /**
126
- * encrypt a string in Node.js
127
- *
128
- * @param {string } data the plaintext to encrypt
129
- * @param {Uint8Array } key the encryption key to use
130
- * @param {string } name the name of the secret
131
- * @param {string } ivStr the initialization vector to use
132
- */
133
- async function encryptBrowser ( data : string , key : Uint8Array , name : string , ivStr ?: string ) : Promise < IEncryptedPayload > {
134
- let iv ;
38
+ export async function encryptAES (
39
+ data : string ,
40
+ key : Uint8Array ,
41
+ name : string ,
42
+ ivStr ?: string ,
43
+ ) : Promise < IEncryptedPayload > {
44
+ let iv : Uint8Array ;
135
45
if ( ivStr ) {
136
46
iv = decodeBase64 ( ivStr ) ;
137
47
} else {
138
48
iv = new Uint8Array ( 16 ) ;
139
- window . crypto . getRandomValues ( iv ) ;
49
+ crypto . getRandomValues ( iv ) ;
140
50
141
51
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
142
52
// (which would mean we wouldn't be able to decrypt on Android). The loss
143
53
// of a single bit of iv is a price we have to pay.
144
54
iv [ 8 ] &= 0x7f ;
145
55
}
146
56
147
- const [ aesKey , hmacKey ] = await deriveKeysBrowser ( key , name ) ;
57
+ const [ aesKey , hmacKey ] = await deriveKeys ( key , name ) ;
148
58
const encodedData = new TextEncoder ( ) . encode ( data ) ;
149
59
150
60
const ciphertext = await subtleCrypto . encrypt (
@@ -171,7 +81,7 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr
171
81
}
172
82
173
83
/**
174
- * decrypt a string in the browser
84
+ * decrypt a string
175
85
*
176
86
* @param {object } data the encrypted data
177
87
* @param {string } data.ciphertext the ciphertext in base64
@@ -180,8 +90,8 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr
180
90
* @param {Uint8Array } key the encryption key to use
181
91
* @param {string } name the name of the secret
182
92
*/
183
- async function decryptBrowser ( data : IEncryptedPayload , key : Uint8Array , name : string ) : Promise < string > {
184
- const [ aesKey , hmacKey ] = await deriveKeysBrowser ( key , name ) ;
93
+ export async function decryptAES ( data : IEncryptedPayload , key : Uint8Array , name : string ) : Promise < string > {
94
+ const [ aesKey , hmacKey ] = await deriveKeys ( key , name ) ;
185
95
186
96
const ciphertext = decodeBase64 ( data . ciphertext ) ;
187
97
@@ -207,7 +117,7 @@ async function decryptBrowser(data: IEncryptedPayload, key: Uint8Array, name: st
207
117
return new TextDecoder ( ) . decode ( new Uint8Array ( plaintext ) ) ;
208
118
}
209
119
210
- async function deriveKeysBrowser ( key : Uint8Array , name : string ) : Promise < [ CryptoKey , CryptoKey ] > {
120
+ async function deriveKeys ( key : Uint8Array , name : string ) : Promise < [ CryptoKey , CryptoKey ] > {
211
121
const hkdfkey = await subtleCrypto . importKey (
212
122
'raw' ,
213
123
key ,
@@ -253,14 +163,6 @@ async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[Crypto
253
163
return Promise . all ( [ aesProm , hmacProm ] ) ;
254
164
}
255
165
256
- export function encryptAES ( data : string , key : Uint8Array , name : string , ivStr ?: string ) : Promise < IEncryptedPayload > {
257
- return subtleCrypto ? encryptBrowser ( data , key , name , ivStr ) : encryptNode ( data , key , name , ivStr ) ;
258
- }
259
-
260
- export function decryptAES ( data : IEncryptedPayload , key : Uint8Array , name : string ) : Promise < string > {
261
- return subtleCrypto ? decryptBrowser ( data , key , name ) : decryptNode ( data , key , name ) ;
262
- }
263
-
264
166
// string of zeroes, for calculating the key check
265
167
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" ;
266
168
0 commit comments