123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- <?php
- /**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- * @link https://cakephp.org CakePHP(tm) Project
- * @since 0.10.0
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Utility;
- use Cake\Utility\Crypto\Mcrypt;
- use Cake\Utility\Crypto\OpenSsl;
- use InvalidArgumentException;
- use RuntimeException;
- /**
- * Security Library contains utility methods related to security
- */
- class Security
- {
- /**
- * Default hash method. If `$type` param for `Security::hash()` is not specified
- * this value is used. Defaults to 'sha1'.
- *
- * @var string
- */
- public static $hashType = 'sha1';
- /**
- * The HMAC salt to use for encryption and decryption routines
- *
- * @var string
- */
- protected static $_salt;
- /**
- * The crypto implementation to use.
- *
- * @var object
- */
- protected static $_instance;
- /**
- * Create a hash from string using given method.
- *
- * @param string $string String to hash
- * @param string|null $algorithm Hashing algo to use (i.e. sha1, sha256 etc.).
- * Can be any valid algo included in list returned by hash_algos().
- * If no value is passed the type specified by `Security::$hashType` is used.
- * @param mixed $salt If true, automatically prepends the application's salt
- * value to $string (Security.salt).
- * @return string Hash
- * @link https://book.cakephp.org/3.0/en/core-libraries/security.html#hashing-data
- */
- public static function hash($string, $algorithm = null, $salt = false)
- {
- if (empty($algorithm)) {
- $algorithm = static::$hashType;
- }
- $algorithm = strtolower($algorithm);
- $availableAlgorithms = hash_algos();
- if (!in_array($algorithm, $availableAlgorithms)) {
- throw new RuntimeException(sprintf(
- 'The hash type `%s` was not found. Available algorithms are: %s',
- $algorithm,
- implode(', ', $availableAlgorithms)
- ));
- }
- if ($salt) {
- if (!is_string($salt)) {
- $salt = static::$_salt;
- }
- $string = $salt . $string;
- }
- return hash($algorithm, $string);
- }
- /**
- * Sets the default hash method for the Security object. This affects all objects
- * using Security::hash().
- *
- * @param string $hash Method to use (sha1/sha256/md5 etc.)
- * @return void
- * @see \Cake\Utility\Security::hash()
- */
- public static function setHash($hash)
- {
- static::$hashType = $hash;
- }
- /**
- * Get random bytes from a secure source.
- *
- * This method will fall back to an insecure source an trigger a warning
- * if it cannot find a secure source of random data.
- *
- * @param int $length The number of bytes you want.
- * @return string Random bytes in binary.
- */
- public static function randomBytes($length)
- {
- if (function_exists('random_bytes')) {
- return random_bytes($length);
- }
- if (!function_exists('openssl_random_pseudo_bytes')) {
- throw new RuntimeException(
- 'You do not have a safe source of random data available. ' .
- 'Install either the openssl extension, or paragonie/random_compat. ' .
- 'Or use Security::insecureRandomBytes() alternatively.'
- );
- }
- $bytes = openssl_random_pseudo_bytes($length, $strongSource);
- if (!$strongSource) {
- trigger_error(
- 'openssl was unable to use a strong source of entropy. ' .
- 'Consider updating your system libraries, or ensuring ' .
- 'you have more available entropy.',
- E_USER_WARNING
- );
- }
- return $bytes;
- }
- /**
- * Creates a secure random string.
- *
- * @param int $length String length. Default 64.
- * @return string
- * @since 3.6.0
- */
- public static function randomString($length = 64)
- {
- return substr(
- bin2hex(Security::randomBytes(ceil($length / 2))),
- 0,
- $length
- );
- }
- /**
- * Like randomBytes() above, but not cryptographically secure.
- *
- * @param int $length The number of bytes you want.
- * @return string Random bytes in binary.
- * @see \Cake\Utility\Security::randomBytes()
- */
- public static function insecureRandomBytes($length)
- {
- $length *= 2;
- $bytes = '';
- $byteLength = 0;
- while ($byteLength < $length) {
- $bytes .= static::hash(Text::uuid() . uniqid(mt_rand(), true), 'sha512', true);
- $byteLength = strlen($bytes);
- }
- $bytes = substr($bytes, 0, $length);
- return pack('H*', $bytes);
- }
- /**
- * Get the crypto implementation based on the loaded extensions.
- *
- * You can use this method to forcibly decide between mcrypt/openssl/custom implementations.
- *
- * @param \Cake\Utility\Crypto\OpenSsl|\Cake\Utility\Crypto\Mcrypt|null $instance The crypto instance to use.
- * @return \Cake\Utility\Crypto\OpenSsl|\Cake\Utility\Crypto\Mcrypt Crypto instance.
- * @throws \InvalidArgumentException When no compatible crypto extension is available.
- */
- public static function engine($instance = null)
- {
- if ($instance === null && static::$_instance === null) {
- if (extension_loaded('openssl')) {
- $instance = new OpenSsl();
- } elseif (extension_loaded('mcrypt')) {
- $instance = new Mcrypt();
- }
- }
- if ($instance) {
- static::$_instance = $instance;
- }
- if (isset(static::$_instance)) {
- return static::$_instance;
- }
- throw new InvalidArgumentException(
- 'No compatible crypto engine available. ' .
- 'Load either the openssl or mcrypt extensions'
- );
- }
- /**
- * Encrypts/Decrypts a text using the given key using rijndael method.
- *
- * @param string $text Encrypted string to decrypt, normal string to encrypt
- * @param string $key Key to use as the encryption key for encrypted data.
- * @param string $operation Operation to perform, encrypt or decrypt
- * @throws \InvalidArgumentException When there are errors.
- * @return string Encrypted/Decrypted string.
- * @deprecated 3.6.3 This method relies on functions provided by mcrypt
- * extension which has been deprecated in PHP 7.1 and removed in PHP 7.2.
- * There's no 1:1 replacement for this method.
- * Upgrade your code to use Security::encrypt()/Security::decrypt() with
- * OpenSsl engine instead.
- */
- public static function rijndael($text, $key, $operation)
- {
- if (empty($key)) {
- throw new InvalidArgumentException('You cannot use an empty key for Security::rijndael()');
- }
- if (empty($operation) || !in_array($operation, ['encrypt', 'decrypt'])) {
- throw new InvalidArgumentException('You must specify the operation for Security::rijndael(), either encrypt or decrypt');
- }
- if (mb_strlen($key, '8bit') < 32) {
- throw new InvalidArgumentException('You must use a key larger than 32 bytes for Security::rijndael()');
- }
- $crypto = static::engine();
- return $crypto->rijndael($text, $key, $operation);
- }
- /**
- * Encrypt a value using AES-256.
- *
- * *Caveat* You cannot properly encrypt/decrypt data with trailing null bytes.
- * Any trailing null bytes will be removed on decryption due to how PHP pads messages
- * with nulls prior to encryption.
- *
- * @param string $plain The value to encrypt.
- * @param string $key The 256 bit/32 byte key to use as a cipher key.
- * @param string|null $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
- * @return string Encrypted data.
- * @throws \InvalidArgumentException On invalid data or key.
- */
- public static function encrypt($plain, $key, $hmacSalt = null)
- {
- self::_checkKey($key, 'encrypt()');
- if ($hmacSalt === null) {
- $hmacSalt = static::$_salt;
- }
- // Generate the encryption and hmac key.
- $key = mb_substr(hash('sha256', $key . $hmacSalt), 0, 32, '8bit');
- $crypto = static::engine();
- $ciphertext = $crypto->encrypt($plain, $key);
- $hmac = hash_hmac('sha256', $ciphertext, $key);
- return $hmac . $ciphertext;
- }
- /**
- * Check the encryption key for proper length.
- *
- * @param string $key Key to check.
- * @param string $method The method the key is being checked for.
- * @return void
- * @throws \InvalidArgumentException When key length is not 256 bit/32 bytes
- */
- protected static function _checkKey($key, $method)
- {
- if (mb_strlen($key, '8bit') < 32) {
- throw new InvalidArgumentException(
- sprintf('Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method)
- );
- }
- }
- /**
- * Decrypt a value using AES-256.
- *
- * @param string $cipher The ciphertext to decrypt.
- * @param string $key The 256 bit/32 byte key to use as a cipher key.
- * @param string|null $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
- * @return string|bool Decrypted data. Any trailing null bytes will be removed.
- * @throws \InvalidArgumentException On invalid data or key.
- */
- public static function decrypt($cipher, $key, $hmacSalt = null)
- {
- self::_checkKey($key, 'decrypt()');
- if (empty($cipher)) {
- throw new InvalidArgumentException('The data to decrypt cannot be empty.');
- }
- if ($hmacSalt === null) {
- $hmacSalt = static::$_salt;
- }
- // Generate the encryption and hmac key.
- $key = mb_substr(hash('sha256', $key . $hmacSalt), 0, 32, '8bit');
- // Split out hmac for comparison
- $macSize = 64;
- $hmac = mb_substr($cipher, 0, $macSize, '8bit');
- $cipher = mb_substr($cipher, $macSize, null, '8bit');
- $compareHmac = hash_hmac('sha256', $cipher, $key);
- if (!static::constantEquals($hmac, $compareHmac)) {
- return false;
- }
- $crypto = static::engine();
- return $crypto->decrypt($cipher, $key);
- }
- /**
- * A timing attack resistant comparison that prefers native PHP implementations.
- *
- * @param string $original The original value.
- * @param string $compare The comparison value.
- * @return bool
- * @see https://github.com/resonantcore/php-future/
- * @since 3.6.2
- */
- public static function constantEquals($original, $compare)
- {
- if (!is_string($original) || !is_string($compare)) {
- return false;
- }
- if (function_exists('hash_equals')) {
- return hash_equals($original, $compare);
- }
- $originalLength = mb_strlen($original, '8bit');
- $compareLength = mb_strlen($compare, '8bit');
- if ($originalLength !== $compareLength) {
- return false;
- }
- $result = 0;
- for ($i = 0; $i < $originalLength; $i++) {
- $result |= (ord($original[$i]) ^ ord($compare[$i]));
- }
- return $result === 0;
- }
- /**
- * Gets the HMAC salt to be used for encryption/decryption
- * routines.
- *
- * @return string The currently configured salt
- */
- public static function getSalt()
- {
- return static::$_salt;
- }
- /**
- * Sets the HMAC salt to be used for encryption/decryption
- * routines.
- *
- * @param string $salt The salt to use for encryption routines.
- * @return void
- */
- public static function setSalt($salt)
- {
- static::$_salt = (string)$salt;
- }
- /**
- * Gets or sets the HMAC salt to be used for encryption/decryption
- * routines.
- *
- * @deprecated 3.5.0 Use getSalt()/setSalt() instead.
- * @param string|null $salt The salt to use for encryption routines. If null returns current salt.
- * @return string The currently configured salt
- */
- public static function salt($salt = null)
- {
- deprecationWarning(
- 'Security::salt() is deprecated. ' .
- 'Use Security::getSalt()/setSalt() instead.'
- );
- if ($salt === null) {
- return static::$_salt;
- }
- return static::$_salt = (string)$salt;
- }
- }
|