7
7
8
8
import CryptoJS from 'crypto-js' ;
9
9
10
- const P2P_ENCRYPTION = false ;
10
+ const P2P_ENCRYPTION = true ;
11
+
12
+ const kMaxIncomingPacketSize = 128 * 1024 * 1024 ;
13
+
14
+ function uint8ArrayToWordArray ( u8arr ) {
15
+ let len = u8arr . length ;
16
+ let words = [ ] ;
17
+ for ( let i = 0 ; i < len ; i ++ ) {
18
+ words [ i >>> 2 ] |= ( u8arr [ i ] & 0xff ) << ( 24 - ( i % 4 ) * 8 ) ;
19
+ }
20
+
21
+ return CryptoJS . lib . WordArray . create ( words , len ) ;
22
+ }
23
+
24
+ function wordArrayToUint8Array ( wordArray ) {
25
+ const l = wordArray . sigBytes ;
26
+ const words = wordArray . words ;
27
+ const result = new Uint8Array ( l ) ;
28
+
29
+ let i = 0 /*dst*/ , j = 0 /*src*/ ;
30
+ while ( true ) {
31
+ // here i is a multiple of 4
32
+ if ( i === l )
33
+ break ;
34
+ let w = words [ j ++ ] ;
35
+ result [ i ++ ] = ( w & 0xff000000 ) >>> 24 ;
36
+ if ( i === l )
37
+ break ;
38
+ result [ i ++ ] = ( w & 0x00ff0000 ) >>> 16 ;
39
+ if ( i === l )
40
+ break ;
41
+ result [ i ++ ] = ( w & 0x0000ff00 ) >>> 8 ;
42
+ if ( i === l )
43
+ break ;
44
+ result [ i ++ ] = ( w & 0x000000ff ) ;
45
+ }
46
+
47
+ return result ;
48
+ }
11
49
12
50
export default class P2PEncryptor {
13
- constructor ( key , isOutgoing ) {
14
- const p2pKey = CryptoJS . enc . Base64 . parse ( key ) ;
51
+ constructor ( isOutgoing , keyBase64 ) {
52
+ this . keyBase64 = keyBase64 ;
53
+ this . isOutgoing = isOutgoing ;
54
+ this . type = 'Signaling' ;
55
+ this . counter = 0 ;
15
56
16
- this . key = CryptoJS . enc . Hex . parse ( '3132333435363738393031323334353641424344454647484940414243444546' ) ;
17
- this . iv = CryptoJS . enc . Hex . parse ( '0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f' ) ;
18
- this . mode = CryptoJS . mode . CTR ;
19
- this . padding = CryptoJS . pad . NoPadding ;
57
+ const p2pKeyWA = CryptoJS . enc . Base64 . parse ( keyBase64 ) ;
58
+ this . p2pKey = wordArrayToUint8Array ( p2pKeyWA ) ;
20
59
}
21
60
22
61
encryptToBase64 ( str ) {
23
62
if ( P2P_ENCRYPTION ) {
24
- const { key, iv, mode, padding } = this ;
63
+ const enc = new TextEncoder ( ) ;
64
+ const arr = enc . encode ( str ) ;
25
65
26
- const encrypted = CryptoJS . AES . encrypt ( str , key , {
27
- mode,
28
- iv,
29
- padding
30
- } ) ;
66
+ const packet = this . encryptRawPacket ( new Uint8Array ( arr ) ) ;
67
+
68
+ const { bytes } = packet ;
69
+ const wa = uint8ArrayToWordArray ( bytes ) ;
31
70
32
- return encrypted . toString ( ) ;
71
+ return CryptoJS . enc . Base64 . stringify ( wa ) ;
33
72
} else {
34
- // to base64 string
35
73
return btoa ( str ) ;
36
74
}
37
75
}
38
76
39
77
decryptFromBase64 ( base64 ) {
40
78
if ( P2P_ENCRYPTION ) {
41
- const { key, iv, mode, padding } = this ;
79
+ const wa = CryptoJS . enc . Base64 . parse ( base64 ) ;
80
+
81
+ const buffer = wordArrayToUint8Array ( wa ) ;
82
+ const decrypted = this . decryptRawPacket ( buffer ) ;
83
+
84
+ const dec = new TextDecoder ( 'utf-8' ) ;
85
+ return dec . decode ( decrypted ) ;
86
+ } else {
87
+ return atob ( base64 ) ;
88
+ }
89
+ }
90
+
91
+ concatSHA256 ( parts ) {
92
+ const sha256 = CryptoJS . algo . SHA256 . create ( ) ;
93
+ const dec = new TextDecoder ( 'utf-8' )
94
+ for ( let i = 0 ; i < parts . length ; i ++ ) {
95
+ const str = dec . decode ( parts [ i ] ) ;
96
+ sha256 . update ( str ) ;
97
+ }
98
+
99
+ const result = sha256 . finalize ( ) ;
100
+
101
+ return wordArrayToUint8Array ( result ) ;
102
+ }
103
+
104
+ encryptPrepared ( buffer ) {
105
+ const result = {
106
+ counter : 0 , //this.counterFromSeq(this.readSeq(buffer)),
107
+ bytes : new Uint8Array ( 16 + buffer . length )
108
+ }
109
+
110
+ const x = ( this . isOutgoing ? 0 : 8 ) + ( this . type === 'Signaling' ? 128 : 0 ) ;
111
+ const key = this . p2pKey ;
112
+
113
+ // console.log('[encryptor][p2p] encryptPrepared (x, key)', x, key);
114
+ const msgKeyLarge = this . concatSHA256 ( [ key . subarray ( x + 88 , x + 88 + 32 ) , buffer ] ) ;
115
+ const msgKey = result . bytes ;
116
+ for ( let i = 0 ; i < 16 ; i ++ ) {
117
+ msgKey [ i ] = msgKeyLarge [ i + 8 ] ;
118
+ }
119
+ // console.log('[encryptor][p2p] encryptPrepared msgKeyLarge', msgKeyLarge, msgKey);
120
+
121
+ // console.log('[encryptor][p2p] encryptPrepared prepareAesKeyIv start', key, msgKey, x);
122
+ const aesKeyIv = this . prepareAesKeyIv ( key , msgKey , x ) ;
123
+ // console.log('[encryptor][p2p] encryptPrepared prepareAesKeyIv stop', aesKeyIv);
124
+
125
+ // console.log('[encryptor][p2p] encryptPrepared aesProcessCtr start', buffer, buffer.length, aesKeyIv);
126
+ const bytes = this . aesProcessCtr ( buffer , buffer . length , aesKeyIv , true ) ;
127
+ // console.log('[encryptor][p2p] encryptPrepared aesProcessCtr stop', bytes);
128
+
129
+ result . bytes = new Uint8Array ( [ ...result . bytes . subarray ( 0 , 16 ) , ...bytes ] ) ;
130
+
131
+ return result ;
132
+ }
133
+
134
+ encryptObjToBase64 ( obj ) {
135
+ const str = JSON . stringify ( obj ) ;
136
+
137
+ const enc = new TextEncoder ( ) ;
138
+ const arr = enc . encode ( str ) ;
139
+
140
+ const packet = this . encryptRawPacket ( new Uint8Array ( arr ) ) ;
141
+
142
+ const { bytes } = packet ;
143
+ const wa = uint8ArrayToWordArray ( bytes ) ;
144
+
145
+ return CryptoJS . enc . Base64 . stringify ( wa ) ;
146
+ }
147
+
148
+ encryptRawPacket ( buffer ) {
149
+ const seq = ++ this . counter ;
150
+ const arr = new ArrayBuffer ( 4 ) ;
151
+ const view = new DataView ( arr ) ;
152
+ view . setUint32 ( 0 , seq >>> 0 , false ) ; // byteOffset = 0; litteEndian = false
153
+
154
+ const result = new Uint8Array ( [ ...new Uint8Array ( arr ) , ...buffer ] ) ;
155
+
156
+ // console.log('[encryptor][p2p] encryptRawPacker buffer', result);
157
+ const encryptedPacket = this . encryptPrepared ( result ) ;
158
+
159
+ return encryptedPacket ;
160
+ }
161
+
162
+ prepareAesKeyIv ( key , msgKey , x ) {
163
+ const sha256a = this . concatSHA256 ( [
164
+ msgKey . subarray ( 0 , 16 ) ,
165
+ key . subarray ( x , x + 36 )
166
+ ] ) ;
167
+
168
+ const sha256b = this . concatSHA256 ( [
169
+ key . subarray ( 40 + x , 40 + x + 36 ) ,
170
+ msgKey . subarray ( 0 , 16 )
171
+ ] ) ;
172
+
173
+ return {
174
+ key : new Uint8Array ( [
175
+ ...sha256a . subarray ( 0 , 8 ) ,
176
+ ...sha256b . subarray ( 8 , 8 + 16 ) ,
177
+ ...sha256a . subarray ( 24 , 24 + 8 )
178
+ ] ) ,
179
+ iv : new Uint8Array ( [
180
+ ...sha256b . subarray ( 0 , 4 ) ,
181
+ ...sha256a . subarray ( 8 , 8 + 8 ) ,
182
+ ...sha256b . subarray ( 24 , 24 + 4 )
183
+ ] )
184
+ } ;
185
+ }
42
186
43
- const decrypted = CryptoJS . AES . decrypt ( base64 , key , {
44
- mode,
187
+ aesProcessCtr ( encryptedData , dataSize , aesKeyIv , encrypt = true ) {
188
+ const key = uint8ArrayToWordArray ( aesKeyIv . key ) ;
189
+ const iv = uint8ArrayToWordArray ( aesKeyIv . iv ) ;
190
+
191
+ const str = uint8ArrayToWordArray ( encryptedData ) ;
192
+ // console.log('[encryptor][p2p] aesProcessCtr (aesKey, aesIv, encrypt)', { key, iv, encrypt, encryptedData });
193
+
194
+ if ( encrypt ) {
195
+ const encrypted = CryptoJS . AES . encrypt ( str , key , {
196
+ mode : CryptoJS . mode . CTR ,
45
197
iv,
46
- padding
198
+ padding : CryptoJS . pad . ZeroPadding
47
199
} ) ;
48
200
49
- return decrypted . toString ( CryptoJS . enc . Utf8 ) ;
201
+ const result = wordArrayToUint8Array ( encrypted . ciphertext ) ;
202
+
203
+ // console.log('[encryptor][p2p] aesProcessCtr (result)', { result, ciphertext: encrypted.ciphertext });
204
+
205
+ return result ;
50
206
} else {
51
- // from base64 string
52
- return atob ( base64 ) ;
207
+ const decrypted = CryptoJS . AES . decrypt ( { ciphertext : str } , key , {
208
+ mode : CryptoJS . mode . CTR ,
209
+ iv,
210
+ padding : CryptoJS . pad . ZeroPadding
211
+ } ) ;
212
+
213
+ const result = wordArrayToUint8Array ( decrypted ) ;
214
+
215
+ // console.log('[encryptor][p2p] aesProcessCtr (result)', { result, text: decrypted });
216
+ return result ;
217
+ }
218
+ }
219
+
220
+ decryptObjFromBase64 ( base64 ) {
221
+ const wa = CryptoJS . enc . Base64 . parse ( base64 ) ;
222
+
223
+ const buffer = wordArrayToUint8Array ( wa ) ;
224
+ const decrypted = this . decryptRawPacket ( buffer ) ;
225
+
226
+ const dec = new TextDecoder ( 'utf-8' ) ;
227
+ return JSON . parse ( dec . decode ( decrypted ) )
228
+ }
229
+
230
+ decryptRawPacket ( buffer ) {
231
+ if ( buffer . length < 21 || buffer . length > kMaxIncomingPacketSize ) {
232
+ return null ;
53
233
}
234
+
235
+ const { isOutgoing, type } = this ;
236
+
237
+ const x = ( isOutgoing ? 8 : 0 ) + ( type === 'Signaling' ? 128 : 0 ) ;
238
+ const key = this . p2pKey ;
239
+ // console.log('[encryptor][p2p] decryptRawPacket (x, key)', x, key);
240
+
241
+ const msgKey = buffer . subarray ( 0 , 16 ) ;
242
+ const encryptedData = buffer . subarray ( 16 ) ;
243
+ const encryptedDataSize = buffer . length - 16 ;
244
+
245
+ // console.log('[encryptor][p2p] decryptRawPacket prepareAesKeyIv start', { key, msgKey, x });
246
+ const aesKeyIv = this . prepareAesKeyIv ( key , msgKey , x ) ;
247
+ // console.log('[encryptor][p2p] decryptRawPacket prepareAesKeyIv stop', aesKeyIv);
248
+
249
+ // console.log('[encryptor][p2p] decryptRawPacket aesProcessCtr start', encryptedData, dataSize, aesKeyIv);
250
+ const decryptionBuffer = this . aesProcessCtr ( encryptedData , encryptedDataSize , aesKeyIv , false ) ;
251
+ // console.log('[encryptor][p2p] decryptRawPacket aesProcessCtr stop', decryptionBuffer);
252
+
253
+ const msgKeyLarge = this . concatSHA256 ( [
254
+ key . subarray ( 88 + x , 88 + x + 32 ) ,
255
+ decryptionBuffer
256
+ ] ) ;
257
+
258
+ let msgKeyEquals = true ;
259
+ for ( let i = 0 ; i < 16 ; i ++ ) {
260
+ if ( msgKey [ i ] !== msgKeyLarge [ i + 8 ] ) {
261
+ msgKeyEquals = false ;
262
+ }
263
+ }
264
+ console . log ( '[msgKey]' , msgKey , msgKeyLarge , msgKeyEquals ) ;
265
+ if ( ! msgKeyEquals ) {
266
+ return null ;
267
+ }
268
+
269
+ const resultBuffer = decryptionBuffer . slice ( 4 ) ;
270
+
271
+ return resultBuffer ;
54
272
}
55
273
} ;
0 commit comments