├── .editorconfig ├── .gitignore ├── LICENSE.md ├── README.md ├── bin └── phpcrypter ├── composer.json ├── src └── Console │ └── Commands │ ├── Decrypt.php │ ├── Encrypt.php │ └── Generate.php └── stubs └── php └── ext ├── openssl └── php_openssl.h └── skeleton ├── config.m4 ├── config.w32 ├── skeleton.c └── skeleton.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpcrypter 2 | /vendor/ 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Christopher Pearse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A PHP Source Code Encrypter 2 | 3 | The goal of this open source package is **security _through_ obscurity**. 4 | 5 | It aims to offer an alternative to delivering your closed source projects in **plaintext**. Instead, you can opt to deliver them in **ciphertext** (encrypted), alongside a binary PHP extension which will decrypt them on the fly. 6 | 7 | This package uses symmetric encryption, therefore the AES-256 key (which is only known to you as the developer), can be unique per project and/or release. To avoid being detected by hex editors (e.g. [Hex Fiend](https://hexfiend.com/)) and the [strings](https://www.unix.com/man-page/osx/1/strings) command, the key is stored within the binary as an XOR cipher, split into 32 parts. Additionally, the XOR key is also split into 32 parts. All 64 key parts are then shuffled together along with 64 _random_ key parts (128 parts in total) to ensure that the AES-256 and XOR key parts never appear in the same place twice. 8 | 9 | #### Why encryption, not obfuscation? 10 | 11 | If you search for an obfuscation package, there is almost always a complimentary deobfuscation package available (written by someone else), which renders the original package obsolete (unfortunately). On the other hand, AES-256 encryption hasn't been broken (yet)! 12 | 13 | That being said, I would certainly consider obfuscation as a compliment to encryption. If your source code is obfuscated first (before encryption) and someone tries to reverse engineer your project by looking at the opcodes and stepping through it, it would be much more difficult to understand. 14 | 15 | Typically, obfuscation focuses on altering the execution flow of your source code, combined with the scrambling of the names of your classes, methods, functions, variables and string literals. Because obfuscation essentially rewrites your code, it inevitably comes with a few "gotchas" along the way. Encryption, on the other hand, keeps your code intact (exactly as you wrote it). 16 | 17 | ## Requirements 18 | 19 | ### macOS/Linux 20 | 21 | 1. PHP ^8.2 22 | 2. `phpize` 23 | 24 | ### Windows 25 | 26 | This package was built with support for Windows in mind, however, it has not been tested yet. 27 | 28 | ## Installation 29 | 30 | The below assumes that you're currently in your application's root directory. 31 | 32 | ```console 33 | $ composer require chrisvpearse/phpcrypter --dev 34 | ``` 35 | 36 | ## Usage 37 | 38 | ### Generate a Key 39 | 40 | ```console 41 | $ ./vendor/bin/phpcrypter generate [--clean] [--] [] 42 | ``` 43 | 44 | The below command will generate a unique AES-256-CBC symmetric key named `foo`: 45 | 46 | ```console 47 | $ ./vendor/bin/phpcrypter generate foo 48 | ``` 49 | 50 | Additionally, a `.phpcrypter/foo` directory will be created in your application's root, containing a PHP extension skeleton. The symmetric key is the :heart: of the skeleton :bone: — they will both be used to later build a binary PHP extension of the same name (`foo.so`). 51 | 52 | A good rule of thumb is one key (and therefore one PHP extension) per project. 53 | 54 | The output of the above command will be similar to the following: 55 | 56 | ``` 57 | Success! 58 | Payload: pAYL0AD== 59 | ``` 60 | 61 | :exclamation: Please remember to add `/.phpcrypter` to your `.gitignore` file. 62 | 63 | :bangbang: Additionally, it is important to save the payload in a password manager, such as [1Password](https://1password.com) or [pass](https://www.passwordstore.org). 64 | 65 | ### Build the PHP Extension 66 | 67 | #### macOS/Linux 68 | 69 | ```console 70 | $ cd .phpcrypter/foo 71 | $ phpize 72 | $ ./configure 73 | $ make 74 | $ make install 75 | ``` 76 | 77 | The above commands will build a PHP extension named `foo.so` and copy it into your PHP extension directory. The directory can be found via the following command: 78 | 79 | ```console 80 | $ php -i | grep ^extension_dir 81 | ``` 82 | 83 | You should then add the following line to your `php.ini` configuration file: 84 | 85 | ``` 86 | extension=foo.so 87 | ``` 88 | 89 | The location of the loaded `php.ini` configuration file can be found via the following command: 90 | 91 | ```console 92 | $ php -i | grep "Loaded Configuration File" 93 | ``` 94 | 95 | Next, verify that the extension is _loaded_: 96 | 97 | ```console 98 | $ php -m | grep foo 99 | foo 100 | ``` 101 | 102 | ### Encrypt Directories and/or Files 103 | 104 | ```console 105 | $ ./vendor/bin/phpcrypter encrypt ... 106 | ``` 107 | 108 | The below encrypts multiple directories and files at once. You must specify the previously obtained `payload` as the first argument. 109 | 110 | ```console 111 | $ ./vendor/bin/phpcrypter encrypt "pAYL0AD==" \ 112 | "dir-1" \ 113 | "dir-2" \ 114 | "file-1.php" \ 115 | "file-2.php" 116 | ``` 117 | 118 | :exclamation: The contents of any PHP files found in the above paths will be overwritten. It is highly recommended that you create a new Git branch for these files: 119 | 120 | ```console 121 | $ git checkout -b encrypted 122 | ``` 123 | 124 | #### Decrypt 125 | 126 | If you're just experimenting, it's useful to be able to encrypt and decrypt at will. The below decrypts any directories and/or files previously encrypted with the `payload` argument: 127 | 128 | ```console 129 | $ ./vendor/bin/phpcrypter decrypt ... 130 | ``` 131 | 132 | :exclamation: Again, the contents of any PHP files found in the above paths will be overwritten. 133 | 134 | #### What does an encrypted file look like? 135 | 136 | ```php 137 | add(new Generate()); 13 | $application->add(new Encrypt()); 14 | $application->add(new Decrypt()); 15 | 16 | $application->run(); 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrisvpearse/phpcrypter", 3 | "description": "A PHP source code encrypter.", 4 | "keywords": [ 5 | "chrisvpearse", 6 | "phpcrypter" 7 | ], 8 | "homepage": "https://github.com/chrisvpearse/phpcrypter", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Christopher Pearse", 13 | "email": "chris@cpearse.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.2", 18 | "ext-openssl": "*", 19 | "symfony/console": "^6.3" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "Crypter\\": "src/" 24 | } 25 | }, 26 | "config": { 27 | "sort-packages": true 28 | }, 29 | "minimum-stability": "dev", 30 | "prefer-stable": true, 31 | "bin": [ 32 | "bin/phpcrypter" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /src/Console/Commands/Decrypt.php: -------------------------------------------------------------------------------- 1 | addArgument('payload', InputArgument::REQUIRED); 23 | $this->addArgument('path', InputArgument::REQUIRED | InputArgument::IS_ARRAY); 24 | } 25 | 26 | protected function execute(InputInterface $input, OutputInterface $output) 27 | { 28 | $payload = $input->getArgument('payload'); 29 | $path = $input->getArgument('path'); 30 | 31 | $payload = base64_decode($payload); 32 | 33 | if (! $payload || substr_count($payload, ',') != 2) { 34 | $output->writeln('The payload format is invalid'); 35 | 36 | return Command::FAILURE; 37 | } 38 | 39 | [$name, $key] = explode(',', $payload, 3); 40 | 41 | $key = base64_decode($key); 42 | 43 | if (! $key || strlen($key) != openssl_cipher_key_length($this->cipherAlgo)) { 44 | $output->writeln('The key within the payload is invalid'); 45 | 46 | return Command::FAILURE; 47 | } 48 | 49 | foreach ($path as $p) { 50 | $p = getcwd().'/'.$p; 51 | 52 | if (is_file($p)) { 53 | $p = new SplFileInfo($p); 54 | 55 | $this->decrypt($output, $p, $name, $key); 56 | } elseif (is_dir($p)) { 57 | $iterator = new RegexIterator( 58 | new RecursiveIteratorIterator( 59 | new RecursiveDirectoryIterator( 60 | $p, 61 | RecursiveDirectoryIterator::SKIP_DOTS 62 | ), 63 | RecursiveIteratorIterator::CHILD_FIRST 64 | ), 65 | '/\.php$/' 66 | ); 67 | 68 | foreach ($iterator as $item) { 69 | $this->decrypt($output, $item, $name, $key); 70 | } 71 | } 72 | } 73 | 74 | return Command::SUCCESS; 75 | } 76 | 77 | private function decrypt($output, $file, $name, $key) 78 | { 79 | $contents = file_get_contents($file->getPathname()); 80 | 81 | if (! empty($contents)) { 82 | $sig = 'cipherAlgo, 92 | $key, 93 | 0, 94 | base64_decode($iv) 95 | ); 96 | 97 | if (file_put_contents($file->getPathname(), $decrypted)) { 98 | $output->writeln('Decrypted! '.$file->getPathname()); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Console/Commands/Encrypt.php: -------------------------------------------------------------------------------- 1 | addArgument('payload', InputArgument::REQUIRED); 26 | $this->addArgument('path', InputArgument::REQUIRED | InputArgument::IS_ARRAY); 27 | } 28 | 29 | protected function execute(InputInterface $input, OutputInterface $output) 30 | { 31 | $payload = $input->getArgument('payload'); 32 | $path = $input->getArgument('path'); 33 | 34 | $payload = base64_decode($payload); 35 | 36 | if (! $payload || substr_count($payload, ',') != 2) { 37 | $output->writeln('The payload format is invalid'); 38 | 39 | return Command::FAILURE; 40 | } 41 | 42 | [$name, $key] = explode(',', $payload, 3); 43 | 44 | $key = base64_decode($key); 45 | 46 | if (! $key || strlen($key) != openssl_cipher_key_length($this->cipherAlgo)) { 47 | $output->writeln('The key within the payload is invalid'); 48 | 49 | return Command::FAILURE; 50 | } 51 | 52 | if (file_exists(__DIR__.'/../../../../../../bootstrap/app.php')) { 53 | $app = require_once __DIR__.'/../../../../../../bootstrap/app.php'; 54 | $app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap(); 55 | 56 | $this->bladeCompiler = $app->make('blade.compiler'); 57 | } 58 | 59 | foreach ($path as $p) { 60 | $p = getcwd().'/'.$p; 61 | 62 | if (is_file($p)) { 63 | $p = new SplFileInfo($p); 64 | 65 | $this->encrypt($output, $p, $name, $key); 66 | } elseif (is_dir($p)) { 67 | $iterator = new RegexIterator( 68 | new RecursiveIteratorIterator( 69 | new RecursiveDirectoryIterator( 70 | $p, 71 | RecursiveDirectoryIterator::SKIP_DOTS 72 | ), 73 | RecursiveIteratorIterator::CHILD_FIRST 74 | ), 75 | '/\.php$/' 76 | ); 77 | 78 | foreach ($iterator as $item) { 79 | $this->encrypt($output, $item, $name, $key); 80 | } 81 | } 82 | } 83 | 84 | return Command::SUCCESS; 85 | } 86 | 87 | private function encrypt($output, $file, $name, $key) 88 | { 89 | $contents = file_get_contents($file->getPathname()); 90 | 91 | if (! empty($contents)) { 92 | $sig = 'writeln('Already Encrypted. '.$file->getPathname()); 96 | } else { 97 | if ($this->bladeCompiler && strpos($file->getFilename(), '.blade.php') !== false) { 98 | $contents = $this->bladeCompiler->compileString($contents); 99 | 100 | $newPath = $file->getPath().DIRECTORY_SEPARATOR.str_replace('.blade.php', '.php', $file->getFilename()); 101 | 102 | if (file_put_contents($file->getPathname(), $contents) && rename($file->getPathname(), $newPath)) { 103 | $output->writeln('Compiled Blade! '.$file->getPathname()); 104 | } 105 | } 106 | 107 | $iv = random_bytes(openssl_cipher_iv_length($this->cipherAlgo)); 108 | 109 | $encrypted = openssl_encrypt( 110 | $contents, 111 | $this->cipherAlgo, 112 | $key, 113 | 0, 114 | $iv 115 | ); 116 | 117 | $encoded = base64_encode($this->version.','.base64_encode($iv).','.$encrypted); 118 | 119 | $php = <<getPathname(); 126 | 127 | if (file_put_contents($p, $php)) { 128 | $output->writeln('Encrypted! '.$p); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Console/Commands/Generate.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED); 22 | $this->addArgument('payload', InputArgument::OPTIONAL); 23 | $this->addOption('clean', null, InputOption::VALUE_NONE); 24 | } 25 | 26 | protected function execute(InputInterface $input, OutputInterface $output) 27 | { 28 | $name = $input->getArgument('name'); 29 | $payload = $input->getArgument('payload'); 30 | $clean = $input->getOption('clean'); 31 | 32 | if (! preg_match('/^[a-zA-Z]+$/', $name)) { 33 | $output->writeln('The "name" must contain alpha characters only'); 34 | 35 | return Command::FAILURE; 36 | } 37 | 38 | $name = strtolower($name); 39 | 40 | $dir = '.phpcrypter/'.$name; 41 | $path = getcwd().'/'.$dir; 42 | 43 | if (is_dir($path)) { 44 | if ($clean) { 45 | $iterator = new RecursiveIteratorIterator( 46 | new RecursiveDirectoryIterator( 47 | $path, 48 | RecursiveDirectoryIterator::SKIP_DOTS 49 | ), 50 | RecursiveIteratorIterator::CHILD_FIRST 51 | ); 52 | 53 | foreach ($iterator as $item) { 54 | if ($item->isDir()) { 55 | if (! rmdir($item->getPathname())) { 56 | $output->writeln('Could not remove directory: '.$item->getPathname().''); 57 | 58 | return Command::FAILURE; 59 | } 60 | } else { 61 | if (! unlink($item->getPathname())) { 62 | $output->writeln('Could not remove file: '.$item->getPathname().''); 63 | 64 | return Command::FAILURE; 65 | } 66 | } 67 | } 68 | } else { 69 | $output->writeln('"'.$dir.'" already exists in the current working directory'); 70 | $output->writeln('To rebuild, please specify the "--clean" option'); 71 | 72 | return Command::FAILURE; 73 | } 74 | } else { 75 | if (! mkdir($path, 0777, true)) { 76 | $output->writeln('Could not make "'.$dir.'" in the current working directory'); 77 | 78 | return Command::FAILURE; 79 | } 80 | } 81 | 82 | if (is_null($payload)) { 83 | $key = random_bytes(openssl_cipher_key_length($this->cipherAlgo)); 84 | $xorKey = random_bytes(openssl_cipher_key_length($this->cipherAlgo)); 85 | } else { 86 | $payload = base64_decode($payload); 87 | 88 | if (! $payload || substr_count($payload, ',') != 2) { 89 | $output->writeln('The payload format is invalid'); 90 | 91 | return Command::FAILURE; 92 | } 93 | 94 | [, $key, $xorKey] = explode(',', $payload, 3); 95 | 96 | $key = base64_decode($key); 97 | $xorKey = base64_decode($xorKey); 98 | 99 | if (! $key || strlen($key) != openssl_cipher_key_length($this->cipherAlgo)) { 100 | $output->writeln('The key within the payload is invalid'); 101 | 102 | return Command::FAILURE; 103 | } 104 | 105 | if (! $xorKey || strlen($xorKey) != openssl_cipher_key_length($this->cipherAlgo)) { 106 | $output->writeln('The XOR key within the payload is invalid'); 107 | 108 | return Command::FAILURE; 109 | } 110 | } 111 | 112 | $keyXorArr = []; 113 | $xorKeyArr = []; 114 | 115 | for ($i = 0; $i < strlen($key); $i++) { 116 | $byte = $key[$i] ^ $xorKey[$i % strlen($xorKey)]; 117 | 118 | $keyXorArr[$i] = '0x'.bin2hex($byte); 119 | } 120 | 121 | foreach (str_split($xorKey) as $byte) { 122 | $xorKeyArr[] = '0x'.bin2hex($byte); 123 | } 124 | 125 | $char = []; 126 | $memcpy = []; 127 | 128 | foreach ($keyXorArr as $k => $byte) { 129 | $i = $k + 1; 130 | 131 | $char[] = 'unsigned char key_xor_'.$i.'[] = {'.$byte.'};'; 132 | 133 | $memcpy[] = 'memcpy(key_xor + '.$k.', key_xor_'.$i.', sizeof(key_xor_'.$i.'));'; 134 | } 135 | 136 | foreach ($xorKeyArr as $k => $byte) { 137 | $i = $k + 1; 138 | 139 | $char[] = 'unsigned char xor_key_'.$i.'[] = {'.$byte.'};'; 140 | 141 | $memcpy[] = 'memcpy(xor_key + '.$k.', xor_key_'.$i.', sizeof(xor_key_'.$i.'));'; 142 | } 143 | 144 | for ($i = 1; $i <= 32; $i++) { 145 | $j = $i + 32; 146 | 147 | $byte = '0x'.bin2hex(random_bytes(1)); 148 | $char[] = 'unsigned char key_xor_'.$j.'[] = {'.$byte.'};'; 149 | 150 | $byte = '0x'.bin2hex(random_bytes(1)); 151 | $char[] = 'unsigned char xor_key_'.$j.'[] = {'.$byte.'};'; 152 | } 153 | 154 | shuffle($char); 155 | 156 | $char = implode(' ', $char); 157 | $memcpy = implode(' ', $memcpy); 158 | 159 | $search = [ 160 | 'skeleton', 161 | 'SKELETON', 162 | '// @char', 163 | '// @memcpy', 164 | ]; 165 | 166 | $replace = [ 167 | $name, 168 | strtoupper($name), 169 | $char, 170 | $memcpy, 171 | ]; 172 | 173 | $phpStubs = __DIR__.'/../../../stubs/php'; 174 | 175 | $extSkeleton = 'ext/skeleton'; 176 | $extOpenssl = 'ext/openssl'; 177 | 178 | $paths = [ 179 | [ 180 | 'from' => $phpStubs.'/'.$extSkeleton.'/config.m4', 181 | 'to' => $path.'/config.m4', 182 | ], 183 | [ 184 | 'from' => $phpStubs.'/'.$extSkeleton.'/config.w32', 185 | 'to' => $path.'/config.w32', 186 | ], 187 | [ 188 | 'from' => $phpStubs.'/'.$extSkeleton.'/skeleton.c', 189 | 'to' => $path.'/'.$name.'.c', 190 | ], 191 | [ 192 | 'from' => $phpStubs.'/'.$extSkeleton.'/skeleton.h', 193 | 'to' => $path.'/'.$name.'.h', 194 | ], 195 | [ 196 | 'from' => $phpStubs.'/'.$extOpenssl.'/php_openssl.h', 197 | 'to' => $path.'/'.$extOpenssl.'/php_openssl.h', 198 | ], 199 | ]; 200 | 201 | if (! mkdir($path.'/'.$extOpenssl, 0777, true)) { 202 | $output->writeln('Could not make directory: '.$path.'/'.$extOpenssl.''); 203 | 204 | return Command::FAILURE; 205 | } 206 | 207 | foreach ($paths as $path) { 208 | if (is_file($path['from']) && ! file_put_contents($path['to'], str_replace($search, $replace, file_get_contents($path['from'])))) { 209 | $output->writeln('Could not make file: '.$path['to'].''); 210 | 211 | return Command::FAILURE; 212 | } 213 | } 214 | 215 | $payload = base64_encode( 216 | $name.','.base64_encode($key).','.base64_encode($xorKey) 217 | ); 218 | 219 | $output->writeln('Success!'); 220 | $output->writeln('Payload: '.$payload.''); 221 | 222 | return Command::SUCCESS; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /stubs/php/ext/openssl/php_openssl.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | Copyright (c) The PHP Group | 4 | +----------------------------------------------------------------------+ 5 | | This source file is subject to version 3.01 of the PHP license, | 6 | | that is bundled with this package in the file LICENSE, and is | 7 | | available through the world-wide-web at the following url: | 8 | | https://www.php.net/license/3_01.txt | 9 | | If you did not receive a copy of the PHP license and are unable to | 10 | | obtain it through the world-wide-web, please send a note to | 11 | | license@php.net so we can mail you a copy immediately. | 12 | +----------------------------------------------------------------------+ 13 | | Authors: Stig Venaas | 14 | | Wez Furlong 29 | #if defined(LIBRESSL_VERSION_NUMBER) 30 | /* LibreSSL version check */ 31 | #if LIBRESSL_VERSION_NUMBER < 0x20700000L 32 | #define PHP_OPENSSL_API_VERSION 0x10001 33 | #else 34 | #define PHP_OPENSSL_API_VERSION 0x10100 35 | #endif 36 | #else 37 | /* OpenSSL version check */ 38 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 39 | #define PHP_OPENSSL_API_VERSION 0x10002 40 | #elif OPENSSL_VERSION_NUMBER < 0x30000000L 41 | #define PHP_OPENSSL_API_VERSION 0x10100 42 | #else 43 | #define PHP_OPENSSL_API_VERSION 0x30000 44 | #endif 45 | #endif 46 | 47 | #define OPENSSL_RAW_DATA 1 48 | #define OPENSSL_ZERO_PADDING 2 49 | #define OPENSSL_DONT_ZERO_PAD_KEY 4 50 | 51 | #define OPENSSL_ERROR_X509_PRIVATE_KEY_VALUES_MISMATCH 0x0B080074 52 | 53 | /* Used for client-initiated handshake renegotiation DoS protection*/ 54 | #define OPENSSL_DEFAULT_RENEG_LIMIT 2 55 | #define OPENSSL_DEFAULT_RENEG_WINDOW 300 56 | #define OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH 9 57 | #define OPENSSL_DEFAULT_STREAM_CIPHERS "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:" \ 58 | "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:" \ 59 | "DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:" \ 60 | "ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:" \ 61 | "ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:" \ 62 | "DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:" \ 63 | "AES256-GCM-SHA384:AES128:AES256:HIGH:!SSLv2:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!RC4:!ADH" 64 | 65 | #include 66 | 67 | #ifdef PHP_WIN32 68 | # define PHP_OPENSSL_API __declspec(dllexport) 69 | #elif defined(__GNUC__) && __GNUC__ >= 4 70 | # define PHP_OPENSSL_API __attribute__((visibility("default"))) 71 | #else 72 | # define PHP_OPENSSL_API 73 | #endif 74 | 75 | struct php_openssl_errors { 76 | int buffer[ERR_NUM_ERRORS]; 77 | int top; 78 | int bottom; 79 | }; 80 | 81 | ZEND_BEGIN_MODULE_GLOBALS(openssl) 82 | struct php_openssl_errors *errors; 83 | ZEND_END_MODULE_GLOBALS(openssl) 84 | 85 | #define OPENSSL_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(openssl, v) 86 | 87 | #if defined(ZTS) && defined(COMPILE_DL_OPENSSL) 88 | ZEND_TSRMLS_CACHE_EXTERN(); 89 | #endif 90 | 91 | php_stream_transport_factory_func php_openssl_ssl_socket_factory; 92 | 93 | void php_openssl_store_errors(void); 94 | 95 | /* openssl file path extra */ 96 | bool php_openssl_check_path_ex( 97 | const char *file_path, size_t file_path_len, char *real_path, uint32_t arg_num, 98 | bool contains_file_protocol, bool is_from_array, const char *option_name); 99 | 100 | /* openssl file path check */ 101 | static inline bool php_openssl_check_path( 102 | const char *file_path, size_t file_path_len, char *real_path, uint32_t arg_num) 103 | { 104 | return php_openssl_check_path_ex( 105 | file_path, file_path_len, real_path, arg_num, false, false, NULL); 106 | } 107 | 108 | /* openssl file path extra check with zend string */ 109 | static inline bool php_openssl_check_path_str_ex( 110 | zend_string *file_path, char *real_path, uint32_t arg_num, 111 | bool contains_file_protocol, bool is_from_array, const char *option_name) 112 | { 113 | return php_openssl_check_path_ex( 114 | ZSTR_VAL(file_path), ZSTR_LEN(file_path), real_path, arg_num, contains_file_protocol, 115 | is_from_array, option_name); 116 | } 117 | 118 | /* openssl file path check with zend string */ 119 | static inline bool php_openssl_check_path_str( 120 | zend_string *file_path, char *real_path, uint32_t arg_num) 121 | { 122 | return php_openssl_check_path_str_ex(file_path, real_path, arg_num, true, false, NULL); 123 | } 124 | 125 | PHP_OPENSSL_API zend_long php_openssl_cipher_iv_length(const char *method); 126 | PHP_OPENSSL_API zend_long php_openssl_cipher_key_length(const char *method); 127 | PHP_OPENSSL_API zend_string* php_openssl_random_pseudo_bytes(zend_long length); 128 | PHP_OPENSSL_API zend_string* php_openssl_encrypt( 129 | const char *data, size_t data_len, 130 | const char *method, size_t method_len, 131 | const char *password, size_t password_len, 132 | zend_long options, 133 | const char *iv, size_t iv_len, 134 | zval *tag, zend_long tag_len, 135 | const char *aad, size_t aad_len); 136 | PHP_OPENSSL_API zend_string* php_openssl_decrypt( 137 | const char *data, size_t data_len, 138 | const char *method, size_t method_len, 139 | const char *password, size_t password_len, 140 | zend_long options, 141 | const char *iv, size_t iv_len, 142 | const char *tag, zend_long tag_len, 143 | const char *aad, size_t aad_len); 144 | 145 | /* OpenSSLCertificate class */ 146 | 147 | typedef struct _php_openssl_certificate_object { 148 | X509 *x509; 149 | zend_object std; 150 | } php_openssl_certificate_object; 151 | 152 | extern zend_class_entry *php_openssl_certificate_ce; 153 | 154 | static inline php_openssl_certificate_object *php_openssl_certificate_from_obj(zend_object *obj) { 155 | return (php_openssl_certificate_object *)((char *)(obj) - XtOffsetOf(php_openssl_certificate_object, std)); 156 | } 157 | 158 | #define Z_OPENSSL_CERTIFICATE_P(zv) php_openssl_certificate_from_obj(Z_OBJ_P(zv)) 159 | 160 | PHP_MINIT_FUNCTION(openssl); 161 | PHP_MSHUTDOWN_FUNCTION(openssl); 162 | PHP_MINFO_FUNCTION(openssl); 163 | PHP_GINIT_FUNCTION(openssl); 164 | PHP_GSHUTDOWN_FUNCTION(openssl); 165 | 166 | #ifdef PHP_WIN32 167 | #define PHP_OPENSSL_BIO_MODE_R(flags) (((flags) & PKCS7_BINARY) ? "rb" : "r") 168 | #define PHP_OPENSSL_BIO_MODE_W(flags) (((flags) & PKCS7_BINARY) ? "wb" : "w") 169 | #else 170 | #define PHP_OPENSSL_BIO_MODE_R(flags) "r" 171 | #define PHP_OPENSSL_BIO_MODE_W(flags) "w" 172 | #endif 173 | 174 | #else 175 | 176 | #define phpext_openssl_ptr NULL 177 | 178 | #endif 179 | 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /stubs/php/ext/skeleton/config.m4: -------------------------------------------------------------------------------- 1 | PHP_ARG_ENABLE(skeleton, whether to enable skeleton support, 2 | [ --enable-skeleton Enable skeleton support]) 3 | 4 | if test "$PHP_SKELETON" != "no"; then 5 | PHP_NEW_EXTENSION(skeleton, skeleton.c, $ext_shared) 6 | fi 7 | -------------------------------------------------------------------------------- /stubs/php/ext/skeleton/config.w32: -------------------------------------------------------------------------------- 1 | ARG_ENABLE("skeleton", "enable skeleton support", "no"); 2 | 3 | if (PHP_SKELETON != "no") { 4 | EXTENSION("skeleton", "skeleton.c"); 5 | } 6 | -------------------------------------------------------------------------------- /stubs/php/ext/skeleton/skeleton.c: -------------------------------------------------------------------------------- 1 | #include "php.h" 2 | #include "ext/standard/info.h" 3 | #include "ext/standard/file.h" 4 | #include "ext/standard/base64.h" 5 | #include "ext/openssl/php_openssl.h" 6 | #include "skeleton.h" 7 | 8 | ZEND_DECLARE_MODULE_GLOBALS(skeleton) 9 | 10 | static zend_op_array* (*old_compile_file)(zend_file_handle *file_handle, int type); 11 | static zend_op_array* new_compile_file(zend_file_handle *file_handle, int type) 12 | { 13 | if (PHP_VERSION_ID < 80200 || ! SKELETON_G(decrypt)) { 14 | return old_compile_file(file_handle, type); 15 | } 16 | 17 | // @char 18 | 19 | do { 20 | FILE *fp; 21 | 22 | fp = fopen(ZSTR_VAL(file_handle->filename), "rb"); 23 | 24 | if (! fp) { 25 | break; 26 | } 27 | 28 | char sig[] = SKELETON_SIG; 29 | size_t sig_length = strlen(sig); 30 | 31 | char *sig_buffer = (char *)emalloc(sig_length); 32 | fread(sig_buffer, sizeof(char), sig_length, fp); 33 | 34 | if (memcmp(sig_buffer, sig, sig_length) != 0) { 35 | fclose(fp); 36 | 37 | efree(sig_buffer); 38 | 39 | break; 40 | } 41 | 42 | efree(sig_buffer); 43 | 44 | fseek(fp, 0, SEEK_END); 45 | long file_size = ftell(fp); 46 | fseek(fp, 0, SEEK_SET); 47 | 48 | char *file_contents = (char *)emalloc(file_size); 49 | fread(file_contents, sizeof(char), file_size, fp); 50 | 51 | fclose(fp); 52 | 53 | strtok(file_contents, "#"); 54 | char *encoded_data = strtok(NULL, "#"); 55 | 56 | efree(file_contents); 57 | 58 | if (! encoded_data) { 59 | break; 60 | } 61 | 62 | zend_string *tmp_encoded_data = zend_string_init(encoded_data, strlen(encoded_data), 0); 63 | zend_string *decoded_data = php_base64_decode_str(tmp_encoded_data); 64 | zend_string_release(tmp_encoded_data); 65 | 66 | if (! ZSTR_LEN(decoded_data)) { 67 | break; 68 | } 69 | 70 | char *skeleton_version = strtok(ZSTR_VAL(decoded_data), ","); 71 | char *encoded_iv = strtok(NULL, ","); 72 | char *encrypted_data = strtok(NULL, ","); 73 | 74 | zend_string_release(decoded_data); 75 | 76 | if (! skeleton_version || ! encoded_iv || ! encrypted_data) { 77 | break; 78 | } 79 | 80 | if (strcmp(skeleton_version, SKELETON_VERSION) != 0) { 81 | break; 82 | } 83 | 84 | zend_string *tmp_encoded_iv = zend_string_init(encoded_iv, strlen(encoded_iv), 0); 85 | zend_string *decoded_iv = php_base64_decode_str(tmp_encoded_iv); 86 | zend_string_release(tmp_encoded_iv); 87 | 88 | if (! ZSTR_LEN(decoded_iv)) { 89 | break; 90 | } 91 | 92 | char *iv = ZSTR_VAL(decoded_iv); 93 | 94 | size_t key_xor_length = SKELETON_KEY_LENGTH; 95 | size_t xor_key_length = SKELETON_KEY_LENGTH; 96 | 97 | char key_xor[key_xor_length]; 98 | char xor_key[xor_key_length]; 99 | 100 | // @memcpy 101 | 102 | char key[key_xor_length]; 103 | 104 | for (size_t i = 0; i < key_xor_length; i++) { 105 | key[i] = key_xor[i] ^ xor_key[i % sizeof(xor_key)]; 106 | } 107 | 108 | char *cipher_algo = SKELETON_CIPHER_ALGO; 109 | 110 | zend_string *decrypted_data = php_openssl_decrypt( 111 | encrypted_data, strlen(encrypted_data), 112 | cipher_algo, strlen(cipher_algo), 113 | key, strlen(key), 114 | 0, 115 | iv, strlen(iv), 116 | NULL, 0, 117 | NULL, 0 118 | ); 119 | 120 | if (! ZSTR_LEN(decrypted_data)) { 121 | break; 122 | } 123 | 124 | size_t decrypted_data_length = ZSTR_LEN(decrypted_data); 125 | char *new_buffer = estrndup(ZSTR_VAL(decrypted_data), decrypted_data_length); 126 | 127 | zend_string_release(decrypted_data); 128 | 129 | char *tmp_buffer = NULL; 130 | size_t tmp_length = 0; 131 | 132 | if (zend_stream_fixup(file_handle, &tmp_buffer, &tmp_length) == FAILURE) { 133 | break; 134 | } 135 | 136 | if (file_handle->buf != NULL) { 137 | efree(file_handle->buf); 138 | } 139 | 140 | file_handle->buf = new_buffer; 141 | file_handle->len = decrypted_data_length; 142 | } while (0); 143 | 144 | return old_compile_file(file_handle, type); 145 | } 146 | 147 | static void php_skeleton_init_globals(zend_skeleton_globals *skeleton_globals) { 148 | skeleton_globals->decrypt = 1; 149 | } 150 | 151 | PHP_INI_BEGIN() 152 | STD_PHP_INI_BOOLEAN("skeleton.decrypt", "1", PHP_INI_ALL, OnUpdateBool, decrypt, zend_skeleton_globals, skeleton_globals) 153 | PHP_INI_END() 154 | 155 | PHP_MINIT_FUNCTION(skeleton) 156 | { 157 | ZEND_INIT_MODULE_GLOBALS(skeleton, php_skeleton_init_globals, NULL); 158 | REGISTER_INI_ENTRIES(); 159 | 160 | old_compile_file = zend_compile_file; 161 | zend_compile_file = new_compile_file; 162 | 163 | return SUCCESS; 164 | } 165 | 166 | PHP_MSHUTDOWN_FUNCTION(skeleton) 167 | { 168 | zend_compile_file = old_compile_file; 169 | 170 | return SUCCESS; 171 | } 172 | 173 | PHP_RINIT_FUNCTION(skeleton) 174 | { 175 | return SUCCESS; 176 | } 177 | 178 | PHP_RSHUTDOWN_FUNCTION(skeleton) 179 | { 180 | return SUCCESS; 181 | } 182 | 183 | PHP_MINFO_FUNCTION(skeleton) 184 | { 185 | php_info_print_table_start(); 186 | php_info_print_table_row(2, "skeleton", "enabled"); 187 | php_info_print_table_row(2, "version", SKELETON_VERSION); 188 | php_info_print_table_end(); 189 | } 190 | 191 | zend_function_entry skeleton_functions[] = { 192 | ZEND_FE_END 193 | }; 194 | 195 | zend_module_entry skeleton_module_entry = { 196 | STANDARD_MODULE_HEADER, 197 | "skeleton", 198 | skeleton_functions, 199 | PHP_MINIT(skeleton), 200 | PHP_MSHUTDOWN(skeleton), 201 | PHP_RINIT(skeleton), 202 | PHP_RSHUTDOWN(skeleton), 203 | PHP_MINFO(skeleton), 204 | SKELETON_VERSION, 205 | STANDARD_MODULE_PROPERTIES 206 | }; 207 | 208 | ZEND_GET_MODULE(skeleton) 209 | -------------------------------------------------------------------------------- /stubs/php/ext/skeleton/skeleton.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_SKELETON_H 2 | #define PHP_SKELETON_H 3 | 4 | extern zend_module_entry skeleton_module_entry; 5 | #define phpext_skeleton_ptr &skeleton_module_entry 6 | 7 | #define SKELETON_VERSION "0.1.1" 8 | #define SKELETON_SIG "