Skip to content

Commit

Permalink
Enabled the extension by default & added support for compiling Blade …
Browse files Browse the repository at this point in the history
…into PHP before encrypting
  • Loading branch information
chrisvpearse committed Jan 13, 2024
1 parent 65ea9dd commit 6f44f3d
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 35 deletions.
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ You should then add the following line to your `php.ini` configuration file:

```
extension=foo.so
foo.decrypt=1
```

The location of the loaded `php.ini` configuration file can be found via the following command:
Expand Down Expand Up @@ -145,11 +144,9 @@ The PHP code block should be self explanatory, however, the final line contains

#### How does it work?

By default, when the extension is _loaded_, it simply hooks into the internals of PHP, namely the `zend_compile_file()` function, but it doesn't do anything, unless the `foo.decrypt` configuration option is set to `1`.
By default, when the extension is _loaded_, it simply hooks into the internals of PHP, namely the `zend_compile_file()` function, but it doesn't do anything, unless the `foo.decrypt` configuration option is set to `1` (it is set to `1` by default).

In production, it is recommended that you set `foo.decrypt` to `0` in your `php.ini` configuration file. This means that there's no additional overhead for unencrypted PHP files (which will typically be any open source packages in your Composer dependencies).

Then, it is recommended that you use `ini_set('foo.decrypt', 1)` in any unencrypted PHP files which `include`/`require` encrypted files. For example, if you would like to encrypt a controller, you should use `ini_set()` within an unencrypted base controller. You cannot use `ini_set()` within encrypted PHP files because `zend_compile_file()` works at a lower level.
If you set `foo.decrypt` to `0` in your `php.ini` configuration file, it is recommended that you use `ini_set('foo.decrypt', 1)` in any unencrypted PHP files which `include`/`require` encrypted files. For example, if you would like to encrypt a controller, you should use `ini_set()` within an unencrypted base controller. You cannot use `ini_set()` within encrypted PHP files because `zend_compile_file()` works at a lower level.

Below are some [autocannon](https://github.com/mcollina/autocannon) benchmarks (10 connections for 10s):

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
}
],
"require": {
"php": "^8.1",
"php": "^8.2",
"ext-openssl": "*",
"symfony/console": "^6.3"
},
Expand Down
14 changes: 7 additions & 7 deletions src/Console/Commands/Decrypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#[AsCommand(name: 'decrypt')]
class Decrypt extends Command
{
private $cipherAlgo = 'aes-256-cbc';

protected function configure()
{
$this->addArgument('payload', InputArgument::REQUIRED);
Expand All @@ -26,8 +28,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
$payload = $input->getArgument('payload');
$path = $input->getArgument('path');

$cipherAlgo = 'AES-256-CBC';

$payload = base64_decode($payload);

if (! $payload || substr_count($payload, ',') != 2) {
Expand All @@ -40,7 +40,7 @@ protected function execute(InputInterface $input, OutputInterface $output)

$key = base64_decode($key);

if (! $key || strlen($key) != openssl_cipher_key_length($cipherAlgo)) {
if (! $key || strlen($key) != openssl_cipher_key_length($this->cipherAlgo)) {
$output->writeln('<error>The key within the payload is invalid</error>');

return Command::FAILURE;
Expand All @@ -52,7 +52,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
if (is_file($p)) {
$p = new SplFileInfo($p);

$this->decrypt($output, $p, $name, $cipherAlgo, $key);
$this->decrypt($output, $p, $name, $key);
} elseif (is_dir($p)) {
$iterator = new RegexIterator(
new RecursiveIteratorIterator(
Expand All @@ -66,15 +66,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
);

foreach ($iterator as $item) {
$this->decrypt($output, $item, $name, $cipherAlgo, $key);
$this->decrypt($output, $item, $name, $key);
}
}
}

return Command::SUCCESS;
}

private function decrypt($output, $file, $name, $cipherAlgo, $key)
private function decrypt($output, $file, $name, $key)
{
$contents = file_get_contents($file->getPathname());

Expand All @@ -88,7 +88,7 @@ private function decrypt($output, $file, $name, $cipherAlgo, $key)

$decrypted = openssl_decrypt(
$encrypted,
$cipherAlgo,
$this->cipherAlgo,
$key,
0,
base64_decode($iv)
Expand Down
46 changes: 33 additions & 13 deletions src/Console/Commands/Encrypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
#[AsCommand(name: 'encrypt')]
class Encrypt extends Command
{
private $cipherAlgo = 'aes-256-cbc';
private $version = '0.1.1';

private $bladeCompiler;

protected function configure()
{
$this->addArgument('payload', InputArgument::REQUIRED);
Expand All @@ -26,8 +31,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
$payload = $input->getArgument('payload');
$path = $input->getArgument('path');

$cipherAlgo = 'AES-256-CBC';

$payload = base64_decode($payload);

if (! $payload || substr_count($payload, ',') != 2) {
Expand All @@ -40,19 +43,26 @@ protected function execute(InputInterface $input, OutputInterface $output)

$key = base64_decode($key);

if (! $key || strlen($key) != openssl_cipher_key_length($cipherAlgo)) {
if (! $key || strlen($key) != openssl_cipher_key_length($this->cipherAlgo)) {
$output->writeln('<error>The key within the payload is invalid</error>');

return Command::FAILURE;
}

if (file_exists(__DIR__.'/../../../../../../bootstrap/app.php')) {
$app = require_once __DIR__.'/../../../../../../bootstrap/app.php';
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();

$this->bladeCompiler = $app->make('blade.compiler');
}

foreach ($path as $p) {
$p = getcwd().'/'.$p;

if (is_file($p)) {
$p = new SplFileInfo($p);

$this->encrypt($output, $p, $name, $cipherAlgo, $key);
$this->encrypt($output, $p, $name, $key);
} elseif (is_dir($p)) {
$iterator = new RegexIterator(
new RecursiveIteratorIterator(
Expand All @@ -66,15 +76,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
);

foreach ($iterator as $item) {
$this->encrypt($output, $item, $name, $cipherAlgo, $key);
$this->encrypt($output, $item, $name, $key);
}
}
}

return Command::SUCCESS;
}

private function encrypt($output, $file, $name, $cipherAlgo, $key)
private function encrypt($output, $file, $name, $key)
{
$contents = file_get_contents($file->getPathname());

Expand All @@ -84,28 +94,38 @@ private function encrypt($output, $file, $name, $cipherAlgo, $key)
if (substr($contents, 0, strlen($sig)) == $sig) {
$output->writeln('<comment>Already Encrypted.</comment> '.$file->getPathname());
} else {
$iv = random_bytes(openssl_cipher_iv_length($cipherAlgo));
if ($this->bladeCompiler && strpos($file->getFilename(), '.blade.php') !== false) {
$contents = $this->bladeCompiler->compileString($contents);

$newPath = $file->getPath().DIRECTORY_SEPARATOR.str_replace('.blade.php', '.php', $file->getFilename());

if (file_put_contents($file->getPathname(), $contents) && rename($file->getPathname(), $newPath)) {
$output->writeln('<info>Compiled Blade!</info> '.$file->getPathname());
}
}

$iv = random_bytes(openssl_cipher_iv_length($this->cipherAlgo));

$encrypted = openssl_encrypt(
$contents,
$cipherAlgo,
$this->cipherAlgo,
$key,
0,
$iv
);

$version = '0.1.0';

$encoded = base64_encode($version.','.base64_encode($iv).','.$encrypted);
$encoded = base64_encode($this->version.','.base64_encode($iv).','.$encrypted);

$php = <<<PHP
{$sig}
if (! extension_loaded('{$name}')) exit('The "{$name}" extension is not loaded');
#{$encoded}
PHP;

if (file_put_contents($file->getPathname(), $php)) {
$output->writeln('<info>Encrypted!</info> '.$file->getPathname());
$p = $newPath ?? $file->getPathname();

if (file_put_contents($p, $php)) {
$output->writeln('<info>Encrypted!</info> '.$p);
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/Console/Commands/Generate.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#[AsCommand(name: 'generate')]
class Generate extends Command
{
private $cipherAlgo = 'aes-256-cbc';

protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED);
Expand Down Expand Up @@ -77,11 +79,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
}

$cipherAlgo = 'AES-256-CBC';

if (is_null($payload)) {
$key = random_bytes(openssl_cipher_key_length($cipherAlgo));
$xorKey = random_bytes(openssl_cipher_key_length($cipherAlgo));
$key = random_bytes(openssl_cipher_key_length($this->cipherAlgo));
$xorKey = random_bytes(openssl_cipher_key_length($this->cipherAlgo));
} else {
$payload = base64_decode($payload);

Expand All @@ -96,13 +96,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
$key = base64_decode($key);
$xorKey = base64_decode($xorKey);

if (! $key || strlen($key) != openssl_cipher_key_length($cipherAlgo)) {
if (! $key || strlen($key) != openssl_cipher_key_length($this->cipherAlgo)) {
$output->writeln('<error>The key within the payload is invalid</error>');

return Command::FAILURE;
}

if (! $xorKey || strlen($xorKey) != openssl_cipher_key_length($cipherAlgo)) {
if (! $xorKey || strlen($xorKey) != openssl_cipher_key_length($this->cipherAlgo)) {
$output->writeln('<error>The XOR key within the payload is invalid</error>');

return Command::FAILURE;
Expand Down
4 changes: 2 additions & 2 deletions stubs/php/ext/skeleton/skeleton.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ static zend_op_array* new_compile_file(zend_file_handle *file_handle, int type)
}

static void php_skeleton_init_globals(zend_skeleton_globals *skeleton_globals) {
skeleton_globals->decrypt = 0;
skeleton_globals->decrypt = 1;
}

PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("skeleton.decrypt", "0", PHP_INI_ALL, OnUpdateBool, decrypt, zend_skeleton_globals, skeleton_globals)
STD_PHP_INI_BOOLEAN("skeleton.decrypt", "1", PHP_INI_ALL, OnUpdateBool, decrypt, zend_skeleton_globals, skeleton_globals)
PHP_INI_END()

PHP_MINIT_FUNCTION(skeleton)
Expand Down
2 changes: 1 addition & 1 deletion stubs/php/ext/skeleton/skeleton.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
extern zend_module_entry skeleton_module_entry;
#define phpext_skeleton_ptr &skeleton_module_entry

#define SKELETON_VERSION "0.1.0"
#define SKELETON_VERSION "0.1.1"
#define SKELETON_SIG "<?php // @skeleton"

#define SKELETON_CIPHER_ALGO "AES-256-CBC"
Expand Down

0 comments on commit 6f44f3d

Please sign in to comment.