Periklis Ntanasis:
Master's Touch

fade out

A deep look into PHPShadow 3.2

Introduction

Back in August of 2011 I’ve written a post about PHP code obfuscation and encryption.

As I had wrote, one could use these two methods to keep his php source code more or less secret.

In this article I am going to review one particular commercial PHP encryption solution, PHPShadow.

I was lucky enough to get a free license of PHPShadow by PHPShadow’s Edwin Hermann back in January in order to evaluate the -then- new version of it (3.2.0).

However, somehow I got busy and I wasn’t able to publish it. I had reviewed PHPShadow 3.2.0 and almost finished my article but then I got busy and now PHPShadow is in version 4 with significant changes.

To be honest Edwin was kind enough to send me a license for PHPShadow version 4 but it lasted for a month and I wasn’t able to review it back then.

Just to keep my word to Edwin I am going to publish this article which refers mostly to PHPShadow version 3.2.0 and has some notices about version 4.

So here I go. In the beginning I am going to analyze PHPShadow from some different viewpoints and in the end I’ll share with you my conclusions about it. If you are looking just for a quick PHPShadow overview jump to the conclusion instead. I’ll have a special paragraph in the end for PHPShadow 4, everything else is for 3.2.0, especially the Performance and Security sections.

PHPShadow in a nutshell

For those of you not familiar with PHPShadow, it is an encryption mechanism for PHP.

The current workflow is pretty simple. Just use the encryption tool to encrypt your PHP files and you are done.

However, a webserver should have the phpshadow.so PHP extension installed in order to execute the encrypted file.

Here is a quick and straightforward code example:

Original Code
<?php echo "Hello, World!"; ?>
Encrypted Code
<?php
/* This script is protected by PHPshadow.  Visit http://www.phpshadow.com */
phpshadow_exec(0,__FILE__,"
ECQAGMWTWIOIVTRKACVFNHIAVNJEECUT
"); ?>

Let’s dive into more details

User Guide

PHPShadow comes with a user guide that can be downloaded from the download section.

As I am writing this post the current version is 3.2.0.

I found it very detailed and easy to read. There are step by step instructions that even a very inexperienced developer can easily understand.

The guide includes topics as phpshadow extension installation, installation on shared hosting, the phpshadow encoder’s usage, usage examples, best practice guidelines etc.

Encryption Workflow

There are two possible ways to encrypt a file. The online encoder that encrypts only one file for 0.99 euro (or for free in trial mode) and the encoder tool.

The online tool is pretty straightforward, however I’ve only tested the free encryption. Let’s just say that it can be very painfull to encrypt large or multiple files in this way. The reasons are obvious.

The encoder tool can be downloaded from the download section for free. It is available for Linux, FreeBSD and Mac OS X Lion.

It says that is available for Linux kernel 2.6 but I have used it with 3.x series with no problem.

If you run the encoder with no options you’ll get the available options. Let me quote them and explain some of them.

OPTIONS
     -d               do not include the dynamic extension loader
     -f               force encoding of already-encoded files
     -l lpath         looks in lpath for the licence file (instead of in
                      /etc/phpshadow)
     -o               overwrite files (no backup copy made)
     -s filename      skips files named filename
     -t               uses the free trial licence (introduces a 10-second delay
                      when the script is executed)
     -x extension     encodes files ending in ".extension"  (in addition to
                      the default ".php")

Without the -d option the dynamic loader will be included and executed before our code. This looks like this:

<?php
eval(base64_decode('aWYoIWV4dGVuc2lvbl9sb2FkZWQoJ1BIUHNoYWRvdycpKXtpZighaW5p
X2dldCgnZW5hYmxlX2RsJykpe2V4aXQoIkV4dGVuc2lvbiBsb2FkaW5nIGRpc2FibGVkIG9uIHNl
cnZlci5cbiIpO31pZighZnVuY3Rpb25fZXhpc3RzKCdkbCcpKXtleGl0KCJFeHRlbnNpb24gbG9h
ZGluZyBub3QgYXZhaWxhYmxlIGZvciB0aGlzIHNhcGkgKCIucGhwX3NhcGlfbmFtZSgpLiIpLlxu
Iik7fWlmKCFmaWxlX2V4aXN0cyhpbmlfZ2V0KCdleHRlbnNpb25fZGlyJykuJy9waHBzaGFkb3cu
c28nKSl7ZXhpdCgiRXh0ZW5zaW9uIG1pc3NpbmcgZnJvbSBleHRlbnNpb25zIGRpcmVjdG9yeSAo
Ii5pbmlfZ2V0KCdleHRlbnNpb25fZGlyJykuIikuICBJZiB5b3UgYXJlIHVzaW5nIGEgc2hhcmVk
IGhvc3RpbmcgZW52aXJvbm1lbnQsIGFzayB5b3VyIHByb3ZpZGVyIHRvIHBsYWNlIGEgY29weSBv
ZiB0aGUgUEhQc2hhZG93IHNlcnZlciBleHRlbnNpb24gaW4gdGhlIGV4dGVuc2lvbnMgZGlyZWN0
b3J5LlxuIik7fWlmKGRsKCdwaHBzaGFkb3cuc28nKT09PWZhbHNlKXtleGl0KCJFeHRlbnNpb24g
ZmFpbGVkIHRvIGxvYWQuXG4iKTt9fQ=='));
?>

This is actually PHP code in base64 format that will be executed by calling the PHP’s eval() method. If we decode it, it looks like this:

<?php
if(!extension_loaded('PHPshadow')){
        if(!ini_get('enable_dl')){
                exit("Extension loading disabled on server.\n");
        }
        if(!function_exists('dl')){
                exit("Extension loading not available for this sapi (
                ".php_sapi_name().").\n");
        }
        if(!file_exists(ini_get('extension_dir').'/phpshadow.so')){
                exit("Extension missing from extensions directory (
                ".ini_get('extension_dir').").
                 If you are using a shared hosting environment, ask your provider
                 to place a copy of the PHPshadow server extension in
                 the extensions directory.\n");
        }
        if(dl('phpshadow.so')===false){
                exit("Extension failed to load.\n");
        }
}
?>

As you can understand in case we know the phpshadow extension is on and working, this is a needless check that will slow our code execution, especially if we use multiple inclusions of encrypted files, with no reason.

So, as it is indicated in the guide one should use this only if she isn’t sure about her phpshadow installation.

Other than that there isn’t any other option that needs further explanation.

To encrypt a file you have to be connected to the internet in order for the encoder to be able to check if your license is valid.

In conclusion, I found the encoder easy to use and surprisingly fast!

Installing the PHPShadow extension

Installing phpshadow.so PHP extension isn’t any different from installing any other PHP extension. Just place phpshadow.so in the web server’s extension directory and add the extension=phpshadow.so line in your php.ini file.

Once again there are details in the user guide. Nothing tricky here.

Free PHPShadow Version

The free version introduces a 10 second delay. This delay is in any case unacceptable. I wouldn’t recommend anyone to use it for any case other than getting familiar with PHPShadow’s workflow.

Performance

I have benchmarked PHPShadow’s encrypted code and here are my findings.

Part 1 - ApacheBench

For the first part of my benchmark I’ve used Apache Benchmark version 2.3 (Revision 655654), and in the figures above I’ve compared the requests per second which demonstrates the average time a page has to load. I have run the test in the same enviroment for about 3-5 times and I’ve kept the best average run.

Note I’ve run the benchmark like ab -c 1 <url>. That is concurrency level 1 a.k.a. just one request.

At first I’ve tested a simple Hello World PHP script. As you can see in the chart below all the script variations (unencrypted,encrypted without dynamic loader, encrypted with dynamic loader, encrypted by the free encoder) are almost similar for a project of that scale. However, even for a webpage as simple as this the online encoder ir out of the question.

stats figure 1

After that I wanted to test only the dynamic loader to see how much overhead adds to the whole process.

As shown in the next chart, calling the dynamic encoder code unformatted can serve up to 300 requests more than the base64 one, that is included by the phpshadow encryption tool.

stats figure 2

This means that if you have to include the dynamic loader you should better use the non base64 format. However, as I’ve said before it’s really better to not use it at all.

Continuing my tests I’ve benchmarked two real world projects, the first is the manuf.php that can be found here. It’s actually a 20k lines PHP array with MAC vendor prefixes taken from the wireshark project. The other is arq, a custom MVC project I wrote some months back for a university project and implements multiple file inclusions.

Here are the charts.

stats figure 3

In these cases as we can see unencrypted code can run significantly faster than the encrypted one. Note that in this chart the encrypted code doesn’t use the dynamic loader.

Also, I’d like to mention that the original’s manuf.php code is 20023 lines, 2105628 chars and the encrypted’s manuf_le.php code is 85765 lines, 6603460 chars. Ofcourse this is an extreme case but however this is why I choose it.

Anyway, I have kept playing with the concurrency level and the number of requests with arq (my custom project) until I’ve reached to a peak!

In 10000 requests and concurrency level 25 the arq scored 295.62 requests per second while the encoded version scored 67.67.

stats figure 4

This is pretty significant and shows that the encoded code scales poorly.

Part 2 - Memory Management

I’ve also conducted some tests to find the maximum memory used. To find this I used the PHP’s memory_get_peak_usage() function.

The unencrypted manuf.php peaked 26921244 or 26.92 MB max memory usage, while the encrypted peaked 42244396 or 42.24 MB.

The unencrypted arq project peaked 1245692 or 1.246 MB max memory usage, while the encrypted peaked 1325184 or 1.325 MB

stats figure 6

As we can see in some extreme cases there can be nearly twice the normal maximum memory usage.

Security

Security would be my main concern if I would like to use an encryption mechanism to hide my code. I may not be the best to answer if PHPShadow is totaly secure but as far as I can say, I find it pretty secure.

PHPShadow website claims that:

Decryption is performed on-the-fly and entirely in memory. This means that
data is never saved, shown or stored anywhere - not even temporarily.

Let me waffle a bit before I get to my conclusion.

In the beginning, I tried to analyze the produced code to understand where the encryption key is stored and what is the connection between the original code (plain text) and the produced one (cipher text).

I had no luck on that and the most interesting findings are that unicode characters produce more encrypted text than the ascii ones. Actually, they produce almost 2 times more code which is pretty logical if you consider that UTF-8 requires more bytes to represent the characters.

Another interesting finding is that free encoder produces different ciphertext from the paid one and decoder determines what kind of encoder was used by the first argument of phpshadow_eval() and the first 22bit (I think :P) of the ciphertext.

Finally, when I started analyzing the ciphertext I thought that it was a pattern of keys and ciphertext i.e. qfesAFSD where qfes is the key that decrypts AFSD but as I found out both previous and next ciphertext characters define the decryption product.

This is good because otherwise one could create a pool (something like rainbow tables) of keys and ciphertext and use it to decrypt other ciphertexts with it.

Also, one could use a pool like this to produce a phpshadow encrypted text without a license!

However, I wasn’t able to find out such a relation between encryption key and ciphertext which is rather good or I am noob. I am afraid that the second is more likely but at least I tried :D

A security expert, which I am not, could have another opinion.

After my shot to analyze the ciphertext I wondered if it was possible to run apache in debug mode through gdb and get a glimpse on what is going on behind the scenes, but it seemed a long shot and too much trouble to bother with.

Just from curiosity I would like to hear if that’s possible and if someone had checked it out.

Finally, I tried to see what information I could deduct by using a set of available PHP functions.

Here is my magic code:

<?php
echo "<pre>";
print_r( get_defined_vars());
print_r( get_defined_functions());
print_r( get_declared_classes());
print_r( get_defined_constants());
print_r( get_included_files());
debug_print_backtrace();
// or echo var_dump(debug_backtrace());
echo '</pre>';
?>

This is important because it means that if for example one had an encrypted configuration file another one could just add the get_defined_vars() function at the end of the encrypted code and get all the sensitive information such as database usernames and passwords etc.

This cancels the usage of PHPShadow for protecting configuration files and the like.

One could just unset the variables inside her encrypted block to ensure that noone gets them but that may be impossible in some cases and break dependencies of global variables, especially in large projects.

That’s all about security I think. In general PHPShadow succeeds to protect/hide the code, however a 3rd person could still deduct many important information. I think in combination with the encryption, obfuscation could partially solve this problem.

The best solution however, that I am not aware how doable is, is for PHPShadow to handle this by itself.

Pricing Scheme

When I first spotted PHPShadow, it was selling the server extension and offered the encryption tool for free.

This was a big mistake in my opinion because it made very difficult and especially expensive to install the extension in many servers. Now one can use her encrypted code everywhere as long as the extension is loaded.

Also, this makes the extension more appealing for hosting providers to provide it by default in their hosting packages. This is a win-win situation for both the hosting providers and the PHPShadow.

Now, the price scheme is much better.

However some packages are doomed to fail, or at list they should! Currently the cheaper package offers a 48hours license for 5 EUR and the most expensive a 12 months one for 100 EUR.

If I was going to use PHPShadow in a project of mine I would use the cheapest license. If I wanted to publish a new version in a usual basis I would publish one every 1 month and so I would only had to paid 5x12=60 EUR which is 40 EUR less that the 1 year license.

Ok, I understand that this is a great pressure especially if your product is immature and new bugs pop out all the time. However, with some careful planning this is a very promising tactic, especially for low budget projects.

Other than that one who doesn’t want to spent any money and is a little bold could try to implement another encryption mechanism on top of PHPShadow.

Here I’ll introduce some code snippets that implement this concept. They are pretty naive but they serve our purpose really nicely.

<?php

// Create the keypair
$res=openssl_pkey_new();

// Get private key
openssl_pkey_export($res, $privatekey);

file_put_contents('privatekey.txt',$privatekey);
// Get public key
$publickey=openssl_pkey_get_details($res);
$publickey=$publickey["key"];

file_put_contents('publickey.txt',$publickey);

?>

This generates an openssl key pair. We’ll use the public key for the encryption and the private for the decryption.

<?php

// Get public key
$publickey=file_get_contents('publickey.txt');

$cleartext = file_get_contents('hello.php');

$c= 1;
$cleartext = str_ireplace('<?php','',$cleartext,$c);
if($pos = strrpos($cleartext, '?>'));
        $cleartext = substr_replace($cleartext,'',$pos,strlen('?>'));

openssl_public_encrypt($cleartext, $crypttext, $publickey);

file_put_contents('hello-enc',$crypttext);

?>

This gets a stored public key and encrypts a file with it. A couple of lines there remove the <?php and ?> tags. Then it puts the the ciphertext into a new file. In that case the original file was a "Hello World" one and the ciphertext was the above:

Cipher Text

The critical part is the one that we’ll execute the encypted file. Here is our precious decoder

<?php

$crypttext = file_get_contents('hello-enc');

$privatekey = '-----BEGIN PRIVATE KEY-----
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJMHoO4lIBqjEqLo
Fdcw5IE15niR87wB/DEn2CJzu5aNjgkmU7cQe3jmL6Qme0bAK0UFgHVDHf/M2nmT
DpMLUGiiY47/eD6awUh8ROFq34DLog8k5kmp+kY7WJ49NqNNNgU+gA6gVm93ncKJ
pcMGayfTaEKsdXJ0m7Z/haDeBaJ3AgMBAAECgYAXgBGl3sM53rS82xGampL7YA0d
Wl61vy96/95Y61yhXLDGH50j1nuVwFz/BLORhGemGZIFrBugZjLJWcrrj9RjFwSY
mTzBPwj6Qy5YI0LQDe3fGiPugKEJnRZLbCPo9H+gLQDl1onr0uMdZaB1Gn9eOBEo
UmqAKCZapzeY8LkWcQJBAMIRz6+Gc5MK6mGjucQukNJn20es8lfgFqJ00aIjHVhb
6CcuHSYwUcVihTtCjfgwrEr4fEg7MCvHFceZSLsifRkCQQDB8vtQf3q6birHJ68/
x3ZraKkfdAc4TkjEOQGOBIPrA2Y42UqFz+9KfR7WP46gSier7/9k3gk6Rtx1S8Z4
nn4PAkAmlO3qIlu1vvTLxoX29575BYa1oC9pwvYdBAZtKnf6CgOETomi/vYyvJxq
JJge4GZQXUgv//xap7CvxsyLz5Z5AkBzrSiXanEYoHyDK/gqYh0PEu1MBTgJLSfZ
YOa8fZTwpqZhYbhA9zQRiW7K+j1m/NOMiSgaURPP3lO+4eMOLfmhAkBEKu+zSW0H
S1663mH3Lt4kgFDpSMYqUsEU/oOEP1RlIW90LUJxzirsovQHuP/S53QM04N982GU
U8SME98oZ2kc
-----END PRIVATE KEY-----';
$decrypted = '';

openssl_private_decrypt($crypttext, $decrypted, $privatekey);

eval($decrypted);

unset($privatekey);
unset($crypttext);
unset($decrypted);

?>

I get the ciphertext and use the private key to get the plain text. Then, PHP’s function eval() executes the code. Please note here that I had removed the PHP tags that contained the PHP code during the encryption.

After executing the code I unset the variables that contain the sensitive information.

Also, here I use the encrypted file name hello_enc hardcoded into the decoder but we could easily craft a function to pass it dynamically.

So, now just PHPEncode it and you’ll have your own encoding/decoding mechanism.

Let me just say to you that this is not as trivial as it may seems and in the best case it’s performance and security is equivelant of PHPShadow’s.

Conclusion

PHPShadow is easy to use and well documented. It is also really cheap. Spending 5 EUR in order to protect a commercial project that makes you earn money is a really insignificant amount of money. It is also useful for distributing demos of your app or code components that you don’t want your client to mess with.

The security is pretty good. I guess the weak part of PHPShadow is the performance and especially memory usage. This makes it good only for small to medium applications. However, I guess this is good enough for most of the cases.

PHPShadow 4

I’ll try to be brief about PHPShadow version 4.

I really can’t comment the performance but I find it very reasonable to be worse than the unencrypted code, both in memory usage and execution time. However, I strongly believe that it will be ok for small to medium projects.

As for the security I can only tell that PHPShadow 4 uses both letters and numbers for the cipher-text.

Here is an example:

<?php
/* This script is protected by PHPshadow.  Visit http://www.phpshadow.com */
phpshadow_exec(1,__FILE__,"
dfcd1f6822bfc8bf8f1e0a2719f58be2c17591003dd5a8f2bed21ac635546914
","
92f9489e711898324620984e1ad46037acf02944ed5815b75610899a2fd6867ac9aa0b50821f
a1fd7df1f8a2afa26149
"); ?>

This makes me believe that it’s harder to get cracked by brute force than the previous version.

Also, note that the 64bit key is more distinguished in this version.

Now for the things that I can comment:

Pros

  • Easy to use

  • Very comprehensible manual

  • Quick encryption with the encryption tool

  • If you have a license of a previous version that hasn’t expired you can use it

  • In case of upgrade PHPShadow’s team will probably send you a license that lasts for a small period of time in order to upgrade your projects to the new version

  • Reasonable price, see also the Pricing Scheme section in this article for more info

  • Smaller cipher-text, the manuf.php encrypted with PHPshadow 4 is 6603460 chars long, 35% smaller than that encoded with the version 3.2.0

  • Free email support

Cons

  • Server extension is not backward compatible with version 3.2.0

  • As far as I can tell a server cannot have both PHPShadow 3.2.0 and 4 extensions installed

  • There is no way for one to update an old project to version 4. Ofcourse, as I said it’s very likely the PHPShadow team will send him a new license but what if he couldn’t upgrade all his projects before the license expires? I think a tool that converts encrypted code form a previous PHPShadow version to the latest would be nice :)

That’s all I can really tell about PHPShadow 4. Try it for yourself and let me know if you liked it :)

Keep coding!

Comments

fade out