@@ -6,27 +6,57 @@ import { KeytarService } from "./keytar.service";
66 * Keytar service and key to save the master key
77 */
88const BATCH_APPLICATION = "BatchExplorer" ;
9- const KEYTAR_KEY = "master" ;
9+ const KEYTAR_KEY = "master-v2 " ;
1010
1111/**
1212 * Length of the initialization vector
1313 */
1414const IV_BYTES = 16 ;
1515
1616/**
17- * Algorithm to use when encrypting
17+ * Current encryption algorithm: AES-256-GCM with 32-byte key
1818 */
19- const ENCRYPT_ALGORITHM = "aes-256-ctr" ;
19+ const ALGORITHM = "aes-256-gcm" ;
20+ const ALGORITHM_VERSION = 2 ;
21+
22+ /**
23+ * GCM tag length for authenticated encryption
24+ */
25+ const GCM_TAG_LENGTH = 16 ;
26+
27+ /**
28+ * Version header length (1 byte)
29+ */
30+ const VERSION_HEADER_LENGTH = 1 ;
31+
32+ /**
33+ * Key size in bytes (32 bytes = 256 bits)
34+ */
35+ const KEY_BYTES = 32 ;
2036
2137// What encoding to use when converting buffer to string
2238const DEFAULT_STRING_ENCODING = "base64" ;
2339
40+ export class UnsupportedEncryptionVersionError extends Error {
41+ constructor ( version : number ) {
42+ super ( `Unsupported encryption version: ${ version } . Please re-authenticate.` ) ;
43+ this . name = "UnsupportedEncryptionVersionError" ;
44+ }
45+ }
46+
2447@Injectable ( { providedIn : "root" } )
2548export class CryptoService {
26- private _masterKey : Promise < string > ;
49+ private _masterKey : Promise < string > | null = null ;
2750
2851 constructor ( private keytar : KeytarService ) {
29- this . _masterKey = this . _loadMasterKey ( ) ;
52+ // Don't load keys in constructor - defer until first use to avoid IPC initialization issues
53+ }
54+
55+ private _ensureMasterKey ( ) : Promise < string > {
56+ if ( ! this . _masterKey ) {
57+ this . _masterKey = this . _loadMasterKey ( ) ;
58+ }
59+ return this . _masterKey ;
3060 }
3161
3262 public async encrypt ( content : Buffer ) : Promise < Buffer > ;
@@ -52,33 +82,56 @@ export class CryptoService {
5282 }
5383
5484 private async _encryptBuffer ( content : Buffer ) : Promise < Buffer > {
55- const key = await this . _masterKey ;
85+ const keyHex = await this . _ensureMasterKey ( ) ;
86+ const key = Buffer . from ( keyHex , "hex" ) as crypto . CipherKey ;
5687 const iv = this . _getIV ( ) ;
57- const cipher = crypto . createCipheriv ( ENCRYPT_ALGORITHM , key , iv ) ;
58- return Buffer . concat ( [ iv , cipher . update ( content ) , cipher . final ( ) ] ) ;
88+
89+ const cipher = crypto . createCipheriv ( ALGORITHM as any , key , iv as crypto . BinaryLike ) ;
90+ const encrypted = cipher . update ( content as crypto . BinaryLike ) ;
91+ cipher . final ( ) ;
92+ const tag = cipher . getAuthTag ( ) ;
93+
94+ // Format: [version(1)] + [iv(16)] + [tag(16)] + [encrypted_data]
95+ return Buffer . concat ( [
96+ new Uint8Array ( [ ALGORITHM_VERSION ] ) ,
97+ iv ,
98+ tag ,
99+ encrypted
100+ ] ) ;
59101 }
60102
61103 private async _decryptBuffer ( content : Buffer ) : Promise < Buffer > {
62- const key = await this . _masterKey ;
63- const iv = content . slice ( 0 , IV_BYTES ) ;
64- const decipher = crypto . createDecipheriv ( ENCRYPT_ALGORITHM , key , iv ) ;
65- return Buffer . concat ( [ decipher . update ( content . slice ( 16 ) ) , decipher . final ( ) ] ) ;
104+ // Verify minimum length and version header
105+ if ( content . length < VERSION_HEADER_LENGTH + IV_BYTES + GCM_TAG_LENGTH ) {
106+ throw new Error ( "Invalid encrypted data: content too short" ) ;
107+ }
108+
109+ const version = content [ 0 ] ;
110+ if ( version !== ALGORITHM_VERSION ) {
111+ throw new UnsupportedEncryptionVersionError ( version ) ;
112+ }
113+
114+ const keyHex = await this . _ensureMasterKey ( ) ;
115+ const key = Buffer . from ( keyHex , "hex" ) as crypto . CipherKey ;
116+ const iv = content . slice ( VERSION_HEADER_LENGTH , VERSION_HEADER_LENGTH + IV_BYTES ) ;
117+ const tag = content . slice ( VERSION_HEADER_LENGTH + IV_BYTES , VERSION_HEADER_LENGTH + IV_BYTES + GCM_TAG_LENGTH ) ;
118+ const encrypted = content . slice ( VERSION_HEADER_LENGTH + IV_BYTES + GCM_TAG_LENGTH ) ;
119+
120+ const decipher = crypto . createDecipheriv ( ALGORITHM as any , key , iv as crypto . BinaryLike ) ;
121+ decipher . setAuthTag ( tag ) ;
122+ return Buffer . concat ( [ decipher . update ( encrypted ) , decipher . final ( ) ] ) ;
66123 }
67124
68125 private async _loadMasterKey ( ) : Promise < string > {
69126 let masterKey = await this . keytar . getPassword ( BATCH_APPLICATION , KEYTAR_KEY ) ;
70127 if ( ! masterKey ) {
71- masterKey = this . _generateMasterKey ( ) ;
128+ masterKey = crypto . randomBytes ( KEY_BYTES ) . toString ( "hex" ) ;
72129 await this . keytar . setPassword ( BATCH_APPLICATION , KEYTAR_KEY , masterKey ) ;
73130 }
74131 return masterKey ;
75132 }
76133
77- private _generateMasterKey ( ) {
78- return crypto . randomBytes ( 16 ) . toString ( "hex" ) ;
79- }
80-
81- private _getIV ( ) {
134+ private _getIV ( ) : Buffer {
82135 return crypto . randomBytes ( IV_BYTES ) ;
83136 }
84137}
0 commit comments