diff options
author | Andrey Andreev <narf@devilix.net> | 2014-02-18 15:29:53 +0100 |
---|---|---|
committer | Andrey Andreev <narf@devilix.net> | 2014-02-18 15:29:53 +0100 |
commit | 9a152a91c982d5f2ba07d0197ef2fe5eb8c8510c (patch) | |
tree | 089223cd683e17c84e4a80a4ee3592df3f5a8e9d | |
parent | 9c7ce322232eb1e55b9caa138dffe67d9acc660f (diff) |
Add an ext/hash compatibility layer (just hash_pbkdf2(), for now)
-rw-r--r-- | system/core/CodeIgniter.php | 1 | ||||
-rw-r--r-- | system/core/compat/hash.php | 144 | ||||
-rw-r--r-- | tests/Bootstrap.php | 1 | ||||
-rw-r--r-- | tests/codeigniter/core/compat/hash_test.php | 51 | ||||
-rw-r--r-- | user_guide_src/source/changelog.rst | 2 | ||||
-rw-r--r-- | user_guide_src/source/general/compatibility_functions.rst | 29 |
6 files changed, 227 insertions, 1 deletions
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php index 1f10c452d..2bdd76463 100644 --- a/system/core/CodeIgniter.php +++ b/system/core/CodeIgniter.php @@ -189,6 +189,7 @@ defined('BASEPATH') OR exit('No direct script access allowed'); */ require_once(BASEPATH.'core/compat/mbstring.php'); + require_once(BASEPATH.'core/compat/hash.php'); require_once(BASEPATH.'core/compat/password.php'); /* diff --git a/system/core/compat/hash.php b/system/core/compat/hash.php new file mode 100644 index 000000000..a9f59f110 --- /dev/null +++ b/system/core/compat/hash.php @@ -0,0 +1,144 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP 5.2.4 or newer + * + * NOTICE OF LICENSE + * + * Licensed under the Open Software License version 3.0 + * + * This source file is subject to the Open Software License (OSL 3.0) that is + * bundled with this package in the files license.txt / license.rst. It is + * also available through the world wide web at this URL: + * http://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to obtain it + * through the world wide web, please send an email to + * licensing@ellislab.com so we can send you a copy immediately. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/) + * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * @link http://codeigniter.com + * @since Version 3.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * PHP ext/hash compatibility package + * + * @package CodeIgniter + * @subpackage CodeIgniter + * @category Compatibility + * @author Andrey Andreev + * @link http://codeigniter.com/user_guide/ + * @link http://php.net/hash + */ + +// ------------------------------------------------------------------------ + +if (is_php('5.5')) +{ + return; +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('hash_pbkdf2')) +{ + /** + * hash_pbkdf2() + * + * @link http://php.net/hash_pbkdf2 + * @param string $algo + * @param string $password + * @param string $salt + * @param int $iterations + * @param int $length + * @param bool $raw_output + * @return string + */ + function hash_pbkdf2($algo, $password, $salt, $iterations, $length = 0, $raw_output = FALSE) + { + if ( ! in_array($algo, hash_algos(), TRUE)) + { + trigger_error('hash_pbkdf2(): Unknown hashing algorithm: '.$algo, E_USER_WARNING); + return FALSE; + } + + if (($type = gettype($iterations)) !== 'integer') + { + if ($type === 'object' && method_exists($iterations, '__toString')) + { + $iterations = (string) $iterations; + } + + if (is_string($iterations) && is_numeric($iterations)) + { + $iterations = (int) $iterations; + } + else + { + trigger_error('hash_pbkdf2() expects parameter 4 to be long, '.$type.' given', E_USER_WARNING); + return NULL; + } + } + + if ($iterations < 1) + { + trigger_error('hash_pbkdf2(): Iterations must be a positive integer: '.$iterations, E_USER_WARNING); + return FALSE; + } + + if (($type = gettype($length)) !== 'integer') + { + if ($type === 'object' && method_exists($length, '__toString')) + { + $length = (string) $length; + } + + if (is_string($length) && is_numeric($length)) + { + $length = (int) $length; + } + else + { + trigger_error('hash_pbkdf2() expects parameter 5 to be long, '.$type.' given', E_USER_WARNING); + return NULL; + } + } + + if ($length < 0) + { + trigger_error('hash_pbkdf2(): Length must be greater than or equal to 0: '.$length, E_USER_WARNING); + return FALSE; + } + + $hash_length = strlen(hash($algo, NULL, TRUE)); + if (empty($length)) + { + $length = $hash_length; + } + + $hash = ''; + // Note: Blocks are NOT 0-indexed + for ($bc = ceil($length / $hash_length), $bi = 1; $bi <= $bc; $bi++) + { + $key = $derived_key = hash_hmac($algo, $salt.pack('N', $bi), $password, TRUE); + for ($i = 1; $i < $iterations; $i++) + { + $derived_key ^= $key = hash_hmac($algo, $key, $password, TRUE); + } + + $hash .= $derived_key; + } + + // This is not RFC-compatible, but we're aiming for natural PHP compatibility + return substr($raw_output ? $hash : bin2hex($hash), 0, $length); + } +} + +/* End of file hash.php */ +/* Location: ./system/core/compat/hash.php */
\ No newline at end of file diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php index 439c7fdab..5441f718a 100644 --- a/tests/Bootstrap.php +++ b/tests/Bootstrap.php @@ -64,6 +64,7 @@ else } include_once SYSTEM_PATH.'core/compat/mbstring.php'; +include_once SYSTEM_PATH.'core/compat/hash.php'; include_once SYSTEM_PATH.'core/compat/password.php'; include_once $dir.'/mocks/autoloader.php'; diff --git a/tests/codeigniter/core/compat/hash_test.php b/tests/codeigniter/core/compat/hash_test.php new file mode 100644 index 000000000..25bbd4eb1 --- /dev/null +++ b/tests/codeigniter/core/compat/hash_test.php @@ -0,0 +1,51 @@ +<?php + +class hash_test extends CI_TestCase { + + public function test_bootstrap() + { + if (is_php('5.5')) + { + return $this->markTestSkipped('ext/standard/password is available on PHP 5.5'); + } + + $this->assertTrue(function_exists('hash_pbkdf2')); + } + + // ------------------------------------------------------------------------ + + /** + * hash_pbkdf2() test + * + * Borrowed from PHP's own tests + * + * @depends test_bootstrap + */ + public function test_hash_pbkdf2() + { + $this->assertEquals('0c60c80f961f0e71f3a9', hash_pbkdf2('sha1', 'password', 'salt', 1, 20)); + $this->assertEquals( + "\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6", + hash_pbkdf2('sha1', 'password', 'salt', 1, 20, TRUE) + ); + $this->assertEquals('3d2eec4fe41c849b80c8d8366', hash_pbkdf2('sha1', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25)); + $this->assertEquals( + "\x3d\x2e\xec\x4f\xe4\x1c\x84\x9b\x80\xc8\xd8\x36\x62\xc0\xe4\x4a\x8b\x29\x1a\x96\x4c\xf2\xf0\x70\x38", + hash_pbkdf2('sha1', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25, TRUE) + ); + $this->assertEquals('120fb6cffcf8b32c43e7', hash_pbkdf2('sha256', 'password', 'salt', 1, 20)); + $this->assertEquals( + "\x12\x0f\xb6\xcf\xfc\xf8\xb3\x2c\x43\xe7\x22\x52\x56\xc4\xf8\x37\xa8\x65\x48\xc9", + hash_pbkdf2('sha256', 'password', 'salt', 1, 20, TRUE) + ); + $this->assertEquals( + '348c89dbcbd32b2f32d814b8116e84cf2b17347e', + hash_pbkdf2('sha256', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 40) + ); + $this->assertEquals( + "\x34\x8c\x89\xdb\xcb\xd3\x2b\x2f\x32\xd8\x14\xb8\x11\x6e\x84\xcf\x2b\x17\x34\x7e\xbc\x18\x00\x18\x1c\x4e\x2a\x1f\xb8\xdd\x53\xe1\xc6\x35\x51\x8c\x7d\xac\x47\xe9", + hash_pbkdf2('sha256', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 40, TRUE) + ); + } + +}
\ No newline at end of file diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst index 9907e9722..5d5c5df26 100644 --- a/user_guide_src/source/changelog.rst +++ b/user_guide_src/source/changelog.rst @@ -508,7 +508,7 @@ Release Date: Not Released - Changed method ``clean_string()`` to utilize ``mb_convert_encoding()`` if it is available but ``iconv()`` is not. - Renamed method ``_is_ascii()`` to ``is_ascii()`` and made it public. - - Added `compatibility layers <general/compatibility_functions>` for PHP's `mbstring <http://php.net/mbstring>`_ (limited support) and `password <http://php.net/password>`_ extensions. + - Added `compatibility layers <general/compatibility_functions>` for PHP's `mbstring <http://php.net/mbstring>`_ (limited support), `hash <http://php.net/hash>`_ and `password <http://php.net/password>`_ extensions. - Removed ``CI_CORE`` boolean constant from *CodeIgniter.php* (no longer Reactor and Core versions). - Log Library will now try to create the **log_path** directory if it doesn't exist. - Added support for HTTP-Only cookies with new config option *cookie_httponly* (default FALSE). diff --git a/user_guide_src/source/general/compatibility_functions.rst b/user_guide_src/source/general/compatibility_functions.rst index e025d2aa3..3495101ac 100644 --- a/user_guide_src/source/general/compatibility_functions.rst +++ b/user_guide_src/source/general/compatibility_functions.rst @@ -93,6 +93,35 @@ Function reference For more information, please refer to the `PHP manual for password_verify() <http://php.net/password_verify>`_. +********************* +Hash (Message Digest) +********************* + +This compatibility layer contains only a single function at +this time - ``hash_pbkdf2()``, which otherwise requires PHP 5.5. + +Dependancies +============ + +- None + +Function reference +================== + +.. function:: hash_pbkdf2($algo, $password, $salt, $iterations[, $length = 0[, $raw_output = FALSE]]) + + :param string $algo: Hashing algorithm + :param string $password: Password + :param string $salt: Hash salt + :param int $iterations: Number of iterations to perform during derivation + :param int $length: Output string length + :param bool $raw_output: Whether to return raw binary data + :returns: Password-derived key or FALSE on failure + :rtype: string + + For more information, please refer to the `PHP manual for + hash_pbkdf2() <http://php.net/hash_pbkdf2>`_. + **************** Multibyte String **************** |