mirror of
https://github.com/codeigniter4/CodeIgniter4.git
synced 2025-02-20 11:44:28 +08:00
Add SodiumHandler encrypter
This commit is contained in:
parent
6b80979f35
commit
0f11b66b01
@ -31,12 +31,24 @@ class Encryption extends BaseConfig
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* One of the supported drivers, e.g. 'OpenSSL' or 'Sodium'.
|
||||
* The default driver, if you don't specify one, is 'OpenSSL'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $driver = 'OpenSSL';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* SodiumHandler's Padding Size
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* This is the number of bytes that will be padded to the plaintext message
|
||||
* before it is encrypted. Maximum allowed value is 512 bytes. If none is
|
||||
* given, it will default to 512.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $blockSize = 512;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Encryption digest
|
||||
|
1
env
1
env
@ -91,6 +91,7 @@
|
||||
|
||||
# encryption.key =
|
||||
# encryption.driver = OpenSSL
|
||||
# encryption.blockSize = 512
|
||||
# encryption.digest = SHA512
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
|
@ -64,7 +64,7 @@ abstract class BaseHandler implements EncrypterInterface
|
||||
$config = $config ?? config('Encryption');
|
||||
|
||||
// make the parameters conveniently accessible
|
||||
foreach ($config as $key => $value)
|
||||
foreach (get_object_vars($config) as $key => $value)
|
||||
{
|
||||
if (property_exists($this, $key))
|
||||
{
|
||||
@ -111,6 +111,6 @@ abstract class BaseHandler implements EncrypterInterface
|
||||
*/
|
||||
public function __isset($key): bool
|
||||
{
|
||||
return in_array($key, ['cipher', 'key'], true);
|
||||
return property_exists($this, $key);
|
||||
}
|
||||
}
|
||||
|
168
system/Encryption/Handlers/SodiumHandler.php
Normal file
168
system/Encryption/Handlers/SodiumHandler.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2017 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019-2020 CodeIgniter Foundation
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2014-2017 British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
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 integer
|
||||
*/
|
||||
protected $blockSize = 512;
|
||||
|
||||
/**
|
||||
* {@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
|
||||
|
||||
// pad to a maximum of 512 bytes chunks
|
||||
$paddedLength = $this->blockSize <= 512 ? $this->blockSize : 512;
|
||||
$paddedMessage = sodium_pad($data, $paddedLength);
|
||||
|
||||
// encrypt message and combine with nonce
|
||||
$ciphertext = $nonce . sodium_crypto_secretbox($paddedMessage, $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, null);
|
||||
|
||||
// decrypt and account for extra padding
|
||||
$paddedLength = $this->blockSize <= 512 ? $this->blockSize : 512;
|
||||
$paddedPlaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $this->key);
|
||||
|
||||
if ($paddedPlaintext === false)
|
||||
{
|
||||
// message was tampered in transit
|
||||
throw EncryptionException::forAuthenticationFailed(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$plaintext = sodium_unpad($paddedPlaintext, $paddedLength);
|
||||
|
||||
// cleanup buffers
|
||||
sodium_memzero($ciphertext);
|
||||
sodium_memzero($this->key);
|
||||
|
||||
return $plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the $params before doing assignment.
|
||||
*
|
||||
* @param array|string|null $params
|
||||
*
|
||||
* @throws \CodeIgniter\Encryption\Exceptions\EncryptionException If key is empty
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseParams($params)
|
||||
{
|
||||
if ($params === null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($params))
|
||||
{
|
||||
if (isset($params['key']))
|
||||
{
|
||||
$this->key = $params['key'];
|
||||
}
|
||||
|
||||
if (isset($params['block_size']))
|
||||
{
|
||||
$this->blockSize = $params['block_size'];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->key = (string) $params;
|
||||
}
|
||||
}
|
95
tests/system/Encryption/Handlers/SodiumHandlerTest.php
Normal file
95
tests/system/Encryption/Handlers/SodiumHandlerTest.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace CodeIgniter\Encryption\Handlers;
|
||||
|
||||
use CodeIgniter\Encryption\Encryption;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\Encryption as EncryptionConfig;
|
||||
|
||||
class SodiumHandlerTest extends CIUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var \CodeIgniter\Encryption\Encryption
|
||||
*/
|
||||
protected $encryption;
|
||||
|
||||
/**
|
||||
* @var \Config\Encryption
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
if (! extension_loaded('sodium'))
|
||||
{
|
||||
$this->markTestSkipped('Libsodium is not available.');
|
||||
}
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$this->config = new EncryptionConfig();
|
||||
$this->config->driver = 'Sodium';
|
||||
$this->config->key = sodium_crypto_secretbox_keygen();
|
||||
$this->encryption = new Encryption($this->config);
|
||||
}
|
||||
|
||||
public function testPropertiesGetter()
|
||||
{
|
||||
$this->config->key = sodium_crypto_secretbox_keygen();
|
||||
$this->config->blockSize = 256;
|
||||
$encrypter = $this->encryption->initialize($this->config);
|
||||
|
||||
$this->assertSame($this->config->key, $encrypter->key);
|
||||
$this->assertSame($this->config->blockSize, $encrypter->blockSize);
|
||||
$this->assertNull($encrypter->driver);
|
||||
}
|
||||
|
||||
public function testEmptyKeyThrowsErrorOnInitialize()
|
||||
{
|
||||
$this->expectException('CodeIgniter\Encryption\Exceptions\EncryptionException');
|
||||
|
||||
$this->config->key = '';
|
||||
$this->encryption->initialize($this->config);
|
||||
}
|
||||
|
||||
public function testEmptyKeyThrowsErrorOnEncrypt()
|
||||
{
|
||||
$this->expectException('CodeIgniter\Encryption\Exceptions\EncryptionException');
|
||||
|
||||
$encrypter = $this->encryption->initialize($this->config);
|
||||
$encrypter->encrypt('Some message to encrypt', '');
|
||||
}
|
||||
|
||||
public function testEmptyKeyThrowsErrorOnDecrypt()
|
||||
{
|
||||
$this->expectException('CodeIgniter\Encryption\Exceptions\EncryptionException');
|
||||
|
||||
$encrypter = $this->encryption->initialize($this->config);
|
||||
$ciphertext = $encrypter->encrypt('Some message to encrypt');
|
||||
// After encrypt, the message and key are wiped from buffer
|
||||
$encrypter->decrypt($ciphertext);
|
||||
}
|
||||
|
||||
public function testTruncatedMessageThrowsErrorOnDecrypt()
|
||||
{
|
||||
$this->expectException('CodeIgniter\Encryption\Exceptions\EncryptionException');
|
||||
|
||||
$encrypter = $this->encryption->initialize($this->config);
|
||||
$ciphertext = $encrypter->encrypt('Some message to encrypt');
|
||||
$truncated = mb_substr($ciphertext, 0, 24, '8bit');
|
||||
$encrypter->decrypt($truncated, ['block_size' => 256, 'key' => sodium_crypto_secretbox_keygen()]);
|
||||
}
|
||||
|
||||
public function testDecryptingMessages()
|
||||
{
|
||||
$key = sodium_crypto_secretbox_keygen();
|
||||
$msg = 'A plaintext message for you.';
|
||||
|
||||
$this->config->key = $key;
|
||||
$encrypter = $this->encryption->initialize($this->config);
|
||||
$ciphertext = $encrypter->encrypt($msg);
|
||||
|
||||
$this->assertSame($msg, $encrypter->decrypt($ciphertext, $key));
|
||||
$this->assertNotSame('A plain-text message for you.', $encrypter->decrypt($ciphertext, $key));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user