Use at own risk. Programs haven't been thoroughly tested.

Class: JSObfuscate

Scrambles and Compresses Javascript source Very usefull if you want to make it harder for others to interpret your Javascript source files. I looked hard for a free obfuscator with really good scrambling and compression rates, but I couldn't find one so I created this. Its not possible to unscramble generated code, since it replaces all local variables and all function with random names. It has an average compression rate of 50%. Longer JS files give a better overall compression rate. Feel free to improve it, but please leave credits in tact, and tell me what you've changed, so I can improve it as well!

Info

@author Kevin van Zonneveld
@version 0.3
@link http://kevin.vanzonneveld.net
@function obfuscateString obfuscates a JS string
@function obfuscateFile obfuscates a .JS file

Example

Usage
echo JSObfuscate::obfuscateString("
   function setChildProduct(origin){
     var strInputName,strOutputFile,strCategory,strProduct;
     if(!confirm(strInputName+' and '+strOutputFile+' and '+strCategory+' and '+strProduct)){
       deny('everything');
     }
   }
   funtion deny(strdata){
     alert('everything');
   }
   ",false);

Outputs
var wX=window;var dX=document;funtion deny(strdata){alert('everything');} function Xw(M){var L,s,p,O;if(!confirm(L+' and '+s+' and '+p+' and '+O)){deny('everything');}} function setChildProduct(M){Xw(M);}

Source Code

download source
<?
abstract class JSObfuscate  {
  
 
  static private $globalReplacing=array("dX"=>"document","wX"=>"window");
  static private $reserverdReplacing=array("e"=>true,"in"=>true,"do"=>true,"if"=>true,"id"=>true);
  static private $functionAlias=array();
  static private $keeptrack_rnd=array();
  static private $jsSeperationString = "\s|\=|\.|\?|\,|\(|\)|\[|\]|\+|\:|\;|\!|\||\<|\>|\/|\-|\*";
  static private $jsTrimString = "\(|\)|\{|\}|\,|\;|\:|\?|\=\=\=|\=\=|\!\=|\&\&|\<\=|\>\=|\>|\<|\|\||\*|\/|\=|\+|\-";
 
  static public function obfuscateString($jsbody,$copyright="    Copyright(c) 2006 True, Kevin van Zonneveld    "){
    // links al the obfuscation function together and produces new js code!
 
    $szo = strlen($jsbody);
    $jsbody   = self::_cleanupBody($jsbody);
    $functions   = self::_getFunctions($jsbody);
    $nwcode   = self::_compileBody($functions);
    $nwcode   = self::_obfuscateFunctionNames($nwcode);
    $nwcode   = self::_compressBody($nwcode);
    $sze = strlen($nwcode);
 
    $header="";
    if($copyright ==false){
      $header .= "/***".$copyright."***/\n";
      $header .= "/***\n";
      $header .= "\tEncoded by JSObfuscate 0.3\n";
      $header .= "\t  original size: ".$szo."\n";
      $header .= "\t  encoded  size: ".$sze."\n";
      $header .= "\t  compression r: ".number_format(100-($sze/$szo*100),2) ."%\n";
      $header .= "***/\n";
    }
 
    return $header. $nwcode;
  }
 
  static public function obfuscateFile($jsfilepath){
    // read file
    self::obfuscateString(implode("\n",file($jsfilepath)));
  }
 
  static private function _compressBody($jsbody){
    //remove whitespace around operators and such
    $jsbody = preg_replace("/( |\t)*(".self::$jsTrimString.")( |\t)*/","$2",$jsbody);
 
    $jsbody = str_replace("\nfunction ","function^^^^^ ",$jsbody);
    $jsbody = str_replace("\nString.prototype.","String.prototype.^^^^^",$jsbody);
    $jsbody = str_replace("\nArray.prototype.","Array.prototype.^^^^^",$jsbody);
 
    $jsbody = str_replace("\n","",$jsbody);
 
    $jsbody = str_replace("function^^^^^ ","\nfunction ",$jsbody);
    $jsbody = str_replace("String.prototype.^^^^^","\nString.prototype.",$jsbody);
    $jsbody = str_replace("Array.prototype.^^^^^","\nArray.prototype.",$jsbody);
 
    foreach(self::$globalReplacing as $short=>$large){
      // replace the document variable with a shorter one
      $jsbody = preg_replace("/(".self::$jsSeperationString.")$large(".self::$jsSeperationString.")/","$1$short$2",$jsbody);
      // make the sorter document variable available in the js body
      $jsbody = "var $short=$large;".$jsbody;
    }
 
    return $jsbody;
  }
 
  static private function _obfuscateFunctionNames($jsbody=false){
    if(!$jsbody) return false;
 
    // first find & replace all functions with new names
    preg_match_all("/\nfunction (\w+)\s?\(([a-zA-Z0-9, ]*)\)/",$jsbody,$rslt);
    foreach($rslt[1] as $k=>$fname){
      self::$functionAlias[$fname] = self::_retRandomName("global");
      $functionAttributes[$fname] = $rslt[2][$k];
      $jsbody = preg_replace("/(".self::$jsSeperationString.")$fname(".self::$jsSeperationString.")/",
                  "$1".self::$functionAlias[$fname]."$2",$jsbody);
    }
 
    // randomly sort the functionalias array
    //self::$functionAlias = _assarrayShuffle(self::$functionAlias);
 
    // create references to the new functions so that other applications can stil find them
    $jsbody.= "\n";
    foreach(self::$functionAlias as $fname=>$alias){
      $jsbody.= "function $fname(".$functionAttributes[$fname]."){".self::$functionAlias[$fname].
            "(".$functionAttributes[$fname].");}";
    }
    return $jsbody;
  }
 
  static private function _obfuscateFunctionBlock($fname="",$blcar=false,$attr=false,$vars=false){
    if(!$blcar || !is_array($blcar)) return false;
    if(!$attr || !is_array($attr)) $attr=array();
    if(!$vars || !is_array($vars)) $vars=array();
 
    // replace all attributes AND local variables with a random name
    $seekar = array();
    $replar = array();
    $avars = array_merge($attr,$vars);
    $avars = array_unique($avars);
    $block = implode("\n",$blcar);
    foreach($avars as $k=>$seekvar){
      if( !isset(self::$reserverdReplacing[$seekvar]) && $seekvar){
        $repvar = self::_retRandomName($fname);
 
        $seekar[] = "/(".self::$jsSeperationString.")$seekvar(".self::$jsSeperationString.")/";
        $replar[] = "$1$repvar$2";
 
        if(!isset($repvar) || !$seekvar){
          die("There was no repvar for seekvar: $seekvar ($repvar)");
        }
      }
    }
    // do it
    if(count($seekar)){
      if(count($replar)!=count($seekar)){
        print_r($seekar);
        print_r($replar);
        die("COUNT OF SEEKAR != REPLAR ");
      }
      $block = preg_replace($seekar,$replar,$block);
      // needs to be done 2 times for overlapping matches like "lHI = (lHI?lHI:this);"
      $block = preg_replace($seekar,$replar,$block);
    }
 
    return explode("\n",$block);
  }
 
  static private function _retRandomName($scope="global"){
    // e,f,d have been removed to prevent conflicts with e, if, do, etc.
    $pattern = "123456789abcghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    while($cnt<5000){
 
      $nme = $pattern{rand(11,57)};
      if($scope=="global"){
        $nme .= $pattern{rand(0,57)};
      }
 
      $todo = intval(count(self::$keeptrack_rnd[$scope])/40);
      if($todo>1){
        for($i=0;$i++;$i<$todo){
          $nme .= $pattern{rand(0,57)};
        }
      }
 
      if( !isset(self::$keeptrack_rnd[$scope][$nme]) && !isset(self::$keeptrack_rnd["global"][$nme]) &&
        !isset(self::$globalReplacing[$nme]) && !isset(self::$reserverdReplacing[$nme]) ){
        self::$keeptrack_rnd[$scope][$nme]=true;
        return $nme;
        break;
      }
 
      $cnt++;
    }
    die("SAFETY LIMIT OF 5000 TRIES HIT WITHOUT RESULTING IN A GOOD RANDOMNAME!");
  }
 
  static private function _getFunctions($jsbody){
    $arrFBlocks = array();
    $fi=0;
    $frecording=false;
 
    // search all lines
    $jsbodylines = explode("\n",$jsbody);
    foreach($jsbodylines as $clnr=>$cline){
      $cline     = trim($cline);
      $curlevel   += substr_count($cline,"{");
      $curlevel   -= substr_count($cline,"}");
 
      if($frecording == false){
        if(substr($cline,0,strlen("function "))=="function "){
          $frecording  = true;
          $fi++;
          $fname     = trim(self::_retBetween($cline,"function ","(",false,false));
          $arrFBlocks[$fname]["name"]     = $fname;
          $arrFBlocks[$fname]["attributes"]   = explode( ",",self::_retBetween($cline,"(",")",
                              false,false) );
          foreach($arrFBlocks[$fname]["attributes"] as $attk=>$attv){
            $arrFBlocks[$fname]["attributes"][$attk] = trim($attv);
            if(!trim($attv)){
              unset($arrFBlocks[$fname]["attributes"][$attk]);
            }
          }
          $arrFBlocks[$fname]["level"]     = $curlevel;
          $arrFBlocks[$fname]["line"]     = $clnr;
          $arrFBlocks[$fname]["number"]     = $fi;
        }
        elseif($cline){
          $arrFBlocks["!NONFUNC!"]["block"][] = str_pad($cline,$curlevel," ",STR_PAD_LEFT);
        }
      }
      else{
        if(substr($cline,0,strlen("var "))=="var "){
          $p = substr($cline,strlen("var "));
 
          if(substr_count($p,"=")){
            $pis = explode("=",$p);
            $arrFBlocks[$fname]["vars"][]    = str_replace(";","",trim($pis[0]));
          }
          else{
            $pp = explode(",", $p);
            foreach($pp as $pk=>$pv){
              $pis = explode("=",$pv);
              $arrFBlocks[$fname]["vars"][]    = str_replace(";","",trim($pis[0]));
            }
          }
        }
        elseif($arrFBlocks[$fname]["level"]-1 == $curlevel){
          $arrFBlocks[$fname]["block"]     = self::_obfuscateFunctionBlock($fname,
            array_slice($jsbodylines,$arrFBlocks[$fname]["line"],($clnr-$arrFBlocks[$fname]["line"]+1)) ,
            $arrFBlocks[$fname]["attributes"],($arrFBlocks[$fname]["vars"]) );
          $frecording  = false;
        }
      }
    }
 
 
    return $arrFBlocks;
 
  }
 
 
  static private function _cleanupBody($jsbody){
    // convert )\n{ to {
    $jsbody = preg_replace("/\)(\s)*\{/","){",$jsbody);
 
 
    // convert tabs->spaces
    while(substr_count($jsbody,"\t")){
      $jsbody = str_replace("\t"," ",$jsbody);
    }
    // clean multiple spaces
    while(substr_count($jsbody,"  ")){
      $jsbody = str_replace("  "," ",$jsbody);
    }
    // convert nw->cr
    while(substr_count($jsbody,"\r")){
      $jsbody = str_replace("\r","\n",$jsbody);
    }
    // trim all lines
    $jsbodylines = explode("\n",$jsbody);
    foreach($jsbodylines as $k=>$v){
      $jsbodylines[$k] = trim($v);
      if(!$jsbodylines[$k]){
        unset($jsbodylines[$k]);
      }
    }
    $jsbody = implode("\n",$jsbodylines);
    // clean multiple breaks
    while(substr_count($jsbody,"\n ")){
      $jsbody = str_replace("\n ","\n",$jsbody);
    }
    // clean multiple breaks
    while(substr_count($jsbody,"\n\n")){
      $jsbody = str_replace("\n\n","\n",$jsbody);
    }
    // Remove multiline comment
    $mlcomment = '/\/\*(?!-)[\x00-\xff]*?\*\//';
    $jsbody = preg_replace($mlcomment,"",$jsbody);
    // Remove single line comment
    $slcomment = '/[^:]\/\/.*/';
    $jsbody = preg_replace($slcomment,"",$jsbody);
    return $jsbody;
  }
 
 
  static private function _assarrayShuffle ($array) {
    while (count($array) > 0) {
      $val = array_rand($array);
      $new_arr[$val] = $array[$val];
      unset($array[$val]);
    }
    return $new_arr;
  }
 
  static private function _compileBody($functionarray){
    $buf = "";
    $functionarray = self::_assarrayShuffle($functionarray);
    //asort($functionarray);
    foreach($functionarray as $fname=>$far){
      if($fname == "!NONFUNC!"){
        $buf = implode("\n",$far[block])."\n".$buf;
      }
      else{
        //write function
        $buf = $buf."\n". implode("\n",$far[block]);
      }
    }
    return $buf;
  }
 
  static private function _retBetween($base,$left,$right, $insensitive=true,$include_findstr=false){
    //this function finds a string between 2 strings
    //(c) kevin
        $basl   = strlen($base);
 
        $posl   = ($insensitive?strpos(strtolower($base),strtolower($left)):strpos($base,$left));
        $foundl = ($insensitive?substr_count(strtolower($base),strtolower($left)):substr_count($base,$left));
        $base   = substr($base,$posl+($include_findstr?0:strlen($left)),$basl);
 
        $posr   = ($insensitive?strpos(strtolower($base),strtolower($right),0):strpos($base,$right,0));
        $foundr = ($insensitive?substr_count(strtolower($base),strtolower($right)):substr_count($base,$right));
        $base   = substr($base,0,($posr)+($include_findstr?strlen($right):0));
 
        return (!$foundl||!$foundr?false:$base);
  }
}
 
?>

Add comment

» Currently away on vacation. I can reply your message the 24th of July 2008. Please post anyway and check back then. Thank you!

for syntax highlighting

[CODE="Javascript"]
your_code_here();
[/CODE]

Replace "Javascript"
with "php", "text", etc.
code (to make sure you are not a spammer)

Comments

#2. Kevin on 21 March 2008

Kevin@ MD: Yeah this is legacy code. It's not perfect and I used it before I found Dean Edwards packer & jsmin. I keep it online for studying purposes though.

Thanks, Kevin

#1. MD on 19 March 2008

MDIt took me a short while to find lots PHP JS obfuscators, for example this one

http://joliclic.free.fr/php/javascript-packer/en/

searching is usually usually quicker than writing your own code from scratch, but you might miss an enjoyment like I did:) Excellent work though!