mojic 1.2.5 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mojic.js CHANGED
@@ -9,10 +9,12 @@ import { Transform } from 'stream';
9
9
  import { StringDecoder } from 'string_decoder';
10
10
  import { CipherEngine } from '../lib/CipherEngine.js';
11
11
 
12
+ const VERSION = '2.1.1';
13
+
12
14
  program
13
15
  .name('mojic')
14
16
  .description('Obfuscate C source code into emojis')
15
- .version('1.2.5')
17
+ .version(VERSION)
16
18
  .addHelpCommand('help [command]', 'Display help for command')
17
19
  .showHelpAfterError();
18
20
 
@@ -119,7 +121,7 @@ program
119
121
  throw new Error(`'${targetPath}' is a directory. Use -r to process recursively.`);
120
122
  }
121
123
 
122
- console.log(chalk.blue('Initiating Mojic Encryption v1.2...'));
124
+ console.log(chalk.blue(`Initiating Mojic Encryption v${VERSION}...`));
123
125
  if (options.flat) console.log(chalk.yellow(' -> Structural Flattening Enabled'));
124
126
 
125
127
  const password = await promptPassword('Create password for file(s):');
@@ -3,12 +3,15 @@ import { Transform } from 'stream';
3
3
  import { StringDecoder } from 'string_decoder';
4
4
 
5
5
  /**
6
- * MOJIC v1.2.5 CIPHER ENGINE
7
- * "Operation Polymorphic Chaos"
6
+ * MOJIC v2.1.1 CIPHER ENGINE
7
+ * "Operation Ironclad"
8
+ * * Security Upgrades:
9
+ * - KDF: Upgraded from PBKDF2 to Scrypt (Memory-Hard, GPU-Resistant)
10
+ * - PRNG: Upgraded from Xoshiro256** to AES-256-CTR (Cryptographically Secure)
11
+ * - Auth: Extended Key material for higher entropy
8
12
  * * Fixes:
9
- * - Word Boundaries: Prevents splitting variables like 'secretCode'
10
- * - Regex: correctly handles #directives vs keywords
11
- * - Tokenization: Handles combined graphemes (skin tones/modifiers) during decryption
13
+ * - Polymorphic Data: Added XOR Whitening to raw data chunks to hide patterns (e.g., repeating whitespace).
14
+ * - RNG Buffering: Fixed potential byte loss in AES-CTR buffer refill.
12
15
  */
13
16
 
14
17
  // --- EMOJI UNIVERSE GENERATION ---
@@ -33,7 +36,7 @@ const generateUniverse = () => {
33
36
  }
34
37
  }
35
38
  }
36
- return universe; // Expect > 1100 chars
39
+ return universe;
37
40
  };
38
41
 
39
42
  const RAW_UNIVERSE = generateUniverse();
@@ -50,41 +53,50 @@ const C_KEYWORDS = [
50
53
  'include', 'define', 'main', 'printf', 'NULL', '#include', '#define'
51
54
  ];
52
55
 
53
- // --- PRNG: Xoshiro256** ---
54
- class Xoshiro256 {
55
- constructor(seedBuffer) {
56
- if (seedBuffer.length < 32) throw new Error("Seed too short");
57
- this.s = [
58
- seedBuffer.readBigUInt64BE(0),
59
- seedBuffer.readBigUInt64BE(8),
60
- seedBuffer.readBigUInt64BE(16),
61
- seedBuffer.readBigUInt64BE(24)
62
- ];
56
+ // --- PRNG: AES-256-CTR (CSPRNG) ---
57
+ // Replaces Xoshiro with a cryptographically secure stream
58
+ class AesCounterRNG {
59
+ constructor(key, iv) {
60
+ if (key.length !== 32 || iv.length !== 16) throw new Error("Invalid seed length for AES-RNG");
61
+ this.cipher = crypto.createCipheriv('aes-256-ctr', key, iv);
62
+ this.buffer = Buffer.alloc(0);
63
+
64
+ // Encrypt an initial block of zeros to start the keystream
65
+ this._refill();
63
66
  }
64
67
 
65
- next() {
66
- const result = this.rotl(this.s[1] * 5n, 7n) * 9n;
67
- const t = this.s[1] << 17n;
68
-
69
- this.s[2] ^= this.s[0];
70
- this.s[3] ^= this.s[1];
71
- this.s[1] ^= this.s[2];
72
- this.s[0] ^= this.s[3];
68
+ _refill() {
69
+ // Generate 1KB of random keystream at a time
70
+ const zeros = Buffer.alloc(1024);
71
+ const newBytes = this.cipher.update(zeros);
72
+ // Correctly concatenate new bytes to existing buffer to prevent data loss
73
+ this.buffer = Buffer.concat([this.buffer, newBytes]);
74
+ }
73
75
 
74
- this.s[2] ^= t;
75
- this.s[3] = this.rotl(this.s[3], 45n);
76
+ next() {
77
+ if (this.buffer.length < 8) this._refill();
78
+
79
+ // Read 64 bits (BigInt) to match previous interface
80
+ const val = this.buffer.readBigUInt64BE(0);
81
+ this.buffer = this.buffer.subarray(8);
82
+ return val;
83
+ }
76
84
 
77
- return result;
85
+ nextBytes(length) {
86
+ if (this.buffer.length < length) this._refill();
87
+
88
+ // Return a Buffer of requested length
89
+ const bytes = this.buffer.subarray(0, length);
90
+ this.buffer = this.buffer.subarray(length);
91
+ return bytes;
78
92
  }
79
93
 
80
94
  nextFloat() {
95
+ // Convert uint64 to double [0, 1)
96
+ // We take upper 53 bits for standard double precision
81
97
  const val = Number(this.next() >> 11n);
82
98
  return val * (2 ** -53);
83
99
  }
84
-
85
- rotl(x, k) {
86
- return (x << k) | (x >> (64n - k));
87
- }
88
100
  }
89
101
 
90
102
  export class CipherEngine {
@@ -96,24 +108,31 @@ export class CipherEngine {
96
108
  this.dataReverseMap = new Map();
97
109
  this.isReady = false;
98
110
  this.hmac = null;
99
- this.lineLength = 0; // For wrapping
111
+ this.lineLength = 0;
100
112
  }
101
113
 
102
114
  async init(existingSaltHex = null, expectedAuthCheck = null) {
103
115
  this.salt = existingSaltHex
104
116
  ? Buffer.from(existingSaltHex, 'hex')
105
- : crypto.randomBytes(16);
117
+ : crypto.randomBytes(32); // Increased salt size to 32 bytes
106
118
 
119
+ // SECURITY UPGRADE: Scrypt instead of PBKDF2
120
+ // N=16384, r=8, p=1 are standard secure defaults
107
121
  const derivedKey = await new Promise((resolve, reject) => {
108
- crypto.pbkdf2(this.password, this.salt, 100000, 64, 'sha512', (err, key) => {
122
+ crypto.scrypt(this.password, this.salt, 80, { N: 16384, r: 8, p: 1 }, (err, key) => {
109
123
  if (err) reject(err); else resolve(key);
110
124
  });
111
125
  });
112
126
 
113
- const seedBuffer = derivedKey.subarray(0, 32);
114
- this.authKey = derivedKey.subarray(32, 64);
127
+ // Split 80 bytes of key material:
128
+ // 0-32: AES Key (32 bytes)
129
+ // 32-48: AES IV (16 bytes)
130
+ // 48-80: HMAC Auth Key (32 bytes)
131
+
132
+ const rngKey = derivedKey.subarray(0, 32);
133
+ const rngIv = derivedKey.subarray(32, 48);
134
+ this.authKey = derivedKey.subarray(48, 80);
115
135
 
116
- // Check password correctness immediately if Auth Check is provided
117
136
  if (expectedAuthCheck) {
118
137
  const calculatedAuthCheck = this.authKey.subarray(0, 4).toString('hex');
119
138
  if (calculatedAuthCheck !== expectedAuthCheck) {
@@ -121,7 +140,8 @@ export class CipherEngine {
121
140
  }
122
141
  }
123
142
 
124
- this.rng = new Xoshiro256(seedBuffer);
143
+ // Initialize CSPRNG
144
+ this.rng = new AesCounterRNG(rngKey, rngIv);
125
145
 
126
146
  const shuffled = this._shuffleArray([...RAW_UNIVERSE]);
127
147
 
@@ -146,6 +166,7 @@ export class CipherEngine {
146
166
  }
147
167
 
148
168
  _shuffleArray(array) {
169
+ // Fisher-Yates shuffle using CSPRNG
149
170
  for (let i = array.length - 1; i > 0; i--) {
150
171
  const j = Math.floor(this.rng.nextFloat() * (i + 1));
151
172
  [array[i], array[j]] = [array[j], array[i]];
@@ -176,9 +197,16 @@ export class CipherEngine {
176
197
  hexString += index.toString(16);
177
198
  }
178
199
 
200
+ // Salt is now variable length (usually 32 bytes = 64 hex chars),
201
+ // AuthCheck is always last 4 bytes (8 hex chars)
202
+ const totalLen = hexString.length;
203
+ if (totalLen < 8) throw new Error("Header too short");
204
+
205
+ const authStart = totalLen - 8;
206
+
179
207
  return {
180
- saltHex: hexString.substring(0, 32),
181
- authCheckHex: hexString.length >= 40 ? hexString.substring(32, 40) : null
208
+ saltHex: hexString.substring(0, authStart),
209
+ authCheckHex: hexString.substring(authStart)
182
210
  };
183
211
  }
184
212
 
@@ -193,11 +221,9 @@ export class CipherEngine {
193
221
  transform(chunk, encoding, callback) {
194
222
  const str = chunk.toString('utf8');
195
223
 
196
- // Separate alpha keywords (int, void) from symbols (#include)
197
224
  const alphaKeywords = C_KEYWORDS.filter(k => /^\w+$/.test(k)).sort((a,b)=>b.length-a.length).join('|');
198
225
  const symKeywords = C_KEYWORDS.filter(k => !/^\w+$/.test(k)).map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
199
226
 
200
- // Match: \b(int|void)\b OR (#include)
201
227
  const regex = new RegExp(`(\\b(?:${alphaKeywords})\\b|(?:${symKeywords}))`, 'g');
202
228
 
203
229
  const parts = str.split(regex);
@@ -227,7 +253,12 @@ export class CipherEngine {
227
253
  const chunk = buffer.subarray(0, 5);
228
254
  buffer = buffer.subarray(5);
229
255
 
230
- const enc = engine._encodeBase1024(chunk);
256
+ // XOR Whitening: Hide patterns (like spaces) by XORing with RNG stream
257
+ const mask = engine.rng.nextBytes(5);
258
+ const maskedChunk = Buffer.alloc(5);
259
+ for(let i=0; i<5; i++) maskedChunk[i] = chunk[i] ^ mask[i];
260
+
261
+ const enc = engine._encodeBase1024(maskedChunk);
231
262
  engine.hmac.update(enc);
232
263
  this.push(engine._wrapOutput(enc));
233
264
  }
@@ -240,7 +271,13 @@ export class CipherEngine {
240
271
  if (buffer.length > 0) {
241
272
  const padded = Buffer.alloc(5);
242
273
  buffer.copy(padded);
243
- const enc = engine._encodeBase1024(padded);
274
+
275
+ // XOR Whitening for final block
276
+ const mask = engine.rng.nextBytes(5);
277
+ const maskedChunk = Buffer.alloc(5);
278
+ for(let i=0; i<5; i++) maskedChunk[i] = padded[i] ^ mask[i];
279
+
280
+ const enc = engine._encodeBase1024(maskedChunk);
244
281
  engine.hmac.update(enc);
245
282
  this.push(engine._wrapOutput(enc));
246
283
  }
@@ -288,7 +325,13 @@ export class CipherEngine {
288
325
  if (buf.length === 0) return Buffer.alloc(0);
289
326
  const padded = Buffer.alloc(5);
290
327
  buf.copy(padded);
291
- const enc = this._encodeBase1024(padded);
328
+
329
+ // XOR Whitening
330
+ const mask = this.rng.nextBytes(5);
331
+ const maskedChunk = Buffer.alloc(5);
332
+ for(let i=0; i<5; i++) maskedChunk[i] = padded[i] ^ mask[i];
333
+
334
+ const enc = this._encodeBase1024(maskedChunk);
292
335
  this.hmac.update(enc);
293
336
  return this._wrapOutput(enc);
294
337
  }
@@ -309,11 +352,8 @@ export class CipherEngine {
309
352
  const segments = [...segmenter.segment(str)];
310
353
 
311
354
  for (const { segment } of segments) {
312
- if (segment.match(/\s/)) continue; // Skip wraps
355
+ if (segment.match(/\s/)) continue;
313
356
 
314
- // FIX: Some emojis in the universe (like skin tones 0x1F3FB) are modifiers.
315
- // If they appear next to another emoji, Intl.Segmenter merges them.
316
- // We must split them back into atomic code points to preserve token count.
317
357
  const atoms = [...segment];
318
358
 
319
359
  for (const atom of atoms) {
@@ -322,7 +362,6 @@ export class CipherEngine {
322
362
  if (emojiBuffer.length > FOOTER_LEN) {
323
363
  const emo = emojiBuffer.shift();
324
364
  engine._processDecryptToken(emo, this);
325
- // Feed actual atomic tokens to HMAC to match encryption
326
365
  engine.hmac.update(Buffer.from(emo));
327
366
  }
328
367
  }
@@ -373,7 +412,6 @@ export class CipherEngine {
373
412
  const originalEmo = this.keywordEmojis[baseIdx];
374
413
  const keyword = this.keywordReverseMap.get(originalEmo);
375
414
 
376
- // Safety: This buffer should be empty if the stream is synced.
377
415
  if (this.decodeDataBuf.length > 0) {
378
416
  this.decodeDataBuf = [];
379
417
  }
@@ -383,8 +421,17 @@ export class CipherEngine {
383
421
  } else if (this.dataReverseMap.has(emo)) {
384
422
  this.decodeDataBuf.push(this.dataReverseMap.get(emo));
385
423
  if (this.decodeDataBuf.length === 4) {
386
- const chunk = this._decodeBase1024(this.decodeDataBuf);
424
+ // We have a full encoded chunk (4 emojis)
425
+ const maskedChunk = this._decodeBase1024(this.decodeDataBuf);
387
426
  this.decodeDataBuf = [];
427
+
428
+ // Get mask from RNG (Synchronized with encryption)
429
+ const mask = this.rng.nextBytes(5);
430
+ const chunk = Buffer.alloc(5);
431
+
432
+ // XOR Back to get plaintext
433
+ for(let i=0; i<5; i++) chunk[i] = maskedChunk[i] ^ mask[i];
434
+
388
435
  const cleanChunk = chunk.filter(b => b !== 0x00);
389
436
  stream.push(cleanChunk);
390
437
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mojic",
3
- "version": "1.2.5",
3
+ "version": "2.1.1",
4
4
  "description": "Obfuscate C source code into encrypted, password-seeded emoji streams.",
5
5
  "main": "bin/mojic.js",
6
6
  "bin": {
@@ -20,6 +20,7 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
+ "@notamitgamer/mojic": "^1.2.5",
23
24
  "chalk": "^5.3.0",
24
25
  "commander": "^11.1.0",
25
26
  "inquirer": "^9.2.12"