mojic

Obfuscate C source code into e...
Log | Files | Refs | README | LICENSE

CipherEngine.js (16703B)


      1 import crypto from 'crypto';
      2 import { Transform } from 'stream';
      3 import { StringDecoder } from 'string_decoder';
      4 
      5 /**
      6  * MOJIC v2.1.5 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
     12  * * Fixes:
     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.
     15  */
     16 
     17 // --- EMOJI UNIVERSE GENERATION ---
     18 const HEADER_ALPHABET = ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗'];
     19 
     20 const generateUniverse = () => {
     21     const universe = [];
     22     const ranges = [
     23         [0x1F600, 0x1F64F], // Emoticons
     24         [0x1F300, 0x1F5FF], // Misc Symbols (Contains modifiers)
     25         [0x1F680, 0x1F6FF], // Transport
     26         [0x1F900, 0x1F9FF]  // Supplemental
     27     ];
     28 
     29     const headerSet = new Set(HEADER_ALPHABET);
     30 
     31     for (const [start, end] of ranges) {
     32         for (let code = start; code <= end; code++) {
     33             const char = String.fromCodePoint(code);
     34             if (!headerSet.has(char)) {
     35                 universe.push(char);
     36             }
     37         }
     38     }
     39     return universe; 
     40 };
     41 
     42 const RAW_UNIVERSE = generateUniverse();
     43 
     44 if (RAW_UNIVERSE.length < 1080) {
     45     throw new Error("Critical: Emoji Universe generation failed to produce enough tokens.");
     46 }
     47 
     48 const C_KEYWORDS = [
     49     'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
     50     'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if',
     51     'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static',
     52     'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
     53     'include', 'define', 'main', 'printf', 'NULL', '#include', '#define'
     54 ];
     55 
     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();
     66     }
     67 
     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     }
     75 
     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     }
     84 
     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;
     92     }
     93 
     94     nextFloat() {
     95         // Convert uint64 to double [0, 1)
     96         // We take upper 53 bits for standard double precision
     97         const val = Number(this.next() >> 11n);
     98         return val * (2 ** -53); 
     99     }
    100 }
    101 
    102 export class CipherEngine {
    103     constructor(password) {
    104         this.password = password;
    105         this.keywordMap = new Map(); 
    106         this.keywordReverseMap = new Map(); 
    107         this.dataAlphabet = []; 
    108         this.dataReverseMap = new Map(); 
    109         this.isReady = false;
    110         this.hmac = null; 
    111         this.lineLength = 0;
    112     }
    113 
    114     async init(existingSaltHex = null, expectedAuthCheck = null) {
    115         this.salt = existingSaltHex 
    116             ? Buffer.from(existingSaltHex, 'hex') 
    117             : crypto.randomBytes(32); // Increased salt size to 32 bytes
    118 
    119         // SECURITY UPGRADE: Scrypt instead of PBKDF2
    120         // N=16384, r=8, p=1 are standard secure defaults
    121         const derivedKey = await new Promise((resolve, reject) => {
    122             crypto.scrypt(this.password, this.salt, 80, { N: 16384, r: 8, p: 1 }, (err, key) => {
    123                 if (err) reject(err); else resolve(key);
    124             });
    125         });
    126 
    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);
    135         
    136         if (expectedAuthCheck) {
    137             const calculatedAuthCheck = this.authKey.subarray(0, 4).toString('hex');
    138             if (calculatedAuthCheck !== expectedAuthCheck) {
    139                 throw new Error("WRONG_PASSWORD");
    140             }
    141         }
    142 
    143         // Initialize CSPRNG
    144         this.rng = new AesCounterRNG(rngKey, rngIv);
    145 
    146         const shuffled = this._shuffleArray([...RAW_UNIVERSE]);
    147 
    148         let ptr = 0;
    149         this.keywordEmojis = [];
    150         for (const kw of C_KEYWORDS) {
    151             const emo = shuffled[ptr++];
    152             this.keywordEmojis.push(emo); 
    153             this.keywordMap.set(kw, emo); 
    154             this.keywordReverseMap.set(emo, kw);
    155         }
    156 
    157         this.dataAlphabet = shuffled.slice(ptr, ptr + 1024);
    158         if (this.dataAlphabet.length < 1024) throw new Error("Not enough emojis for Base-1024");
    159         
    160         this.dataAlphabet.forEach((emo, idx) => {
    161             this.dataReverseMap.set(emo, idx);
    162         });
    163 
    164         this.hmac = crypto.createHmac('sha256', this.authKey);
    165         this.isReady = true;
    166     }
    167 
    168     _shuffleArray(array) {
    169         // Fisher-Yates shuffle using CSPRNG
    170         for (let i = array.length - 1; i > 0; i--) {
    171             const j = Math.floor(this.rng.nextFloat() * (i + 1));
    172             [array[i], array[j]] = [array[j], array[i]];
    173         }
    174         return array;
    175     }
    176 
    177     _encodeHeader() {
    178         const saltHex = this.salt.toString('hex');
    179         const authCheck = this.authKey.subarray(0, 4).toString('hex'); 
    180         
    181         let headerStr = '';
    182         for (const char of (saltHex + authCheck)) {
    183             const val = parseInt(char, 16);
    184             headerStr += HEADER_ALPHABET[val];
    185         }
    186         return headerStr + '\n';
    187     }
    188 
    189     static decodeHeader(headerStr) {
    190         let hexString = '';
    191         const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
    192         const segments = segmenter.segment(headerStr.trim());
    193 
    194         for (const { segment } of segments) {
    195             const index = HEADER_ALPHABET.indexOf(segment);
    196             if (index === -1) throw new Error("Invalid Header format.");
    197             hexString += index.toString(16);
    198         }
    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         
    207         return { 
    208             saltHex: hexString.substring(0, authStart),
    209             authCheckHex: hexString.substring(authStart)
    210         };
    211     }
    212 
    213     // --- STREAMING ENCRYPTION ---
    214 
    215     getEncryptStream() {
    216         if (!this.isReady) throw new Error("Engine not initialized");
    217         const engine = this;
    218         let buffer = Buffer.alloc(0); 
    219         
    220         return new Transform({
    221             transform(chunk, encoding, callback) {
    222                 const str = chunk.toString('utf8');
    223                 
    224                 const alphaKeywords = C_KEYWORDS.filter(k => /^\w+$/.test(k)).sort((a,b)=>b.length-a.length).join('|');
    225                 const symKeywords = C_KEYWORDS.filter(k => !/^\w+$/.test(k)).map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
    226                 
    227                 const regex = new RegExp(`(\\b(?:${alphaKeywords})\\b|(?:${symKeywords}))`, 'g');
    228                 
    229                 const parts = str.split(regex); 
    230                 
    231                 for (const part of parts) {
    232                     if (!part) continue;
    233 
    234                     if (C_KEYWORDS.includes(part)) {
    235                         if (buffer.length > 0) {
    236                             this.push(engine._flushDataBuffer(buffer));
    237                             buffer = Buffer.alloc(0);
    238                         }
    239 
    240                         const baseIdx = engine.keywordEmojis.indexOf(engine.keywordMap.get(part));
    241                         const shift = Number(engine.rng.next() % BigInt(engine.keywordEmojis.length));
    242                         const newIdx = (baseIdx + shift) % engine.keywordEmojis.length;
    243                         const polyEmoji = engine.keywordEmojis[newIdx];
    244                         
    245                         const outBuf = Buffer.from(polyEmoji);
    246                         engine.hmac.update(outBuf);
    247                         this.push(engine._wrapOutput(outBuf));
    248 
    249                     } else {
    250                         buffer = Buffer.concat([buffer, Buffer.from(part, 'utf8')]);
    251                         
    252                         while (buffer.length >= 5) {
    253                             const chunk = buffer.subarray(0, 5);
    254                             buffer = buffer.subarray(5);
    255                             
    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);
    262                             engine.hmac.update(enc);
    263                             this.push(engine._wrapOutput(enc));
    264                         }
    265                     }
    266                 }
    267                 callback();
    268             },
    269             
    270             flush(callback) {
    271                 if (buffer.length > 0) {
    272                     const padded = Buffer.alloc(5);
    273                     buffer.copy(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);
    281                     engine.hmac.update(enc);
    282                     this.push(engine._wrapOutput(enc));
    283                 }
    284 
    285                 const digest = engine.hmac.digest(); 
    286                 let footerStr = '';
    287                 for (const byte of digest) {
    288                     const hex = byte.toString(16).padStart(2, '0');
    289                     for (const char of hex) {
    290                         const val = parseInt(char, 16);
    291                         footerStr += HEADER_ALPHABET[val];
    292                     }
    293                 }
    294                 this.push(Buffer.from('\n' + footerStr));
    295                 callback();
    296             }
    297         });
    298     }
    299 
    300     _wrapOutput(bufferChunk) {
    301         this.lineLength += bufferChunk.length;
    302         if (this.lineLength > 300) { 
    303             this.lineLength = 0;
    304             return Buffer.concat([bufferChunk, Buffer.from('\n')]);
    305         }
    306         return bufferChunk;
    307     }
    308 
    309     _encodeBase1024(buffer5) {
    310         let val = 0n;
    311         for (let i = 0; i < 5; i++) {
    312             val += BigInt(buffer5[i]) * (256n ** BigInt(i));
    313         }
    314         
    315         let output = '';
    316         for (let i = 0; i < 4; i++) {
    317             const idx = Number(val % 1024n);
    318             val = val / 1024n;
    319             output += this.dataAlphabet[idx];
    320         }
    321         return Buffer.from(output);
    322     }
    323 
    324     _flushDataBuffer(buf) {
    325         if (buf.length === 0) return Buffer.alloc(0);
    326         const padded = Buffer.alloc(5);
    327         buf.copy(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);
    335         this.hmac.update(enc);
    336         return this._wrapOutput(enc);
    337     }
    338 
    339     // --- STREAMING DECRYPTION ---
    340 
    341     getDecryptStream() {
    342         if (!this.isReady) throw new Error("Engine not initialized");
    343         const engine = this;
    344         const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
    345         
    346         const FOOTER_LEN = 64; 
    347         let emojiBuffer = []; 
    348 
    349         return new Transform({
    350             transform(chunk, encoding, callback) {
    351                 const str = chunk.toString('utf8');
    352                 const segments = [...segmenter.segment(str)];
    353                 
    354                 for (const { segment } of segments) {
    355                     if (segment.match(/\s/)) continue; 
    356 
    357                     const atoms = [...segment];
    358 
    359                     for (const atom of atoms) {
    360                         emojiBuffer.push(atom);
    361                         
    362                         if (emojiBuffer.length > FOOTER_LEN) {
    363                             const emo = emojiBuffer.shift();
    364                             engine._processDecryptToken(emo, this);
    365                             engine.hmac.update(Buffer.from(emo));
    366                         }
    367                     }
    368                 }
    369                 callback();
    370             },
    371             
    372             flush(callback) {
    373                 if (emojiBuffer.length !== FOOTER_LEN) {
    374                     this.emit('error', new Error("File corrupted or truncated (No Footer)"));
    375                     return;
    376                 }
    377 
    378                 const footerStr = emojiBuffer.join('');
    379                 const calcDigest = engine.hmac.digest('hex'); 
    380                 
    381                 let footerHex = '';
    382                 try {
    383                     for (const char of footerStr) {
    384                         const idx = HEADER_ALPHABET.indexOf(char);
    385                         if (idx === -1) throw new Error();
    386                         footerHex += idx.toString(16);
    387                     }
    388                 } catch (e) {
    389                     this.emit('error', new Error("Invalid Integrity Seal"));
    390                     return;
    391                 }
    392 
    393                 const footerBuffer = Buffer.from(footerHex, 'hex');
    394                 const calcBuffer = Buffer.from(calcDigest, 'hex');
    395 
    396                 if (footerBuffer.length !== calcBuffer.length || !crypto.timingSafeEqual(footerBuffer, calcBuffer)) {
    397                     this.emit('error', new Error("FILE_TAMPERED"));
    398                     return;
    399                 }
    400                 callback();
    401             }
    402         });
    403     }
    404 
    405     decodeDataBuf = []; 
    406 
    407     _processDecryptToken(emo, stream) {
    408         if (this.keywordReverseMap.has(emo)) {
    409             const currentR = Number(this.rng.next() % BigInt(this.keywordEmojis.length));
    410             const emoIdx = this.keywordEmojis.indexOf(emo);
    411             
    412             let baseIdx = (emoIdx - currentR) % this.keywordEmojis.length;
    413             if (baseIdx < 0) baseIdx += this.keywordEmojis.length;
    414             
    415             const originalEmo = this.keywordEmojis[baseIdx];
    416             const keyword = this.keywordReverseMap.get(originalEmo);
    417             
    418             if (this.decodeDataBuf.length > 0) {
    419                 this.decodeDataBuf = [];
    420             }
    421             
    422             stream.push(keyword);
    423 
    424         } else if (this.dataReverseMap.has(emo)) {
    425             this.decodeDataBuf.push(this.dataReverseMap.get(emo));
    426             if (this.decodeDataBuf.length === 4) {
    427                 // We have a full encoded chunk (4 emojis)
    428                 const maskedChunk = this._decodeBase1024(this.decodeDataBuf);
    429                 this.decodeDataBuf = [];
    430                 
    431                 // Get mask from RNG (Synchronized with encryption)
    432                 const mask = this.rng.nextBytes(5);
    433                 const chunk = Buffer.alloc(5);
    434                 
    435                 // XOR Back to get plaintext
    436                 for(let i=0; i<5; i++) chunk[i] = maskedChunk[i] ^ mask[i];
    437 
    438                 const cleanChunk = chunk.filter(b => b !== 0x00);
    439                 stream.push(cleanChunk);
    440             }
    441         }
    442     }
    443 
    444     _decodeBase1024(indices) {
    445         let val = 0n;
    446         for (let i = 3; i >= 0; i--) {
    447             val = (val * 1024n) + BigInt(indices[i]);
    448         }
    449         
    450         const buf = Buffer.alloc(5);
    451         for (let i = 0; i < 5; i++) {
    452             buf[i] = Number(val % 256n);
    453             val = val / 256n;
    454         }
    455         return buf;
    456     }
    457 }
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror