From 86d851b20e8978b930c51cc6d382c948958f1a86 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 21 Feb 2024 04:08:25 -0600 Subject: [PATCH 1/2] add support for setting target version --- lib/mcrypt.php | 171 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 2 deletions(-) diff --git a/lib/mcrypt.php b/lib/mcrypt.php index ccab850..e58796a 100644 --- a/lib/mcrypt.php +++ b/lib/mcrypt.php @@ -773,6 +773,22 @@ function phpseclib_mdecrypt_generic(Base $td, $data) return phpseclib_mcrypt_generic_helper($td, $data, 'decrypt'); } + /** + * This function terminates encryption + * + * Alias of mcrypt_generic_deinit() + * + * @param Base $td + * @return bool + * @access public + */ + function phpseclib_mcrypt_generic_end(Base $td) + { + // https://web.archive.org/web/20180106174656/https://www.php.net/manual/en/function.mcrypt-generic-end.php + + return phpseclib_mcrypt_generic_deinit($td); + } + /** * This function deinitializes an encryption module * @@ -962,6 +978,47 @@ function phpseclib_mcrypt_module_self_test($algorithm, $lib_dir = '') return in_array($algorithm, phpseclib_mcrypt_list_algorithms()); } + /** + * Encrypt / decrypt data using pre PHP 5.6.0 behavior + * + * Performs checks common to both mcrypt_encrypt and mcrypt_decrypt + * + * @param string $cipher + * @param string $key + * @param string $data + * @param string $mode + * @param string $iv + * @param string $op + * @return string|bool + * @access private + */ + function phpseclib_mcrypt_helper_old($cipher, $key, $data, $mode, $iv, $op) + { + $td = @phpseclib_mcrypt_module_open($cipher, '', $mode, ''); + phpseclib_set_key($td, $key); + + $iv_size = phpseclib_mcrypt_enc_get_iv_size($td); + if ($iv_size && phpseclib_mcrypt_module_is_iv_mode($mode)) { + if (!isset($iv)) { + trigger_error( + 'mcrypt_' . $op . '(): Attempt to use an empty IV, which is NOT recommended', + E_USER_WARNING + ); + $iv = str_repeat("\0", $iv_size); + } elseif (strlen($iv) != $iv_size) { + trigger_error( + 'mcrypt_' . $op . '(): The IV parameter must be as long as the blocksize', + E_USER_WARNING + ); + $iv = str_repeat("\0", $iv_size); + } + } else { + $iv = null; + } + phpseclib_mcrypt_generic_init($td, $key, $iv); + return $op == 'encrypt' ? phpseclib_mcrypt_generic($td, $data) : phpseclib_mdecrypt_generic($td, $data); + } + /** * Encrypt / decrypt data * @@ -1027,6 +1084,85 @@ function phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, $op) return $op == 'encrypt' ? phpseclib_mcrypt_generic($td, $data) : phpseclib_mdecrypt_generic($td, $data); } + /** + * Encrypts/decrypts data in CFB mode + * + * @param string $cipher + * @param string $key + * @param string $data + * @param int $mode + * @param string $iv optional + * @return string|bool + * @access public + */ + function phpseclib_mcrypt_cfb($cipher, $key, $data, $mode, $iv = null) + { + // https://web.archive.org/web/20180106174656/https://www.php.net/manual/en/function.mcrypt-cfb.php + return $mode == MCRYPT_ENCRYPT ? + phpseclib_mcrypt_encrypt($cipher, $key, $data, MCRYPT_MODE_CFB, $iv) : + phpseclib_mcrypt_decrypt($cipher, $key, $data, MCRYPT_MODE_CFB, $iv); + } + + /** + * Encrypts/decrypts data in OFB mode + * + * @param string $cipher + * @param string $key + * @param string $data + * @param int $mode + * @param string $iv optional + * @return string|bool + * @access public + */ + function phpseclib_mcrypt_ofb($cipher, $key, $data, $mode, $iv = null) + { + // https://web.archive.org/web/20180106174656/https://www.php.net/manual/en/function.mcrypt-ofb.php + return $mode == MCRYPT_ENCRYPT ? + phpseclib_mcrypt_encrypt($cipher, $key, $data, MCRYPT_MODE_OFB, $iv) : + phpseclib_mcrypt_decrypt($cipher, $key, $data, MCRYPT_MODE_OFB, $iv); + } + + /** + * Encrypts/decrypts data in CBC mode + * + * @param string $cipher + * @param string $key + * @param string $data + * @param int $mode + * @param string $iv optional + * @return string|bool + * @access public + */ + function phpseclib_mcrypt_cbc($cipher, $key, $data, $mode, $iv = null) + { + // https://web.archive.org/web/20180106174656/https://www.php.net/manual/en/function.mcrypt-cbc.php + return $mode == MCRYPT_ENCRYPT ? + phpseclib_mcrypt_encrypt($cipher, $key, $data, MCRYPT_MODE_CBC, $iv) : + phpseclib_mcrypt_decrypt($cipher, $key, $data, MCRYPT_MODE_CBC, $iv); + } + + /** + * Encrypts/decrypts data in ECB mode + * + * @param string $cipher + * @param string $key + * @param string $data + * @param int $mode + * @param string $iv optional + * @return string|bool + * @access public + */ + function phpseclib_mcrypt_ecb($cipher, $key, $data, $mode, $iv = null) + { + // idk why mcrypt_ecb had an $iv parameter when ECB mode doesn't use an IV + // but whatever + + // https://web.archive.org/web/20180106174656/https://www.php.net/manual/en/function.mcrypt-ecb.php + return $mode == MCRYPT_ENCRYPT ? + phpseclib_mcrypt_encrypt($cipher, $key, $data, MCRYPT_MODE_ECB, $iv) : + phpseclib_mcrypt_decrypt($cipher, $key, $data, MCRYPT_MODE_ECB, $iv); + } + /** * Encrypts plaintext with given parameters * @@ -1042,7 +1178,9 @@ function phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, $op) */ function phpseclib_mcrypt_encrypt($cipher, $key, $data, $mode, $iv = null) { - return phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, 'encrypt'); + return defined('PHPSECLIB_MCRYPT_TARGET_VERSION') && version_compare(PHPSECLIB_MCRYPT_TARGET_VERSION, '5.6.0', '>=') ? + phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, 'encrypt') : + phpseclib_mcrypt_helper_old($cipher, $key, $data, $mode, $iv, 'encrypt'); } /** @@ -1060,7 +1198,9 @@ function phpseclib_mcrypt_encrypt($cipher, $key, $data, $mode, $iv = null) */ function phpseclib_mcrypt_decrypt($cipher, $key, $data, $mode, $iv = null) { - return phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, 'decrypt'); + return defined('PHPSECLIB_MCRYPT_TARGET_VERSION') && version_compare(PHPSECLIB_MCRYPT_TARGET_VERSION, '5.6.0', '>=') ? + phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, 'decrypt') : + phpseclib_mcrypt_helper_old($cipher, $key, $data, $mode, $iv, 'decrypt'); } /** @@ -1266,6 +1406,33 @@ public function onClose() // define if (!function_exists('mcrypt_list_algorithms')) { + if (defined('PHPSECLIB_MCRYPT_TARGET_VERSION') && version_compare(PHPSECLIB_MCRYPT_TARGET_VERSION, '7.0.0', '<')) { + function mcrypt_generic_end($td) + { + return phpseclib_mcrypt_generic_end($td); + } + + function mcrypt_ecb($cipher, $key, $data, $mode, $iv = null) + { + return phpseclib_mcrypt_ecb($cipher, $key, $data, $mode, $iv); + } + + function mcrypt_cbc($cipher, $key, $data, $mode, $iv = null) + { + return phpseclib_mcrypt_cbc($cipher, $key, $data, $mode, $iv); + } + + function mcrypt_cfb($cipher, $key, $data, $mode, $iv = null) + { + return phpseclib_mcrypt_cfb($cipher, $key, $data, $mode, $iv); + } + + function mcrypt_ofb($cipher, $key, $data, $mode, $iv = null) + { + return phpseclib_mcrypt_ofb($cipher, $key, $data, $mode, $iv); + } + } + function mcrypt_list_algorithms($lib_dir = '') { return phpseclib_mcrypt_list_algorithms($lib_dir); From 8c338db21b4da026d89291df5d88e31a02e598d3 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 21 Feb 2024 04:33:11 -0600 Subject: [PATCH 2/2] use GitHub Actions instead of Travis CI --- .github/workflows/ci.yml | 31 ++++++++++++++++ README.md | 2 +- lib/mcrypt.php | 4 +-- tests/MCryptCompatTest.php | 47 +++++++++++++++++++++++++ tests/make_compatible_with_phpunit9.php | 26 ++++++++++++++ 5 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/make_compatible_with_phpunit9.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d981c87 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ + +name: CI +on: [push, pull_request] + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + tests: + name: Tests + timeout-minutes: 10 + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + - name: Composer Install + run: composer install --no-interaction --no-cache + - name: Make Tests Compatiable With PHPUnit 9+ + if: contains(fromJSON('["7.3", "7.4", "8.0", "8.1", "8.2", "8.3"]'), matrix.php-version) + run: php tests/make_compatible_with_phpunit9.php + - name: PHPUnit + run: vendor/bin/phpunit + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] \ No newline at end of file diff --git a/README.md b/README.md index 5289bda..a7d44af 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # mcrypt_compat -[![Build Status](https://travis-ci.org/phpseclib/mcrypt_compat.svg?branch=master)](https://app.travis-ci.com/github/phpseclib/mcrypt_compat) +[![CI Status](https://github.com/phpseclib/mcrypt_compat/actions/workflows/ci.yml/badge.svg?branch=1.0&event=push "CI Status")](https://github.com/phpseclib/mcrypt_compat/actions/workflows/ci.yml?query=branch%3A1.0) PHP 5.x-8.x polyfill for mcrypt extension. diff --git a/lib/mcrypt.php b/lib/mcrypt.php index e58796a..d014343 100644 --- a/lib/mcrypt.php +++ b/lib/mcrypt.php @@ -1178,7 +1178,7 @@ function phpseclib_mcrypt_ecb($cipher, $key, $data, $mode, $iv = null) */ function phpseclib_mcrypt_encrypt($cipher, $key, $data, $mode, $iv = null) { - return defined('PHPSECLIB_MCRYPT_TARGET_VERSION') && version_compare(PHPSECLIB_MCRYPT_TARGET_VERSION, '5.6.0', '>=') ? + return !defined('PHPSECLIB_MCRYPT_TARGET_VERSION') || version_compare(PHPSECLIB_MCRYPT_TARGET_VERSION, '5.6.0', '>=') ? phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, 'encrypt') : phpseclib_mcrypt_helper_old($cipher, $key, $data, $mode, $iv, 'encrypt'); } @@ -1198,7 +1198,7 @@ function phpseclib_mcrypt_encrypt($cipher, $key, $data, $mode, $iv = null) */ function phpseclib_mcrypt_decrypt($cipher, $key, $data, $mode, $iv = null) { - return defined('PHPSECLIB_MCRYPT_TARGET_VERSION') && version_compare(PHPSECLIB_MCRYPT_TARGET_VERSION, '5.6.0', '>=') ? + return !defined('PHPSECLIB_MCRYPT_TARGET_VERSION') || version_compare(PHPSECLIB_MCRYPT_TARGET_VERSION, '5.6.0', '>=') ? phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, 'decrypt') : phpseclib_mcrypt_helper_old($cipher, $key, $data, $mode, $iv, 'decrypt'); } diff --git a/tests/MCryptCompatTest.php b/tests/MCryptCompatTest.php index d2a2b3d..1cea509 100644 --- a/tests/MCryptCompatTest.php +++ b/tests/MCryptCompatTest.php @@ -825,6 +825,53 @@ public function testMcryptGenericWithTwoParamsPHPPost71() phpseclib_mcrypt_generic_init($td, 'xxx'); } + public function testOldMcryptNoIVWarning() + { + $key = 'key'; + $data = 'data'; + $iv = null; + + $this->setExpectedException('PHPUnit_Framework_Error_Warning', 'mcrypt_encrypt(): Attempt to use an empty IV, which is NOT recommended'); + + phpseclib_mcrypt_helper_old('rijndael-128', $key, $data, 'cbc', $iv, 'encrypt'); + } + + public function testOldMcryptShortIVWarning() + { + $key = 'key'; + $data = 'data'; + $iv = 'iv'; + + $this->setExpectedException('PHPUnit_Framework_Error_Warning', 'mcrypt_encrypt(): The IV parameter must be as long as the blocksize'); + + phpseclib_mcrypt_helper_old('rijndael-128', $key, $data, 'cbc', $iv, 'encrypt'); + } + + public function testOldMcryptShortIV() + { + $key = 'key'; + $data = 'data'; + $iv = 'iv'; + + $result = @phpseclib_mcrypt_helper_old('rijndael-128', $key, $data, 'cbc', $iv, 'encrypt'); + + $this->assertEquals('69c48f0bce2c81abd64bbab839080574', bin2hex($result)); + } + + public function testOldMcryptNoIV() + { + $key = str_pad('key', 16, "\0"); + $data = 'data'; + $iv = null; + + $result = @phpseclib_mcrypt_helper_old('rijndael-128', $key, $data, 'cbc', $iv, 'encrypt'); + + // this yields the same result that testOldMcryptShortIV() yields. when the IV is invalid it's assumed to be all null bytes, + // whilst a key that's not the right length is either null padded or truncated as appropriate + + $this->assertEquals('69c48f0bce2c81abd64bbab839080574', bin2hex($result)); + } + public function providerForIVSizeChecks() { $tests = [ diff --git a/tests/make_compatible_with_phpunit9.php b/tests/make_compatible_with_phpunit9.php new file mode 100644 index 0000000..1d2fe26 --- /dev/null +++ b/tests/make_compatible_with_phpunit9.php @@ -0,0 +1,26 @@ + $files */ +$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__)); +foreach ($files as $file) { + if ($file->getExtension() === 'php' && $file->getPathname() !== __FILE__) { + $fileContents = file_get_contents($file->getPathname()); + if ($fileContents === false) { + throw new \RuntimeException('file_get_contents() failed: ' . $file->getPathname()); + } + $patternToReplacementMap = array( + '~(n assertIsArray\([^,\)]*,)([^,\)]*\))~' => '$1 string $2: void', + '~(n assertIsArray\([^,\)]*\))~' => '$1: void', + '~(n assertIsString\([^\)]*\))~' => '$1: void', + '~(n assertStringContainsString\([^\)]*\))~' => '$1: void' + ); + $updatedFileContents = preg_replace( + array_keys($patternToReplacementMap), + array_values($patternToReplacementMap), + $fileContents + ); + if (file_put_contents($file->getPathname(), $updatedFileContents) === false) { + throw new \RuntimeException('file_put_contents() failed: ' . $file->getPathname()); + } + } +} \ No newline at end of file