Skip to main content

Obfuscate your PHP code

Protect your PHP code with obfuscation. Discover how the php_obfuscator script works and if AI tools like Copilot can still de-obfuscate your proprietary source code.

· 4 min read
View raw
Christophe
Markdown, WSL and Docker lover ~ Insatiable curious.
Cet article existe aussi en français — lire la version française.

Changelog

Latest
Updated PHP Docker image from php:7.4-fpm (EOL Nov 2022) to php:8.3-fpm in all examples.

Obfuscate your PHP code

⚡
TL;DR

This article tests whether PHP code obfuscation (using the author's php_obfuscator script, which strips whitespace and renames identifiers to random strings) still protects proprietary code in the AI era. A hangman game generated by Gemini is obfuscated and still runs correctly, but Copilot is then able to fully de-obfuscate it back into readable, commented code — questioning whether obfuscation is still worthwhile.

A few years ago, I sold a software called aeSecure, which was a Web Application Firewall. In order to prevent my proprietary code from being picked up by someone who could then sell it, for example, I had developed an obfuscation script.

Since then, I've released a reusable version of that script: https://github.com/cavo789/php_obfuscator.

And what if, today, with artificial intelligence, this type of code was no longer of any interest? The aim of this article is to see if it's always a good idea to make your code unreadable.

Obtaining a previously non-existent code, ask Gemini​

Just to give you some PHP code to use as an example for this article, let's create a hangman game using artificial intelligence.

Asking Gemini

Here is the code proposed by Gemini. Save it to your disk as hangman.php:

<?php
function generateWordList() {
// Replace these with your own word list
return [
"apple",
"banana",
"orange",
"computer",
"programming",
];
}
function getRandomWord($wordList) {
$randomIndex = array_rand($wordList);
return $wordList[$randomIndex];
}
function displayHangman($guessesLeft) {
$stages = [
"[ O ]",
"[ | ]",
"[ /| ]",
"[ /|\\ ]",
"[ | ]",
"[ / ]",
"Wrong guess: ",
];
echo $stages[$guessesLeft] . PHP_EOL;
}
function displayWord($word, $guessedLetters) {
$displayedWord = "";
for ($i = 0; $i < strlen($word); $i++) {
$letter = $word[$i];
if (in_array($letter, $guessedLetters)) {
$displayedWord .= $letter . " ";
} else {
$displayedWord .= "_ ";
}
}
echo trim($displayedWord) . PHP_EOL;
}
function playHangman() {
$wordList = generateWordList();
$word = getRandomWord($wordList);
$guessedLetters = [];
$guessesLeft = 6;
while ($guessesLeft > 0) {
displayHangman($guessesLeft);
displayWord($word, $guessedLetters);
echo "Guess a letter: ";
$guess = trim(fgets(STDIN));
if (strlen($guess) !== 1 || !ctype_alpha($guess)) {
echo "Please enter a single letter." . PHP_EOL;
continue;
}
$guess = strtolower($guess);
if (in_array($guess, $guessedLetters)) {
echo "You already guessed that letter." . PHP_EOL;
continue;
}
$guessedLetters[] = $guess;
if (strpos($word, $guess) === false) {
$guessesLeft--;
}
if (str_replace(" ", "", displayWord($word, $guessedLetters)) === $word) {
echo "Congratulations! You guessed the word." . PHP_EOL;
break;
}
}
if ($guessesLeft === 0) {
displayHangman($guessesLeft);
echo "You ran out of guesses. The word was: " . $word . PHP_EOL;
}
}
playHangman();

This is a complete Hangman game implementation. It picks a random word from a list, then repeatedly prompts the player to guess letters, tracking which letters have been guessed and how many wrong guesses remain. The game ends when the player either guesses the word correctly or runs out of guesses.

To play with this game, just run docker run -it -v ${PWD}:/src -w /src php:8.3-fpm php hangman.php (see The easiest way to run a PHP script / website if that command looks cryptic).

As you'll see the script works fine without any change!

Playing

Obfuscate​

Now, we'll make this code unreadable by removing unneeded spaces and carriage returns, and replacing names with some random letters. For instance, replace the playHangman function name with a random string like ppny. And to make the code more unreadable, I'll choose only strings like ppmx, ppnx, ppny and so on.

To do such obfuscation, just download my https://github.com/cavo789/php_obfuscator/blob/main/src/minify.php script and save it on your disk as minify.php. Also create a file called settings.json with this content:

{
"replace": {
"getRandomWord":"ppmx",
"displayHangman":"ppnx",
"playHangman":"ppny",
"displayWord":"ppnz",
"guessesLeft":"ppnc",
"guessedLetters":"ppnt",
"randomIndex":"ppmw",
"wordList":"ppmu",
"generateWordList":"ppms",
"displayedWord":"ppmb",
"stages":"ppmc"
}
}

This is a configuration object that maps human-readable variable and function names from a hangman game to shortened placeholder names. It's likely used as a search-and-replace lookup table to obfuscate or minify the original code by substituting meaningful names with cryptic abbreviated ones.

Right now, in your folder, you've three files:

>user@machine: ~/yourproject

Time to make the hangman.php file unreadable by running docker run -it -v ${PWD}:/src -w /src php:8.3-fpm php minify.php input=hangman.php output=hangman_minify.php.

Obfuscating your PHP code

Now, you've a new file called hangman_minify.php:

<?php
// cspell:disable
function ppms(){return [
"apple",
"banana",
"orange",
"computer",
"programming",
];}
function ppmx($ppmu){$ppmw=array_rand($ppmu);return $ppmu[$ppmw];}
function ppnx($ppnc){$ppmc=[
"[ O ]",
"[ | ]",
"[ /| ]",
"[ /|\\ ]",
"[ | ]",
"[ / ]",
"Wrong guess: ",
];echo $ppmc[$ppnc].PHP_EOL;}
function ppnz($word,$ppnt){$ppmb="";
for ($i=0; $i < strlen($word); $i++){$letter=$word[$i];if(in_array($letter,$ppnt)){$ppmb .=$letter." ";}else{$ppmb .="_ ";}}echo trim($ppmb).PHP_EOL;}
function ppny(){$ppmu=ppms();$word=ppmx($ppmu);$ppnt=[];$ppnc=6;while ($ppnc > 0){
ppnx($ppnc);
ppnz($word,$ppnt);echo "Guess a letter: ";$guess=trim(fgets(STDIN));if(strlen($guess) !==1||!ctype_alpha($guess)){echo "Please enter a single letter.".PHP_EOL;continue;}$guess=strtolower($guess);if(in_array($guess,$ppnt)){echo "You already guessed that letter.".PHP_EOL;continue;}$ppnt[]=$guess;if(strpos($word,$guess)===false){$ppnc--;}
if(str_replace(" ", "", ppnz($word,$ppnt))===$word){echo "Congratulations! You guessed the word.".PHP_EOL;break;}}
if($ppnc===0){
ppnx($ppnc);echo "You ran out of guesses. The word was: ".$word.PHP_EOL;}}
ppny();

This is a hangman game implementation. It picks a random word from a list, then lets the player guess letters one at a time until they either guess the word correctly or run out of guesses. As the player makes wrong guesses, it displays a stick figure drawing progressively, and it shows the word with blanks for unguessed letters.

What you see here above is the newer version of the hangman.php file and it's still running. You can check by running docker run -it -v ${PWD}:/src -w /src php:8.3-fpm php hangman_minify.php.

Make the code readable again, ask Copilot​

Asking copilot

The first time, Copilot has kept my minified function name. By asking him Can you suggest another version?, Copilot suggests this code:

Rewriting the code

<?php
// Define an array of words
function getRandomWord()
{
$words = [
"apple",
"banana",
"orange",
"computer",
"programming",
];
return $words[array_rand($words)];
}
// Display hangman art based on the number of incorrect guesses
function displayHangman($incorrectGuesses)
{
$hangmanArt = [
"[ O ]",
"[ | ]",
"[ /| ]",
"[ /|\\ ]",
"[ | ]",
"[ / ]",
];
echo $hangmanArt[$incorrectGuesses] . PHP_EOL;
}
// Display the word with guessed letters revealed
function displayWord($word, $guessedLetters)
{
$displayedWord = "";
foreach (str_split($word) as $letter) {
if (in_array($letter, $guessedLetters)) {
$displayedWord .= $letter . " ";
} else {
$displayedWord .= "_ ";
}
}
echo trim($displayedWord) . PHP_EOL;
}
// Main game loop
function playHangman()
{
$wordToGuess = getRandomWord();
$guessedLetters = [];
$incorrectGuesses = 6;
while ($incorrectGuesses > 0) {
displayHangman($incorrectGuesses);
displayWord($wordToGuess, $guessedLetters);
echo "Guess a letter: ";
$guess = trim(fgets(STDIN));
if (strlen($guess) !== 1 || !ctype_alpha($guess)) {
echo "Please enter a single letter." . PHP_EOL;
continue;
}
$guess = strtolower($guess);
if (in_array($guess, $guessedLetters)) {
echo "You already guessed that letter." . PHP_EOL;
continue;
}
$guessedLetters[] = $guess;
if (strpos($wordToGuess, $guess) === false) {
$incorrectGuesses--;
}
if (str_replace(" ", "", displayWord($wordToGuess, $guessedLetters)) === $wordToGuess) {
echo "Congratulations! You guessed the word." . PHP_EOL;
break;
}
}
if ($incorrectGuesses === 0) {
displayHangman($incorrectGuesses);
echo "You ran out of guesses. The word was: " . $wordToGuess . PHP_EOL;
}
}
// Start the game
playHangman();

This is a hangman game implementation in PHP. It picks a random word, then loops asking the player to guess letters one at a time, revealing correct guesses and drawing hangman stages for wrong ones. The game ends when either the player guesses the full word or runs out of incorrect guesses.

The artificial intelligence managed to understand the purpose of the script (a hangman game) and rewrote the code to make it readable. Not only is the code readable, the function names are too, but it has also written comments. Just incredible!

Obfuscation aside, if you're after more conventional PHP code-quality tooling, check out the Docker image bundling static analysis tools.


Was this article helpful?
Did you try the steps in this article?