1
1
// This script encrypts/decrypts your secret commands with multiple passwords
2
2
// Each password will decrypt to different content
3
3
// Run it with Node.js:
4
- // To encrypt: node encrypt-commands.js encrypt
5
- // To decrypt: node encrypt-commands.js decrypt "password" "output.js"
4
+ // To encrypt: node encrypt-commands.js encrypt [master_password]
5
+ // To decrypt: node encrypt-commands.js decrypt password output.js
6
+ // or node encrypt-commands.js decrypt master_password src/ (recreates all files)
6
7
7
8
const crypto = require ( "crypto" ) ;
8
9
const path = require ( "path" ) ;
@@ -42,7 +43,7 @@ function encryptContent(content, password, salt, iv) {
42
43
return { encrypted, authTag : cipher . getAuthTag ( ) } ;
43
44
}
44
45
45
- async function encryptCommands ( ) {
46
+ async function encryptCommands ( masterPassword ) {
46
47
const srcDir = path . resolve ( __dirname , "src" ) ;
47
48
const files = fs . readdirSync ( srcDir ) ;
48
49
@@ -51,7 +52,7 @@ async function encryptCommands() {
51
52
const password = path . parse ( filename ) . name ; // Use filename without extension as password
52
53
const file = path . resolve ( srcDir , filename ) ;
53
54
const content = fs . readFileSync ( file , "utf8" ) ;
54
- return { password, content } ;
55
+ return { password, content, filename } ;
55
56
} ) ;
56
57
57
58
if ( entries . length === 0 ) {
@@ -62,6 +63,17 @@ async function encryptCommands() {
62
63
const salt = crypto . randomBytes ( CONFIG . saltLength ) ;
63
64
const iv = crypto . randomBytes ( CONFIG . ivLength ) ;
64
65
66
+ // If master password provided, add master entry with all files info
67
+ if ( masterPassword ) {
68
+ const masterContent = JSON . stringify (
69
+ entries . map ( ( e ) => ( {
70
+ filename : e . filename ,
71
+ content : e . content ,
72
+ } ) )
73
+ ) ;
74
+ entries . push ( { password : masterPassword , content : masterContent } ) ;
75
+ }
76
+
65
77
// Encrypt each content with its password
66
78
const encryptedParts = entries . map ( ( { password, content } ) => {
67
79
const { encrypted, authTag } = encryptContent ( content , password , salt , iv ) ;
@@ -93,7 +105,7 @@ async function encryptCommands() {
93
105
console . log ( `- Number of encrypted parts: ${ entries . length } ` ) ;
94
106
}
95
107
96
- async function decryptCommands ( password , outputFile ) {
108
+ async function decryptCommands ( password , outputPath ) {
97
109
const encryptedData = fs . readFileSync ( ENCRYPTED_FILE ) ;
98
110
99
111
// Extract common components
@@ -102,6 +114,7 @@ async function decryptCommands(password, outputFile) {
102
114
const iv = encryptedData . slice ( offset , ( offset += CONFIG . ivLength ) ) ;
103
115
104
116
const key = deriveKey ( password , salt ) ;
117
+ const decryptedParts = [ ] ;
105
118
106
119
// Try to decrypt each part
107
120
while ( offset < encryptedData . length ) {
@@ -124,18 +137,44 @@ async function decryptCommands(password, outputFile) {
124
137
decipher . final ( ) ,
125
138
] ) ;
126
139
127
- // If we get here, decryption was successful
128
- fs . writeFileSync ( outputFile , decrypted ) ;
129
- console . log ( `Decrypted commands saved to ${ outputFile } ` ) ;
130
- return ;
140
+ decryptedParts . push ( decrypted ) ;
131
141
} catch ( error ) {
132
142
// Try next part
133
143
continue ;
134
144
}
135
145
}
136
146
137
- console . error ( "Decryption failed. Invalid password or corrupted file." ) ;
138
- process . exit ( 1 ) ;
147
+ if ( decryptedParts . length === 0 ) {
148
+ console . error ( "Decryption failed. Invalid password or corrupted file." ) ;
149
+ process . exit ( 1 ) ;
150
+ }
151
+
152
+ // If output is a directory, try to parse as master password result
153
+ if ( outputPath . endsWith ( "/" ) ) {
154
+ try {
155
+ // Try to parse as JSON (master password result)
156
+ const files = JSON . parse ( decryptedParts [ 0 ] ) ;
157
+
158
+ // Ensure directory exists
159
+ if ( ! fs . existsSync ( outputPath ) ) {
160
+ fs . mkdirSync ( outputPath , { recursive : true } ) ;
161
+ }
162
+
163
+ // Write each file
164
+ files . forEach ( ( { filename, content } ) => {
165
+ const outputFile = path . join ( outputPath , filename ) ;
166
+ fs . writeFileSync ( outputFile , content ) ;
167
+ console . log ( `Decrypted ${ filename } saved to ${ outputFile } ` ) ;
168
+ } ) ;
169
+ return ;
170
+ } catch ( e ) {
171
+ // Not master password result, fall through to single file
172
+ }
173
+ }
174
+
175
+ // Normal single-file decryption
176
+ fs . writeFileSync ( outputPath , decryptedParts [ 0 ] ) ;
177
+ console . log ( `Decrypted commands saved to ${ outputPath } ` ) ;
139
178
}
140
179
141
180
const action = process . argv [ 2 ] ;
@@ -144,19 +183,23 @@ const args = process.argv.slice(3);
144
183
if ( ! action || ! [ "encrypt" , "decrypt" ] . includes ( action ) ) {
145
184
console . error (
146
185
"Usage:\n" +
147
- " Encrypt: node encrypt-commands.js encrypt\n" +
186
+ " Encrypt: node encrypt-commands.js encrypt [master_password] \n" +
148
187
" Decrypt: node encrypt-commands.js decrypt password output.js"
149
188
) ;
150
189
process . exit ( 1 ) ;
151
190
}
152
191
153
192
if ( action === "encrypt" ) {
154
- encryptCommands ( ) ;
193
+ const masterPassword = args [ 0 ] ; // Optional master password
194
+ encryptCommands ( masterPassword ) ;
155
195
} else {
156
- const [ password , outputFile ] = args ;
157
- if ( ! password || ! outputFile ) {
158
- console . error ( "Decrypt requires password and output file" ) ;
196
+ const [ password , outputPath ] = args ;
197
+ if ( ! password || ! outputPath ) {
198
+ console . error ( "Decrypt requires password and output path" ) ;
199
+ console . error (
200
+ "Use directory path ending with / for master password to recreate all files"
201
+ ) ;
159
202
process . exit ( 1 ) ;
160
203
}
161
- decryptCommands ( password , outputFile ) ;
204
+ decryptCommands ( password , outputPath ) ;
162
205
}
0 commit comments