Skip to content

Commit 56a919b

Browse files
committed
Add programs
1 parent 6fa0918 commit 56a919b

File tree

3 files changed

+128
-147
lines changed

3 files changed

+128
-147
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,3 @@ dist
202202
# .pnp.*
203203

204204
# End of https://www.toptal.com/developers/gitignore/api/hugo,node,go,yarn
205-
utils/src/
17.4 MB
Binary file not shown.

utils/encrypt-commands.js

Lines changed: 128 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
// This script encrypts/decrypts your secret commands with multiple passwords
2-
// Each password will decrypt to different content
3-
// Run it with Node.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)
1+
// Encrypt/decrypt multi-part commands; binary-safe; auto-injects __SECRETS__
2+
// Usage:
3+
// node encrypt-commands.js encrypt [master_password]
4+
// node encrypt-commands.js decrypt password output.js // single
5+
// node encrypt-commands.js decrypt master_password src/ // restore all
76

87
const crypto = require("crypto");
98
const path = require("path");
@@ -14,191 +13,174 @@ const ENCRYPTED_FILE = path.resolve(
1413
"../static/terminal-window/encrypted-commands.js.enc"
1514
);
1615

17-
// Simplified encryption configuration
18-
const CONFIG = {
19-
iterations: 1000000,
20-
keyLength: 32,
21-
ivLength: 16,
22-
saltLength: 32,
23-
tagLength: 16,
24-
};
16+
const CONFIG = { iterations: 1_000_000, keyLength: 32, ivLength: 16, saltLength: 32, tagLength: 16 };
17+
const BINARY_EXT = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".webm"]);
2518

2619
function deriveKey(password, salt) {
27-
return crypto.pbkdf2Sync(
28-
password,
29-
salt,
30-
CONFIG.iterations,
31-
CONFIG.keyLength,
32-
"sha512"
33-
);
20+
return crypto.pbkdf2Sync(password, salt, CONFIG.iterations, CONFIG.keyLength, "sha512");
3421
}
3522

36-
function encryptContent(content, password, salt, iv) {
23+
function encryptBuffer(buffer, password, salt, iv) {
3724
const key = deriveKey(password, salt);
3825
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
39-
const encrypted = Buffer.concat([
40-
cipher.update(content, "utf8"),
41-
cipher.final(),
42-
]);
26+
const encrypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
4327
return { encrypted, authTag: cipher.getAuthTag() };
4428
}
4529

30+
function walk(dir, basedir, out = []) {
31+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
32+
const abs = path.join(dir, ent.name);
33+
if (ent.isDirectory()) walk(abs, basedir, out);
34+
else if (ent.isFile()) {
35+
const rel = path.posix.normalize(path.relative(basedir, abs).split(path.sep).join("/"));
36+
out.push(rel);
37+
}
38+
}
39+
return out;
40+
}
41+
42+
function isBinaryByExt(rel) {
43+
return BINARY_EXT.has(path.extname(rel).toLowerCase());
44+
}
45+
46+
function makeUint8Literal(buf) {
47+
const nums = Array.from(buf);
48+
return `new Uint8Array([${nums.join(",")}])`;
49+
}
50+
51+
function buildSecretsBlock(assets) {
52+
const lines = [];
53+
lines.push("(function(){");
54+
lines.push(" try { if (!window.__SECRETS__) window.__SECRETS__ = Object.create(null);");
55+
for (const a of assets) {
56+
lines.push(` window.__SECRETS__[${JSON.stringify(a.rel)}] = ${makeUint8Literal(a.buffer)};`);
57+
}
58+
lines.push(" } catch(e){ console.error('SECRETS inject failed', e); }");
59+
lines.push("})();");
60+
return lines.join("\n");
61+
}
62+
4663
async function encryptCommands(masterPassword) {
4764
const srcDir = path.resolve(__dirname, "src");
48-
const files = fs.readdirSync(srcDir);
49-
50-
// Each file in src becomes a password:content pair
51-
const entries = files.map((filename) => {
52-
const password = path.parse(filename).name; // Use filename without extension as password
53-
const file = path.resolve(srcDir, filename);
54-
const content = fs.readFileSync(file, "utf8");
55-
return { password, content, filename };
65+
if (!fs.existsSync(srcDir)) throw new Error("Missing src/ directory");
66+
67+
const relPaths = walk(srcDir, srcDir);
68+
if (!relPaths.length) throw new Error("No files found in src/");
69+
70+
const files = relPaths.map((rel) => {
71+
const abs = path.join(srcDir, rel);
72+
return { rel, buffer: fs.readFileSync(abs), isBinary: isBinaryByExt(rel) };
5673
});
5774

58-
if (entries.length === 0) {
59-
throw new Error("No files found in src directory");
60-
}
75+
// All custom cat assets to embed
76+
const onekoAssets = files.filter((f) => f.rel.startsWith("oneko_custom/"));
77+
78+
// Build encrypted parts (password = filename without extension, as before)
79+
const entries = files.map((f) => {
80+
const parsed = path.parse(f.rel);
81+
const password = parsed.name;
82+
83+
// For su:* login payloads, **prepend** the secrets so they exist before file runs
84+
if (/^su:[^:]+:.+$/i.test(password) && parsed.ext === ".js") {
85+
const jsText = f.buffer.toString("utf8");
86+
const secretsCode = buildSecretsBlock(onekoAssets);
87+
const merged = Buffer.from(
88+
"// --- AUTO-INJECTED (oneko_custom assets) ---\n" +
89+
secretsCode +
90+
"\n// --- END AUTO-INJECTED ---\n\n" +
91+
jsText,
92+
"utf8"
93+
);
94+
return { password, buffer: merged, rel: f.rel };
95+
}
96+
return { password, buffer: f.buffer, rel: f.rel };
97+
});
6198

62-
// Use the same salt and IV for all encryptions
99+
// Single salt+IV for whole bundle (keeps legacy layout)
63100
const salt = crypto.randomBytes(CONFIG.saltLength);
64101
const iv = crypto.randomBytes(CONFIG.ivLength);
65102

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-
77-
// Encrypt each content with its password
78-
const encryptedParts = entries.map(({ password, content }) => {
79-
const { encrypted, authTag } = encryptContent(content, password, salt, iv);
103+
const encryptedParts = entries.map(({ password, buffer }) => {
104+
const { encrypted, authTag } = encryptBuffer(buffer, password, salt, iv);
80105
return { encrypted, authTag };
81106
});
82107

83-
// Combine all encrypted parts
84-
// Format: [salt][iv][size1][enc1][tag1][size2][enc2][tag2]...
85-
const parts = [salt, iv];
86-
encryptedParts.forEach(({ encrypted, authTag }) => {
87-
// Add 4-byte size header for each part
88-
const sizeBuffer = Buffer.alloc(4);
89-
sizeBuffer.writeUInt32BE(encrypted.length);
90-
parts.push(sizeBuffer, encrypted, authTag);
91-
});
108+
// Optional master archive (JSON manifest, binary-safe)
109+
if (masterPassword) {
110+
const masterPayload = files.map((f) => ({
111+
filename: f.rel,
112+
isBinary: f.isBinary,
113+
content: f.isBinary ? f.buffer.toString("base64") : f.buffer.toString("utf8"),
114+
}));
115+
const masterBuf = Buffer.from(JSON.stringify(masterPayload), "utf8");
116+
const { encrypted, authTag } = encryptBuffer(masterBuf, masterPassword, salt, iv);
117+
encryptedParts.push({ encrypted, authTag });
118+
}
92119

93-
const result = Buffer.concat(parts);
94-
fs.writeFileSync(ENCRYPTED_FILE, result);
95-
96-
console.log(`Encrypted commands saved to ${ENCRYPTED_FILE}`);
97-
console.log(`\nSecurity parameters:`);
98-
console.log(`- Key derivation: SHA-512`);
99-
console.log(`- Iterations: ${CONFIG.iterations.toLocaleString()}`);
100-
console.log(
101-
`- Salt/Key/IV lengths: ${CONFIG.saltLength * 8}/${CONFIG.keyLength * 8}/${
102-
CONFIG.ivLength * 8
103-
} bits`
104-
);
105-
console.log(`- Number of encrypted parts: ${entries.length}`);
120+
// Write bundle: [salt][iv][sizeN][encN][tagN]...
121+
const parts = [salt, iv];
122+
for (const { encrypted, authTag } of encryptedParts) {
123+
const size = Buffer.alloc(4);
124+
size.writeUInt32BE(encrypted.length);
125+
parts.push(size, encrypted, authTag);
126+
}
127+
fs.writeFileSync(ENCRYPTED_FILE, Buffer.concat(parts));
128+
console.log(`Encrypted → ${ENCRYPTED_FILE} (parts=${encryptedParts.length})`);
106129
}
107130

108131
async function decryptCommands(password, outputPath) {
109-
const encryptedData = fs.readFileSync(ENCRYPTED_FILE);
110-
111-
// Extract common components
112-
let offset = 0;
113-
const salt = encryptedData.slice(offset, (offset += CONFIG.saltLength));
114-
const iv = encryptedData.slice(offset, (offset += CONFIG.ivLength));
115-
132+
const buf = fs.readFileSync(ENCRYPTED_FILE);
133+
let off = 0;
134+
const salt = buf.slice(off, (off += CONFIG.saltLength));
135+
const iv = buf.slice(off, (off += CONFIG.ivLength));
116136
const key = deriveKey(password, salt);
117-
const decryptedParts = [];
118137

119-
// Try to decrypt each part
120-
while (offset < encryptedData.length) {
138+
const outs = [];
139+
while (off < buf.length) {
121140
try {
122-
// Read size of next encrypted part
123-
const size = encryptedData.readUInt32BE(offset);
124-
offset += 4;
125-
126-
// Extract encrypted content and tag
127-
const encrypted = encryptedData.slice(offset, offset + size);
128-
offset += size;
129-
const authTag = encryptedData.slice(offset, offset + CONFIG.tagLength);
130-
offset += CONFIG.tagLength;
131-
132-
// Try to decrypt this part
133-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
134-
decipher.setAuthTag(authTag);
135-
const decrypted = Buffer.concat([
136-
decipher.update(encrypted),
137-
decipher.final(),
138-
]);
139-
140-
decryptedParts.push(decrypted);
141-
} catch (error) {
142-
// Try next part
143-
continue;
144-
}
141+
const size = buf.readUInt32BE(off); off += 4;
142+
const enc = buf.slice(off, off + size); off += size;
143+
const tag = buf.slice(off, off + CONFIG.tagLength); off += CONFIG.tagLength;
144+
const d = crypto.createDecipheriv("aes-256-gcm", key, iv);
145+
d.setAuthTag(tag);
146+
outs.push(Buffer.concat([d.update(enc), d.final()]));
147+
} catch { /* skip wrong key part */ }
145148
}
146-
147-
if (decryptedParts.length === 0) {
148-
console.error("Decryption failed. Invalid password or corrupted file.");
149+
if (!outs.length) {
150+
console.error("Decryption failed (bad password or file).");
149151
process.exit(1);
150152
}
151153

152-
// If output is a directory, try to parse as master password result
153154
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
155+
// Master payload restore
156+
const list = JSON.parse(outs[0].toString("utf8"));
157+
if (!fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
158+
for (const f of list) {
159+
const outFile = path.join(outputPath, f.filename);
160+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
161+
const data = f.isBinary ? Buffer.from(f.content, "base64") : Buffer.from(f.content, "utf8");
162+
fs.writeFileSync(outFile, data);
163+
console.log("Decrypted", f.filename);
172164
}
165+
return;
173166
}
174167

175-
// Normal single-file decryption
176-
fs.writeFileSync(outputPath, decryptedParts[0]);
177-
console.log(`Decrypted commands saved to ${outputPath}`);
168+
fs.writeFileSync(outputPath, outs[0]);
169+
console.log(`Decrypted → ${outputPath}`);
178170
}
179171

180172
const action = process.argv[2];
181173
const args = process.argv.slice(3);
182-
183-
if (!action || !["encrypt", "decrypt"].includes(action)) {
184-
console.error(
185-
"Usage:\n" +
186-
" Encrypt: node encrypt-commands.js encrypt [master_password]\n" +
187-
" Decrypt: node encrypt-commands.js decrypt password output.js"
188-
);
174+
if (!["encrypt", "decrypt"].includes(action)) {
175+
console.error("Usage:\n node encrypt-commands.js encrypt [master_password]\n node encrypt-commands.js decrypt password output.js\n node encrypt-commands.js decrypt master_password src/");
189176
process.exit(1);
190177
}
191-
192178
if (action === "encrypt") {
193-
const masterPassword = args[0]; // Optional master password
194-
encryptCommands(masterPassword);
179+
encryptCommands(args[0]);
195180
} else {
196181
const [password, outputPath] = args;
197182
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-
);
183+
console.error("decrypt requires: password outputPath (use a dir ending with / for master)");
202184
process.exit(1);
203185
}
204186
decryptCommands(password, outputPath);

0 commit comments

Comments
 (0)