mojic

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

root / lib / CipherEngine.js

CipherEngine.js (16906B)


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