Initial
This commit is contained in:
46
system/Encryption/EncrypterInterface.php
Normal file
46
system/Encryption/EncrypterInterface.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Encryption;
|
||||
|
||||
use CodeIgniter\Encryption\Exceptions\EncryptionException;
|
||||
|
||||
/**
|
||||
* CodeIgniter Encryption Handler
|
||||
*
|
||||
* Provides two-way keyed encryption
|
||||
*/
|
||||
interface EncrypterInterface
|
||||
{
|
||||
/**
|
||||
* Encrypt - convert plaintext into ciphertext
|
||||
*
|
||||
* @param string $data Input data
|
||||
* @param array|string|null $params Overridden parameters, specifically the key
|
||||
*
|
||||
* @throws EncryptionException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($data, $params = null);
|
||||
|
||||
/**
|
||||
* Decrypt - convert ciphertext into plaintext
|
||||
*
|
||||
* @param string $data Encrypted data
|
||||
* @param array|string|null $params Overridden parameters, specifically the key
|
||||
*
|
||||
* @throws EncryptionException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function decrypt($data, $params = null);
|
||||
}
|
||||
172
system/Encryption/Encryption.php
Normal file
172
system/Encryption/Encryption.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Encryption;
|
||||
|
||||
use CodeIgniter\Encryption\Exceptions\EncryptionException;
|
||||
use Config\Encryption as EncryptionConfig;
|
||||
|
||||
/**
|
||||
* CodeIgniter Encryption Manager
|
||||
*
|
||||
* Provides two-way keyed encryption via PHP's Sodium and/or OpenSSL extensions.
|
||||
* This class determines the driver, cipher, and mode to use, and then
|
||||
* initializes the appropriate encryption handler.
|
||||
*/
|
||||
class Encryption
|
||||
{
|
||||
/**
|
||||
* The encrypter we create
|
||||
*
|
||||
* @var EncrypterInterface
|
||||
*/
|
||||
protected $encrypter;
|
||||
|
||||
/**
|
||||
* The driver being used
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* The key/seed being used
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* The derived HMAC key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $hmacKey;
|
||||
|
||||
/**
|
||||
* HMAC digest to use
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $digest = 'SHA512';
|
||||
|
||||
/**
|
||||
* Map of drivers to handler classes, in preference order
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $drivers = [
|
||||
'OpenSSL',
|
||||
'Sodium',
|
||||
];
|
||||
|
||||
/**
|
||||
* Handlers that are to be installed
|
||||
*
|
||||
* @var array<string, boolean>
|
||||
*/
|
||||
protected $handlers = [];
|
||||
|
||||
/**
|
||||
* @throws EncryptionException
|
||||
*/
|
||||
public function __construct(?EncryptionConfig $config = null)
|
||||
{
|
||||
$config = $config ?? new EncryptionConfig();
|
||||
|
||||
$this->key = $config->key;
|
||||
$this->driver = $config->driver;
|
||||
$this->digest = $config->digest ?? 'SHA512';
|
||||
|
||||
$this->handlers = [
|
||||
'OpenSSL' => extension_loaded('openssl'),
|
||||
// the SodiumHandler uses some API (like sodium_pad) that is available only on v1.0.14+
|
||||
'Sodium' => extension_loaded('sodium') && version_compare(SODIUM_LIBRARY_VERSION, '1.0.14', '>='),
|
||||
];
|
||||
|
||||
if (! in_array($this->driver, $this->drivers, true) || (array_key_exists($this->driver, $this->handlers) && ! $this->handlers[$this->driver])) {
|
||||
throw EncryptionException::forNoHandlerAvailable($this->driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize or re-initialize an encrypter
|
||||
*
|
||||
* @throws EncryptionException
|
||||
*
|
||||
* @return EncrypterInterface
|
||||
*/
|
||||
public function initialize(?EncryptionConfig $config = null)
|
||||
{
|
||||
if ($config) {
|
||||
$this->key = $config->key;
|
||||
$this->driver = $config->driver;
|
||||
$this->digest = $config->digest ?? 'SHA512';
|
||||
}
|
||||
|
||||
if (empty($this->driver)) {
|
||||
throw EncryptionException::forNoDriverRequested();
|
||||
}
|
||||
|
||||
if (! in_array($this->driver, $this->drivers, true)) {
|
||||
throw EncryptionException::forUnKnownHandler($this->driver);
|
||||
}
|
||||
|
||||
if (empty($this->key)) {
|
||||
throw EncryptionException::forNeedsStarterKey();
|
||||
}
|
||||
|
||||
$this->hmacKey = bin2hex(\hash_hkdf($this->digest, $this->key));
|
||||
|
||||
$handlerName = 'CodeIgniter\\Encryption\\Handlers\\' . $this->driver . 'Handler';
|
||||
$this->encrypter = new $handlerName($config);
|
||||
|
||||
return $this->encrypter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a random key
|
||||
*
|
||||
* @param int $length Output length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function createKey($length = 32)
|
||||
{
|
||||
return random_bytes($length);
|
||||
}
|
||||
|
||||
/**
|
||||
* __get() magic, providing readonly access to some of our protected properties
|
||||
*
|
||||
* @param string $key Property name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
if ($this->__isset($key)) {
|
||||
return $this->{$key};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* __isset() magic, providing checking for some of our protected properties
|
||||
*
|
||||
* @param string $key Property name
|
||||
*/
|
||||
public function __isset($key): bool
|
||||
{
|
||||
return in_array($key, ['key', 'digest', 'driver', 'drivers'], true);
|
||||
}
|
||||
}
|
||||
86
system/Encryption/Exceptions/EncryptionException.php
Normal file
86
system/Encryption/Exceptions/EncryptionException.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Encryption\Exceptions;
|
||||
|
||||
use CodeIgniter\Exceptions\DebugTraceableTrait;
|
||||
use CodeIgniter\Exceptions\ExceptionInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Encryption exception
|
||||
*/
|
||||
class EncryptionException extends RuntimeException implements ExceptionInterface
|
||||
{
|
||||
use DebugTraceableTrait;
|
||||
|
||||
/**
|
||||
* Thrown when no driver is present in the active encryption session.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function forNoDriverRequested()
|
||||
{
|
||||
return new static(lang('Encryption.noDriverRequested'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when the handler requested is not available.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function forNoHandlerAvailable(string $handler)
|
||||
{
|
||||
return new static(lang('Encryption.noHandlerAvailable', [$handler]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when the handler requested is unknown.
|
||||
*
|
||||
* @param string $driver
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function forUnKnownHandler(?string $driver = null)
|
||||
{
|
||||
return new static(lang('Encryption.unKnownHandler', [$driver]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when no starter key is provided for the current encryption session.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function forNeedsStarterKey()
|
||||
{
|
||||
return new static(lang('Encryption.starterKeyNeeded'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown during data decryption when a problem or error occurred.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function forAuthenticationFailed()
|
||||
{
|
||||
return new static(lang('Encryption.authenticationFailed'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown during data encryption when a problem or error occurred.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function forEncryptionFailed()
|
||||
{
|
||||
return new static(lang('Encryption.encryptionFailed'));
|
||||
}
|
||||
}
|
||||
84
system/Encryption/Handlers/BaseHandler.php
Normal file
84
system/Encryption/Handlers/BaseHandler.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Encryption\Handlers;
|
||||
|
||||
use CodeIgniter\Encryption\EncrypterInterface;
|
||||
use Config\Encryption;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Base class for encryption handling
|
||||
*/
|
||||
abstract class BaseHandler implements EncrypterInterface
|
||||
{
|
||||
/**
|
||||
* Logger instance to record error messages and warnings.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(?Encryption $config = null)
|
||||
{
|
||||
$config = $config ?? config('Encryption');
|
||||
|
||||
// make the parameters conveniently accessible
|
||||
foreach (get_object_vars($config) as $key => $value) {
|
||||
if (property_exists($this, $key)) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Byte-safe substr()
|
||||
*
|
||||
* @param string $str
|
||||
* @param int $start
|
||||
* @param int $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function substr($str, $start, $length = null)
|
||||
{
|
||||
return mb_substr($str, $start, $length, '8bit');
|
||||
}
|
||||
|
||||
/**
|
||||
* __get() magic, providing readonly access to some of our properties
|
||||
*
|
||||
* @param string $key Property name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
if ($this->__isset($key)) {
|
||||
return $this->{$key};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* __isset() magic, providing checking for some of our properties
|
||||
*
|
||||
* @param string $key Property name
|
||||
*/
|
||||
public function __isset($key): bool
|
||||
{
|
||||
return property_exists($this, $key);
|
||||
}
|
||||
}
|
||||
110
system/Encryption/Handlers/OpenSSLHandler.php
Normal file
110
system/Encryption/Handlers/OpenSSLHandler.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Encryption\Handlers;
|
||||
|
||||
use CodeIgniter\Encryption\Exceptions\EncryptionException;
|
||||
|
||||
/**
|
||||
* Encryption handling for OpenSSL library
|
||||
*/
|
||||
class OpenSSLHandler extends BaseHandler
|
||||
{
|
||||
/**
|
||||
* HMAC digest to use
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $digest = 'SHA512';
|
||||
|
||||
/**
|
||||
* Cipher to use
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cipher = 'AES-256-CTR';
|
||||
|
||||
/**
|
||||
* Starter key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $key = '';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function encrypt($data, $params = null)
|
||||
{
|
||||
// Allow key override
|
||||
if ($params) {
|
||||
$this->key = is_array($params) && isset($params['key']) ? $params['key'] : $params;
|
||||
}
|
||||
|
||||
if (empty($this->key)) {
|
||||
throw EncryptionException::forNeedsStarterKey();
|
||||
}
|
||||
|
||||
// derive a secret key
|
||||
$secret = \hash_hkdf($this->digest, $this->key);
|
||||
|
||||
// basic encryption
|
||||
$iv = ($ivSize = \openssl_cipher_iv_length($this->cipher)) ? \openssl_random_pseudo_bytes($ivSize) : null;
|
||||
|
||||
$data = \openssl_encrypt($data, $this->cipher, $secret, OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
if ($data === false) {
|
||||
throw EncryptionException::forEncryptionFailed();
|
||||
}
|
||||
|
||||
$result = $iv . $data;
|
||||
|
||||
$hmacKey = \hash_hmac($this->digest, $result, $secret, true);
|
||||
|
||||
return $hmacKey . $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function decrypt($data, $params = null)
|
||||
{
|
||||
// Allow key override
|
||||
if ($params) {
|
||||
$this->key = is_array($params) && isset($params['key']) ? $params['key'] : $params;
|
||||
}
|
||||
|
||||
if (empty($this->key)) {
|
||||
throw EncryptionException::forNeedsStarterKey();
|
||||
}
|
||||
|
||||
// derive a secret key
|
||||
$secret = \hash_hkdf($this->digest, $this->key);
|
||||
|
||||
$hmacLength = self::substr($this->digest, 3) / 8;
|
||||
$hmacKey = self::substr($data, 0, $hmacLength);
|
||||
$data = self::substr($data, $hmacLength);
|
||||
$hmacCalc = \hash_hmac($this->digest, $data, $secret, true);
|
||||
|
||||
if (! hash_equals($hmacKey, $hmacCalc)) {
|
||||
throw EncryptionException::forAuthenticationFailed();
|
||||
}
|
||||
|
||||
if ($ivSize = \openssl_cipher_iv_length($this->cipher)) {
|
||||
$iv = self::substr($data, 0, $ivSize);
|
||||
$data = self::substr($data, $ivSize);
|
||||
} else {
|
||||
$iv = null;
|
||||
}
|
||||
|
||||
return \openssl_decrypt($data, $this->cipher, $secret, OPENSSL_RAW_DATA, $iv);
|
||||
}
|
||||
}
|
||||
137
system/Encryption/Handlers/SodiumHandler.php
Normal file
137
system/Encryption/Handlers/SodiumHandler.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Encryption\Handlers;
|
||||
|
||||
use CodeIgniter\Encryption\Exceptions\EncryptionException;
|
||||
|
||||
/**
|
||||
* SodiumHandler uses libsodium in encryption.
|
||||
*
|
||||
* @see https://github.com/jedisct1/libsodium/issues/392
|
||||
*/
|
||||
class SodiumHandler extends BaseHandler
|
||||
{
|
||||
/**
|
||||
* Starter key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $key = '';
|
||||
|
||||
/**
|
||||
* Block size for padding message.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $blockSize = 16;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function encrypt($data, $params = null)
|
||||
{
|
||||
$this->parseParams($params);
|
||||
|
||||
if (empty($this->key)) {
|
||||
throw EncryptionException::forNeedsStarterKey();
|
||||
}
|
||||
|
||||
// create a nonce for this operation
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes
|
||||
|
||||
// add padding before we encrypt the data
|
||||
if ($this->blockSize <= 0) {
|
||||
throw EncryptionException::forEncryptionFailed();
|
||||
}
|
||||
|
||||
$data = sodium_pad($data, $this->blockSize);
|
||||
|
||||
// encrypt message and combine with nonce
|
||||
$ciphertext = $nonce . sodium_crypto_secretbox($data, $nonce, $this->key);
|
||||
|
||||
// cleanup buffers
|
||||
sodium_memzero($data);
|
||||
sodium_memzero($this->key);
|
||||
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function decrypt($data, $params = null)
|
||||
{
|
||||
$this->parseParams($params);
|
||||
|
||||
if (empty($this->key)) {
|
||||
throw EncryptionException::forNeedsStarterKey();
|
||||
}
|
||||
|
||||
if (mb_strlen($data, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
|
||||
// message was truncated
|
||||
throw EncryptionException::forAuthenticationFailed();
|
||||
}
|
||||
|
||||
// Extract info from encrypted data
|
||||
$nonce = self::substr($data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
$ciphertext = self::substr($data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
|
||||
// decrypt data
|
||||
$data = sodium_crypto_secretbox_open($ciphertext, $nonce, $this->key);
|
||||
|
||||
if ($data === false) {
|
||||
// message was tampered in transit
|
||||
throw EncryptionException::forAuthenticationFailed(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// remove extra padding during encryption
|
||||
if ($this->blockSize <= 0) {
|
||||
throw EncryptionException::forAuthenticationFailed();
|
||||
}
|
||||
|
||||
$data = sodium_unpad($data, $this->blockSize);
|
||||
|
||||
// cleanup buffers
|
||||
sodium_memzero($ciphertext);
|
||||
sodium_memzero($this->key);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the $params before doing assignment.
|
||||
*
|
||||
* @param array|string|null $params
|
||||
*
|
||||
* @throws EncryptionException If key is empty
|
||||
*/
|
||||
protected function parseParams($params)
|
||||
{
|
||||
if ($params === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($params)) {
|
||||
if (isset($params['key'])) {
|
||||
$this->key = $params['key'];
|
||||
}
|
||||
|
||||
if (isset($params['blockSize'])) {
|
||||
$this->blockSize = $params['blockSize'];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->key = (string) $params;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user