1
- const AES = require ( 'crypto-js/aes.js' ) ;
2
- const CryptoJS = require ( 'crypto-js/crypto-js.js' ) ;
3
1
const prompt = require ( 'electron-prompt' ) ;
4
2
/**
5
3
* A class that handles `encryption-*` web events in the renderer process
@@ -45,10 +43,19 @@ class EncryptionService {
45
43
}
46
44
47
45
async encodeAes ( data , passphrase ) {
48
- // Todo: this looks really dangerous to run file encryption in the main
49
- // thread (of the renderer process). Consider other options.
50
- const encrypted = AES . encrypt ( data , passphrase ) ;
51
- return encrypted . toString ( ) ;
46
+ // see https://gist.github.com/chrisveness/43bcda93af9f646d083fad678071b90a
47
+ const pwUtf8 = new TextEncoder ( ) . encode ( passphrase ) ;
48
+ const pwHash = await crypto . subtle . digest ( 'SHA-256' , pwUtf8 ) ;
49
+ const iv = crypto . getRandomValues ( new Uint8Array ( 12 ) ) ;
50
+ const alg = { name : 'AES-GCM' , iv : iv } ;
51
+ const key = await crypto . subtle . importKey ( 'raw' , pwHash , alg , false , [ 'encrypt' ] ) ;
52
+ const ptUint8 = new TextEncoder ( ) . encode ( data ) ;
53
+ const ctBuffer = await crypto . subtle . encrypt ( alg , key , ptUint8 ) ;
54
+ const ctArray = Array . from ( new Uint8Array ( ctBuffer ) ) ;
55
+ const ctStr = ctArray . map ( byte => String . fromCharCode ( byte ) ) . join ( '' ) ;
56
+ const ctBase64 = btoa ( ctStr ) ;
57
+ const ivHex = Array . from ( iv ) . map ( b => ( '00' + b . toString ( 16 ) ) . slice ( - 2 ) ) . join ( '' ) ;
58
+ return ivHex + ctBase64 ;
52
59
}
53
60
54
61
async decode ( method , opts ) {
@@ -58,7 +65,7 @@ class EncryptionService {
58
65
}
59
66
}
60
67
61
- async decodeAes ( data , passphrase ) {
68
+ async decodeAes ( ciphertext , passphrase ) {
62
69
if ( passphrase === undefined ) {
63
70
const win = require ( 'electron' ) . remote . getCurrentWindow ( ) ;
64
71
passphrase = await prompt ( {
@@ -70,8 +77,16 @@ class EncryptionService {
70
77
}
71
78
}
72
79
try {
73
- const bytes = AES . decrypt ( data , passphrase ) ;
74
- return bytes . toString ( CryptoJS . enc . Utf8 ) ;
80
+ const pwUtf8 = new TextEncoder ( ) . encode ( passphrase ) ;
81
+ const pwHash = await crypto . subtle . digest ( 'SHA-256' , pwUtf8 ) ;
82
+ const iv = ciphertext . slice ( 0 , 24 ) . match ( / .{ 2 } / g) . map ( byte => parseInt ( byte , 16 ) ) ;
83
+ const alg = { name : 'AES-GCM' , iv : new Uint8Array ( iv ) } ;
84
+ const key = await crypto . subtle . importKey ( 'raw' , pwHash , alg , false , [ 'decrypt' ] ) ;
85
+ const ctStr = atob ( ciphertext . slice ( 24 ) ) ;
86
+ const ctUint8 = new Uint8Array ( ctStr . match ( / [ \s \S ] / g) . map ( ch => ch . charCodeAt ( 0 ) ) ) ;
87
+ const plainBuffer = await crypto . subtle . decrypt ( alg , key , ctUint8 ) ;
88
+ const plaintext = new TextDecoder ( ) . decode ( plainBuffer ) ;
89
+ return plaintext ;
75
90
} catch ( _ ) {
76
91
throw new Error ( 'Invalid password.' ) ;
77
92
}
0 commit comments