#!/usr/bin/env python3 import argparse import sys import os import base64 import zlib import re import ast import random import string import hashlib import json import struct import urllib.request import shutil from pathlib import Path VERSION = "2.0.0" BASE_URL = "https://enkoala.pages.dev" BANNER = ( "\033[32m\n" " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557\n" " \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u255d\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\n" " \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2554\u255d \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\n" " \u2588\u2588\u2554\u2550\u2550\u255d \u2588\u2588\u2551\u255a\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\n" " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2557\u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\n" " \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d\n" "\033[0m\033[36m\n" f' Code Obfuscation & Encryption Engine v{VERSION}\n' ' "If they can read it, you didn\'t try hard enough."\n' "\033[0m\n" " \033[90m[ HTML ] [ JS ] [ TS ] [ CSS ] [ Python ] [ PHP ] [ Java ] [ C# ] [ Ruby ] [ Lua ] [ LuaU ] [ SQL ]\033[0m\n" ) def print_banner(): print(BANNER) def xor_bytes(data: bytes, key: bytes) -> bytes: result = bytearray(len(data)) key_len = len(key) for i, b in enumerate(data): result[i] = b ^ key[i % key_len] return bytes(result) def generate_key(length: int = 32) -> bytes: return bytes(random.randint(1, 255) for _ in range(length)) def random_var_name(length: int = None) -> str: if length is None: length = random.randint(8, 16) chars = ['O', '0', 'l', '1', 'I'] start_chars = ['O', 'l', 'I'] first = random.choice(start_chars) rest = ''.join(random.choices(chars, k=length - 1)) return first + rest def random_identifier(prefix: str = '_') -> str: chars = string.ascii_letters + string.digits return prefix + ''.join(random.choices(chars, k=random.randint(6, 12))) def int_to_obfuscated_js(n: int) -> str: if n == 0: return '(+[])' if n == 1: return '(+!+[])' if n < 10: return '+'.join(['(+!+[])'] * n) digits = str(n) parts = [] for d in digits: parts.append(int_to_obfuscated_js(int(d))) return '(' + '+'.join(['[' + p + ']' for p in parts]) + '+[])' def js_string_to_array_notation(s: str) -> str: parts = [] for ch in s: code = ord(ch) parts.append(f'String.fromCharCode({code})') return '+'.join(parts) def obfuscate_javascript(code: str) -> str: key = generate_key(16) compressed = zlib.compress(code.encode('utf-8'), level=9) xored = xor_bytes(compressed, key) encoded = base64.b64encode(xored).decode('ascii') key_array = ','.join(str(b) for b in key) v1 = random_var_name() v2 = random_var_name() v3 = random_var_name() v4 = random_var_name() v5 = random_var_name() v6 = random_var_name() v7 = random_var_name() v8 = random_var_name() v9 = random_var_name() vA = random_var_name() noise_vars = [] for _ in range(random.randint(5, 10)): nv = random_var_name() noise_val = random.randint(0, 0xFFFFFF) noise_vars.append(f'var {nv}={noise_val};') noise_block = '\n'.join(noise_vars) self_check_hash = hashlib.sha256(code.encode()).hexdigest()[:16] output = f"""(function(){{ 'use strict'; {noise_block} var {v1}='{encoded}'; var {v2}=[{key_array}]; var {v3}=function(s){{ var b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; var r='',c=0,n=0,a=0; s=s.replace(/[^A-Za-z0-9\\+\\/]/g,''); for(var i=0;i>16)&0xFF,(n>>8)&0xFF,n&0xFF); c=0;n=0; }} }} if(c===2)r+=String.fromCharCode((n>>4)&0xFF); else if(c===3)r+=String.fromCharCode((n>>10)&0xFF,(n>>4)&0xFF); return r; }}; var {v4}=function(d,k){{ var r=new Uint8Array(d.length); for(var i=0;i str: result = re.sub(r'/\*[\s\S]*?\*/', '', code) result = re.sub(r'\s+', ' ', result) result = re.sub(r'\s*([{}:;,>~+])\s*', r'\1', result) result = re.sub(r';\}', '}', result) result = re.sub(r'\s*!\s*important', '!important', result) result = re.sub(r':\s*0px', ':0', result) result = re.sub(r':\s*0em', ':0', result) result = re.sub(r':\s*0rem', ':0', result) result = re.sub(r':\s*0%', ':0', result) def rgb_to_hex(m): r, g, b = int(m.group(1)), int(m.group(2)), int(m.group(3)) return f'#{r:02x}{g:02x}{b:02x}' result = re.sub(r'rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)', rgb_to_hex, result) def shorten_hex(m): h = m.group(1) if len(h) == 6 and h[0]==h[1] and h[2]==h[3] and h[4]==h[5]: return f'#{h[0]}{h[2]}{h[4]}' return m.group(0) result = re.sub(r'#([0-9a-fA-F]{6})', shorten_hex, result) class_map = {} id_map = {} counter = [0] def encode_counter(n): chars = 'abcdefghijklmnopqrstuvwxyz' result = '' n += 1 while n > 0: n -= 1 result = chars[n % 26] + result n //= 26 return '_' + result def replace_class(m): name = m.group(1) if name not in class_map: class_map[name] = encode_counter(counter[0]) counter[0] += 1 return '.' + class_map[name] def replace_id(m): name = m.group(1) if name not in id_map: id_map[name] = encode_counter(counter[0]) counter[0] += 1 return '#' + id_map[name] result = re.sub(r'\.([a-zA-Z][a-zA-Z0-9_-]*)', replace_class, result) result = re.sub(r'#([a-zA-Z][a-zA-Z0-9_-]*)', replace_id, result) map_comment = '' if class_map or id_map: mapping = {'classes': class_map, 'ids': id_map} map_b64 = base64.b64encode(json.dumps(mapping).encode()).decode() map_comment = f'/*!enkoala:{map_b64}*/' return map_comment + result.strip() def obfuscate_html(code: str) -> str: result = re.sub(r'', '', code) result = re.sub(r'\n\s*\n', '\n', result) result = re.sub(r'[ \t]+', ' ', result) result = re.sub(r'>\s+<', '><', result) def encode_inline_js(m): tag_open = m.group(1) js_code = m.group(2) tag_close = m.group(3) if js_code.strip(): obf = obfuscate_javascript(js_code) return f'{tag_open}{obf}{tag_close}' return m.group(0) result = re.sub( r'(]*>)([\s\S]*?)()', encode_inline_js, result, flags=re.IGNORECASE ) def encode_inline_css(m): tag_open = m.group(1) css_code = m.group(2) tag_close = m.group(3) if css_code.strip(): obf = obfuscate_css(css_code) return f'{tag_open}{obf}{tag_close}' return m.group(0) result = re.sub( r'(]*>)([\s\S]*?)()', encode_inline_css, result, flags=re.IGNORECASE ) def obfuscate_attrs(m): tag = m.group(0) def encode_handler(hm): attr = hm.group(1) js = hm.group(2) quote = hm.group(3) return attr + '=' + quote + js + quote return re.sub(r'(on\w+)=["\']([^"\']+)(["\'])', encode_handler, tag) result = re.sub(r'<[^>]+>', obfuscate_attrs, result) return result.strip() def obfuscate_python(code: str) -> str: try: ast.parse(code) except SyntaxError as e: raise ValueError(f"Python syntax error: {e}") try: lines = [] for line in code.splitlines(): stripped = line.strip() if stripped.startswith('#'): continue comment_pos = line.find(' #') if comment_pos > 0: before = line[:comment_pos] if before.count('"') % 2 == 0 and before.count("'") % 2 == 0: line = before lines.append(line) code_no_comments = '\n'.join(lines) except Exception: code_no_comments = code key = generate_key(32) layer1 = zlib.compress(code_no_comments.encode('utf-8'), level=9) layer2 = xor_bytes(layer1, key) layer3_chunks = [] chunk_size = 64 for i in range(0, len(layer2), chunk_size): chunk = layer2[i:i+chunk_size] encoded_chunk = base64.b64encode(chunk).decode('ascii') layer3_chunks.append(encoded_chunk) key_repr = repr(list(key)) chunks_repr = repr(layer3_chunks) v_chunks = random_var_name() v_key = random_var_name() v_combined = random_var_name() v_decoded = random_var_name() v_xored = random_var_name() v_decompressed = random_var_name() v_i = random_var_name() v_b = random_var_name() sentinel = hashlib.md5(code.encode()).hexdigest() noise_lines = [] for _ in range(random.randint(8, 15)): nv = random_var_name() kind = random.randint(0, 3) if kind == 0: noise_lines.append(f'{nv}=lambda {random_var_name()}:{random.randint(0,999)}') elif kind == 1: noise_lines.append(f'{nv}={random.randint(0,0xFFFFFFFF)}') elif kind == 2: noise_lines.append(f'{nv}="{random_var_name()}"') else: noise_lines.append(f'{nv}=({random.randint(0,100)},{random.randint(0,100)})') noise_block = '\n'.join(noise_lines) wrapper = f'''import base64 as _b64,zlib as _z,sys as _s {noise_block} {v_chunks}={chunks_repr} {v_key}=bytes({key_repr}) {v_combined}=b"".join(_b64.b64decode(_c) for _c in {v_chunks}) {v_xored}=bytes({v_b}^{v_key}[{v_i}%len({v_key})] for {v_i},{v_b} in enumerate({v_combined})) {v_decompressed}=_z.decompress({v_xored}) exec(compile({v_decompressed},"","exec"),{{"__name__":"__main__","__file__":getattr(_s.modules.get("__main__",None),"__file__",""),"__builtins__":__builtins__}})\n''' return wrapper def obfuscate_typescript(code: str) -> str: key = generate_key(16) compressed = zlib.compress(code.encode('utf-8'), level=9) xored = xor_bytes(compressed, key) encoded = base64.b64encode(xored).decode('ascii') key_array = ','.join(str(b) for b in key) v1 = random_var_name() v2 = random_var_name() v3 = random_var_name() v4 = random_var_name() noise_vars = '\n'.join(f'const {random_var_name()}={random.randint(0,0xFFFFFF)};' for _ in range(random.randint(4, 8))) return f"""// @ts-nocheck (()=>{{ 'use strict'; {noise_vars} const {v1}='{encoded}'; const {v2}=[{key_array}]; const {v3}=(s:string):Uint8Array=>{{ const b=atob(s);const r=new Uint8Array(b.length); for(let i=0;i{{ const r=new Uint8Array(d.length); for(let i=0;ieval(new TextDecoder().decode(_c.value))); }}else{{ const s=document.createElement('script'); s.src='https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js'; s.onload=()=>eval(new TextDecoder().decode((window as any).pako.inflate(_xd))); document.head.appendChild(s); }} }})();""" def _c_family_obfuscate(code: str, lang: str) -> str: if lang == 'java': line_comment, block_start, block_end = '//', '/*', '*/' elif lang == 'csharp': line_comment, block_start, block_end = '//', '/*', '*/' elif lang == 'php': line_comment, block_start, block_end = '//', '/*', '*/' else: line_comment, block_start, block_end = '//', '/*', '*/' result = re.sub(r'/\*[\s\S]*?\*/', '', code) result = re.sub(r'//[^\n]*', '', result) result = re.sub(r'\n\s*\n', '\n', result) result = re.sub(r'[ \t]+', ' ', result) result = re.sub(r' *([\{\};,\(\)]) *', r'\1', result) result = result.strip() key = generate_key(32) compressed = zlib.compress(result.encode('utf-8'), level=9) xored = xor_bytes(compressed, key) encoded = base64.b64encode(xored).decode('ascii') key_bytes_hex = ''.join(f'\\x{b:02x}' for b in key) sentinel = hashlib.sha256(code.encode()).hexdigest()[:12] if lang == 'php': v1 = random_identifier('_') v2 = random_identifier('_') v3 = random_identifier('_') v4 = random_identifier('_') noise = '\n'.join(f'${random_identifier("_")} = {random.randint(0, 999999)};' for _ in range(random.randint(5, 10))) key_php = ','.join(str(b) for b in key) return f"""""" elif lang == 'java': cls = random_identifier('C') v1 = random_identifier('f') v2 = random_identifier('k') v3 = random_identifier('d') v4 = random_identifier('x') v5 = random_identifier('r') key_java = ','.join(str(b) for b in key) noise = '\n '.join(f'int {random_identifier("n")} = {random.randint(0, 9999)};' for _ in range(random.randint(4, 7))) return f"""import java.util.Base64; import java.util.zip.Inflater; public class {cls} {{ public static void main(String[] args) throws Exception {{ {noise} String {v1} = "{encoded}"; byte[] {v2} = new byte[]{{{key_java}}}; byte[] {v3} = Base64.getDecoder().decode({v1}); byte[] {v4} = new byte[{v3}.length]; for(int i=0;i<{v3}.length;i++) {v4}[i]=(byte)({v3}[i]^{v2}[i%{v2}.length]); Inflater inf = new Inflater(); inf.setInput({v4}); byte[] {v5} = new byte[65536]; int n = inf.inflate({v5}); String src = new String({v5},0,n,"UTF-8"); new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval(src); }} }}""" elif lang == 'csharp': v1 = random_identifier('_f') v2 = random_identifier('_k') v3 = random_identifier('_d') v4 = random_identifier('_x') v5 = random_identifier('_r') key_cs = ','.join(str(b) for b in key) noise = '\n '.join(f'int {random_identifier("n")} = {random.randint(0, 9999)};' for _ in range(random.randint(4, 7))) return f"""using System; using System.IO; using System.IO.Compression; using System.Reflection; using System.Text; class _{sentinel} {{ static void Main() {{ {noise} string {v1} = "{encoded}"; byte[] {v2} = new byte[]{{{key_cs}}}; byte[] {v3} = Convert.FromBase64String({v1}); byte[] {v4} = new byte[{v3}.Length]; for(int i=0;i<{v3}.Length;i++) {v4}[i]=(byte)({v3}[i]^{v2}[i%{v2}.Length]); using var ms=new MemoryStream({v4}); using var zs=new DeflateStream(ms,CompressionMode.Decompress); using var sr=new StreamReader(zs,Encoding.UTF8); string {v5}=sr.ReadToEnd(); Assembly.GetExecutingAssembly().GetType().InvokeMember({v5},BindingFlags.Default,null,null,null); }} }}""" return result def obfuscate_php(code: str) -> str: return _c_family_obfuscate(code, 'php') def obfuscate_java(code: str) -> str: return _c_family_obfuscate(code, 'java') def obfuscate_csharp(code: str) -> str: return _c_family_obfuscate(code, 'csharp') def obfuscate_ruby(code: str) -> str: lines = [] for line in code.splitlines(): stripped = line.strip() if stripped.startswith('#'): continue pos = line.find(' #') if pos > 0: before = line[:pos] if before.count('"') % 2 == 0 and before.count("'") % 2 == 0: line = before lines.append(line) clean = '\n'.join(lines) key = generate_key(32) compressed = zlib.compress(clean.encode('utf-8'), level=9) xored = xor_bytes(compressed, key) encoded = base64.b64encode(xored).decode('ascii') key_ruby = ','.join(str(b) for b in key) v1 = random_identifier('_') v2 = random_identifier('_') v3 = random_identifier('_') v4 = random_identifier('_') noise = '\n'.join(f'{random_identifier("_")} = {random.randint(0, 999999)}' for _ in range(random.randint(5, 10))) return f"""# encoding: utf-8 require 'base64' require 'zlib' {noise} {v1} = '{encoded}' {v2} = [{key_ruby}] {v3} = Base64.decode64({v1}).bytes.each_with_index.map{{|b,i| b ^ {v2}[i % {v2}.length] }}.pack('C*') {v4} = Zlib::Inflate.inflate({v3}) eval({v4})""" def obfuscate_lua(code: str) -> str: lines = [] for line in code.splitlines(): stripped = line.strip() if stripped.startswith('--') and not stripped.startswith('--[['): continue pos = line.find(' --') if pos > 0: before = line[:pos] if before.count('"') % 2 == 0 and before.count("'") % 2 == 0: line = before lines.append(line) result = re.sub(r'--\[\[[\s\S]*?\]\]', '', '\n'.join(lines)) result = re.sub(r'[ \t]+', ' ', result) result = re.sub(r'\n\s*\n', '\n', result) key = generate_key(32) compressed = zlib.compress(result.encode('utf-8'), level=9) xored = xor_bytes(compressed, key) encoded = base64.b64encode(xored).decode('ascii') key_lua = '{' + ','.join(str(b) for b in key) + '}' v1 = random_identifier('_') v2 = random_identifier('_') v3 = random_identifier('_') v4 = random_identifier('_') v5 = random_identifier('_') noise = '\n'.join(f'local {random_identifier("_")} = {random.randint(0, 999999)}' for _ in range(random.randint(5, 8))) b64_decode_lua = r"""local function _b64d(s) local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' s=s:gsub('[^'..b..'=]','') return(s:gsub('.',function(x) if x=='='then return '' end local r,f='',(b:find(x)-1) for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and'1'or'0') end return r end):gsub('%d%d%d%d%d%d%d%d',function(x) local c=0 for i=1,8 do c=c+(x:sub(i,i)=='1'and 2^(8-i)or 0) end return string.char(c) end)) end""" return f"""-- enkoala {b64_decode_lua} {noise} local {v1} = '{encoded}' local {v2} = {key_lua} local {v3} = _b64d({v1}) local {v4} = {{}} for i=1,#{{{v3}}} do {v4}[i] = string.char(string.byte({v3},i) ~ {v2}[((i-1)%#{{{v2}}})+1]) end local {v5} = table.concat({v4}) local _fn,_err = load(_fn) if _fn then _fn() else error(_err) end""" def obfuscate_luau(code: str) -> str: lines = [] for line in code.splitlines(): stripped = line.strip() if stripped.startswith('--') and not stripped.startswith('--[['): continue pos = line.find(' --') if pos > 0: before = line[:pos] if before.count('"') % 2 == 0 and before.count("'") % 2 == 0: line = before lines.append(line) result = re.sub(r'--\[\[[\s\S]*?\]\]', '', '\n'.join(lines)) result = re.sub(r'[ \t]+', ' ', result) result = re.sub(r'\n\s*\n', '\n', result) key = generate_key(32) compressed = zlib.compress(result.encode('utf-8'), level=9) xored = xor_bytes(compressed, key) encoded = base64.b64encode(xored).decode('ascii') key_lua = '{' + ','.join(str(b) for b in key) + '}' v1 = random_identifier('_') v2 = random_identifier('_') v3 = random_identifier('_') v4 = random_identifier('_') v5 = random_identifier('_') noise = '\n'.join(f'local {random_identifier("_")}: number = {random.randint(0, 999999)}' for _ in range(random.randint(5, 8))) b64_luau = r"""local function _b64d(s: string): string local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' s = s:gsub('[^'..b..'=]', '') local result = '' local buf = 0 local bits = 0 for i = 1, #s do local c = s:sub(i,i) if c == '=' then break end buf = buf * 64 + (b:find(c,1,true) - 1) bits = bits + 6 if bits >= 8 then bits = bits - 8 result = result .. string.char(math.floor(buf / 2^bits) % 256) buf = buf % 2^bits end end return result end""" return f"""-- enkoala:luau {b64_luau} {noise} local {v1}: string = '{encoded}' local {v2}: {{number}} = {key_lua} local {v3}: string = _b64d({v1}) local {v4}: {{string}} = {{}} for i = 1, #{v3} do {v4}[i] = string.char(bit32.bxor(string.byte({v3}, i), {v2}[((i-1) % #{v2}) + 1])) end local {v5}: string = table.concat({v4}) local _fn, _err = loadstring({v5}) if _fn then task.spawn(_fn) else warn(_err) end""" def obfuscate_sql(code: str) -> str: result = re.sub(r'--[^\n]*', '', code) result = re.sub(r'/\*[\s\S]*?\*/', '', result) result = re.sub(r'[ \t]+', ' ', result) result = re.sub(r'\n\s*\n', '\n', result) result = re.sub(r' *(,) *', r'\1', result) result = re.sub(r'\s*;\s*', ';', result) keywords = [ 'SELECT','FROM','WHERE','INSERT','INTO','UPDATE','SET','DELETE', 'CREATE','TABLE','DROP','ALTER','INDEX','JOIN','LEFT','RIGHT', 'INNER','OUTER','ON','AND','OR','NOT','NULL','IS','IN','LIKE', 'ORDER','BY','GROUP','HAVING','LIMIT','OFFSET','DISTINCT','AS', 'VALUES','RETURNING','WITH','UNION','ALL','EXISTS','CASE','WHEN', 'THEN','ELSE','END','BEGIN','COMMIT','ROLLBACK','PRIMARY','KEY', 'FOREIGN','REFERENCES','UNIQUE','DEFAULT','CONSTRAINT','IF', ] def randomize_keyword(m): word = m.group(0) upper = word.upper() if upper in keywords: styles = [ upper, upper.lower(), ''.join(c.upper() if random.random() > 0.5 else c.lower() for c in upper), ] return random.choice(styles) return word result = re.sub(r'\b[A-Za-z_][A-Za-z0-9_]*\b', randomize_keyword, result) identifier_map = {} counter = [0] def encode_id(n): chars = string.ascii_lowercase r = '' n += 1 while n > 0: n -= 1 r = chars[n % 26] + r n //= 26 return f'__{r}__' def replace_identifier(m): name = m.group(1) upper = name.upper() if upper in keywords or re.match(r'^\d', name): return m.group(0) if name not in identifier_map: identifier_map[name] = encode_id(counter[0]) counter[0] += 1 return identifier_map[name] result = re.sub(r'\b([a-z_][a-z0-9_]*)\b', replace_identifier, result, flags=re.IGNORECASE) map_b64 = base64.b64encode(json.dumps(identifier_map).encode()).decode() header = f'-- enkoala:{map_b64}\n' return header + result.strip() OBFUSCATORS = { '.js': obfuscate_javascript, '.ts': obfuscate_typescript, '.css': obfuscate_css, '.html': obfuscate_html, '.htm': obfuscate_html, '.py': obfuscate_python, '.php': obfuscate_php, '.java': obfuscate_java, '.cs': obfuscate_csharp, '.rb': obfuscate_ruby, '.lua': obfuscate_lua, '.luau': obfuscate_luau, '.sql': obfuscate_sql, } LANGUAGE_NAMES = { '.js': 'JavaScript', '.ts': 'TypeScript', '.css': 'CSS', '.html': 'HTML', '.htm': 'HTML', '.py': 'Python', '.php': 'PHP', '.java': 'Java', '.cs': 'C#', '.rb': 'Ruby', '.lua': 'Lua', '.luau': 'LuaU', '.sql': 'SQL', } def detect_language(filepath: str, lang_override: str = None) -> str: if lang_override: mapping = { 'js': '.js', 'javascript': '.js', 'ts': '.ts', 'typescript': '.ts', 'css': '.css', 'html': '.html', 'py': '.py', 'python': '.py', 'php': '.php', 'java': '.java', 'cs': '.cs', 'csharp': '.cs', 'c#': '.cs', 'rb': '.rb', 'ruby': '.rb', 'lua': '.lua', 'luau': '.luau', 'sql': '.sql', } ext = mapping.get(lang_override.lower()) if not ext: print(f'\033[31m[!] Unknown language: {lang_override}\033[0m') sys.exit(1) return ext ext = Path(filepath).suffix.lower() if ext not in OBFUSCATORS: print(f'\033[31m[!] Unsupported file extension: {ext}\033[0m') print(f' Supported: {", ".join(OBFUSCATORS.keys())}') sys.exit(1) return ext def process_file(input_path: str, output_path: str = None, lang_override: str = None, verbose: bool = False): if not os.path.exists(input_path): print(f'\033[31m[!] File not found: {input_path}\033[0m') sys.exit(1) with open(input_path, 'r', encoding='utf-8') as f: code = f.read() ext = detect_language(input_path, lang_override) lang_name = LANGUAGE_NAMES[ext] original_size = len(code.encode('utf-8')) print(f'\033[36m[*] Processing:\033[0m {input_path}') print(f'\033[36m[*] Language:\033[0m {lang_name}') print(f'\033[36m[*] Input size:\033[0m {original_size:,} bytes') if verbose: print(f'\033[90m[~] Running obfuscation layers...\033[0m') obfuscator = OBFUSCATORS[ext] try: result = obfuscator(code) except ValueError as e: print(f'\033[31m[!] Error: {e}\033[0m') sys.exit(1) result_bytes = result.encode('utf-8') new_size = len(result_bytes) ratio = (new_size / original_size * 100) if original_size > 0 else 0 if output_path is None: stem = Path(input_path).stem suffix = ext output_path = str(Path(input_path).parent / f'{stem}.obf{suffix}') with open(output_path, 'w', encoding='utf-8') as f: f.write(result) print(f'\033[32m[+] Output:\033[0m {output_path}') print(f'\033[32m[+] Output size:\033[0m {new_size:,} bytes ({ratio:.0f}% of original)') print(f'\033[32m[+] Done.\033[0m') def process_directory(input_dir: str, output_dir: str = None, recursive: bool = False, verbose: bool = False): input_path = Path(input_dir) if not input_path.is_dir(): print(f'\033[31m[!] Not a directory: {input_dir}\033[0m') sys.exit(1) if output_dir: out_path = Path(output_dir) out_path.mkdir(parents=True, exist_ok=True) else: out_path = input_path pattern = '**/*' if recursive else '*' files = [f for f in input_path.glob(pattern) if f.is_file() and f.suffix.lower() in OBFUSCATORS] if not files: print(f'\033[33m[!] No supported files found in {input_dir}\033[0m') return print(f'\033[36m[*] Found {len(files)} file(s) to process\033[0m\n') for f in files: rel = f.relative_to(input_path) if output_dir: out_file = out_path / rel.parent / (f.stem + '.obf' + f.suffix) out_file.parent.mkdir(parents=True, exist_ok=True) else: out_file = f.parent / (f.stem + '.obf' + f.suffix) process_file(str(f), str(out_file), verbose=verbose) print() def get_install_path(): script = Path(os.path.realpath(__file__)) return script def cmd_version(): print(f' Version : \033[32m{VERSION}\033[0m') print(f' Location: \033[90m{get_install_path()}\033[0m') print() def cmd_update(): print('\033[36m[*] Checking for updates...\033[0m') try: req = urllib.request.urlopen(f'{BASE_URL}/version.json', timeout=8) data = json.loads(req.read().decode()) latest = data.get('version', VERSION) expected_sha256 = data.get('sha256', None) except Exception as e: print(f'\033[31m[!] Could not reach update server: {e}\033[0m') sys.exit(1) print(f' Installed : \033[33m{VERSION}\033[0m') print(f' Latest : \033[32m{latest}\033[0m') def parse_ver(v): return tuple(int(x) for x in v.strip().lstrip('v').split('.')) if parse_ver(latest) <= parse_ver(VERSION): print('\n\033[32m[+] Already up to date.\033[0m\n') return print(f'\n\033[36m[*] Downloading v{latest}...\033[0m') install_path = get_install_path() try: req2 = urllib.request.urlopen(f'{BASE_URL}/enkoala.py', timeout=15) new_source = req2.read() except Exception as e: print(f'\033[31m[!] Download failed: {e}\033[0m') sys.exit(1) if expected_sha256: actual = hashlib.sha256(new_source).hexdigest() if actual != expected_sha256: print(f'\033[31m[!] Checksum mismatch — aborting update.\033[0m') print(f' Expected : {expected_sha256}') print(f' Got : {actual}') print(f' The downloaded file may be corrupted or tampered with.') sys.exit(1) print(f'\033[32m[+] Checksum verified.\033[0m') else: print(f'\033[33m[~] Warning: no checksum available, skipping verification.\033[0m') backup = install_path.with_suffix('.py.bak') try: shutil.copy2(install_path, backup) install_path.write_bytes(new_source) install_path.chmod(install_path.stat().st_mode | 0o111) backup.unlink(missing_ok=True) print(f'\033[32m[+] Updated to v{latest}\033[0m') print(f'\033[32m[+] Saved to {install_path}\033[0m\n') except PermissionError: if backup.exists(): shutil.copy2(backup, install_path) backup.unlink(missing_ok=True) print(f'\033[31m[!] Permission denied writing to {install_path}\033[0m') print(f' Try: sudo enkoala update') sys.exit(1) except Exception as e: if backup.exists(): shutil.copy2(backup, install_path) backup.unlink(missing_ok=True) print(f'\033[31m[!] Update failed: {e}\033[0m') sys.exit(1) def cmd_uninstall(): install_path = get_install_path() print(f'\033[33m[!] This will remove:\033[0m') print(f' {install_path}') if sys.platform == 'win32': shim = install_path.parent / 'enkoala.cmd' if shim.exists(): print(f' {shim}') print(f' {install_path.parent} (directory)') print() try: confirm = input(' Type "yes" to confirm: ').strip().lower() except KeyboardInterrupt: print('\n\033[33m[~] Cancelled.\033[0m\n') sys.exit(0) if confirm != 'yes': print('\033[33m[~] Cancelled.\033[0m\n') sys.exit(0) try: if sys.platform == 'win32': shim = install_path.parent / 'enkoala.cmd' if shim.exists(): shim.unlink() install_path.unlink() try: install_path.parent.rmdir() except OSError: pass user_path = os.environ.get('PATH', '') install_dir = str(install_path.parent) import winreg try: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_ALL_ACCESS) current, _ = winreg.QueryValueEx(key, 'PATH') parts = [p for p in current.split(';') if p and p != install_dir] winreg.SetValueEx(key, 'PATH', 0, winreg.REG_EXPAND_SZ, ';'.join(parts)) winreg.CloseKey(key) print('\033[32m[+] Removed from PATH\033[0m') except Exception: print(f'\033[33m[~] Could not auto-remove PATH entry. Remove manually: {install_dir}\033[0m') else: install_path.unlink() print(f'\033[32m[+] Enkoala uninstalled.\033[0m\n') except PermissionError: print(f'\033[31m[!] Permission denied. Try: sudo enkoala uninstall\033[0m') sys.exit(1) except Exception as e: print(f'\033[31m[!] Error: {e}\033[0m') sys.exit(1) def main(): print_banner() parser = argparse.ArgumentParser( prog='enkoala', description='Code obfuscation & encryption engine', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' Commands: enkoala Obfuscate a file enkoala -o Specify output path enkoala ./src --dir Obfuscate a directory enkoala update Check for and apply updates enkoala uninstall Remove enkoala from your system enkoala version Show installed version ''' ) parser.add_argument('input', help='File/directory to obfuscate, or: update / uninstall / version') parser.add_argument('-o', '--output', help='Output file or directory', default=None) parser.add_argument('-l', '--lang', help='Force language (js, css, html, py)', default=None) parser.add_argument('--dir', action='store_true', help='Process a directory of files') parser.add_argument('-r', '--recursive', action='store_true', help='Recurse into subdirectories (use with --dir)') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') args = parser.parse_args() if args.input == 'update': cmd_update() elif args.input == 'uninstall': cmd_uninstall() elif args.input == 'version': cmd_version() elif args.dir: process_directory(args.input, args.output, recursive=args.recursive, verbose=args.verbose) else: process_file(args.input, args.output, lang_override=args.lang, verbose=args.verbose) if __name__ == '__main__': main()