terdia/legato-framework

View on GitHub
src/Security/Encryption.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php
 
/*
* This file is part of the Legato package.
*
* (c) Osayawe Ogbemudia Terry <terry@devscreencast.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
 
namespace Legato\Framework\Security;
 
use Exception;
use RuntimeException;
 
class Encryption
{
protected $key;
 
protected $cipher;
 
/**
* Supported ciphers, and length.
*/
const SUPPORTED_CIPHER_16 = 'AES-128-CBC';
const SUPPORTED_CIPHER_32 = 'AES-256-CBC';
const SUPPORTED_CIPHER_32_LENGTH = 32;
const SUPPORTED_CIPHER_16_LENGTH = 16;
 
/**
* Encryption constructor.
*/
public function __construct()
{
$config = getConfigPath('app', 'encryption');
$key = $config['key'];
$cipher = $config['cipher'];
 
if (!static::valid((string) $key, $cipher)) {
throw new RuntimeException(
'Legato framework only support AES-256-CBC and AES-128-CBC ciphers'
);
}
 
$this->key = $key;
$this->cipher = $cipher;
}
 
/**
* Check if the given key and cipher have valid length and name.
*
* @param $key
* @param $cipher
*
* @return bool
*/
public static function valid($key, $cipher)
{
$keyLength = mb_strlen($key, '8bit');
 
if (static::SUPPORTED_CIPHER_32_LENGTH === $keyLength
&& $cipher === static::SUPPORTED_CIPHER_32) {
return true;
}
 
if (static::SUPPORTED_CIPHER_16_LENGTH == $keyLength
&& $cipher === static::SUPPORTED_CIPHER_16) {
return true;
}
 
return false;
}
 
/**
* Generate encryption key.
*
* @param $cipher
*
* @throws \Exception
*
* @return string
*/
public static function generateEncryptionKey($cipher)
{
if ($cipher === static::SUPPORTED_CIPHER_32) {
return random_bytes(static::SUPPORTED_CIPHER_32_LENGTH);
}
 
if ($cipher === static::SUPPORTED_CIPHER_16) {
return random_bytes(static::SUPPORTED_CIPHER_16_LENGTH);
}
}
 
/**
* Encrypt the value.
*
* @param $value
*
* @throws Exception
*
* @return string
*/
public function encrypt($value)
{
/**
* Gets the cipher iv length.
*/
$iv = random_bytes(openssl_cipher_iv_length($this->cipher));
 
/**
* Encrypts the given value.
*/
$value = \openssl_encrypt($value, $this->cipher, $this->key, 0, $iv);
 
$hash_mac = $this->mac($iv = base64_encode($iv), $value);
 
if (!$value) {
throw new Exception('Unable to encrypt given value');
}
 
$encrypted = json_encode(compact('iv', 'value', 'hash_mac'));
 
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('Unable to encrypt given value.');
}
 
return base64_encode($encrypted);
}
 
/**
* Decrypt the given data and return plain text.
*
* @param $data
*
* @throws Exception
*
* @return string
*/
public function decrypt($data)
{
$data = json_decode(base64_decode($data), true);
 
$iv = base64_decode($data['iv']);
 
if (!$this->isEncryptedDataValid($data)) {
throw new Exception('The given encrypted data is invalid.');
}
if (!$this->isMacValid($data, 16)) {
throw new Exception('The hash is invalid.');
}
/**
* try to decrypt.
*/
$decrypted = \openssl_decrypt(
$data['value'], $this->cipher, $this->key, 0, $iv
);
 
/*
* throw exception is we cannot decrypt
*/
if ($decrypted === false) {
throw new Exception('Data could not be decrypted.');
}
 
return $decrypted;
}
 
/**
* Check if the encrypted data is still valid.
*
* @param $data
*
* @return bool
*/
protected function isEncryptedDataValid($data)
{
return is_array($data) && isset(
$data['iv'], $data['value'], $data['hash_mac']
);
}
 
/**
* Determine if hash is valid.
*
* @param $data
* @param $bytes
*
* @return bool
*/
protected function isMacValid($data, $bytes)
{
$calculated = hash_hmac(
'sha384', $this->mac($data['iv'], $data['value']), $bytes, true
);
 
return hash_equals(
hash_hmac('sha384', $data['hash_mac'], $bytes, true), $calculated
);
}
 
/**
* hash the given value.
*
* @param $iv
* @param $value
*
* @return string
*/
protected function mac($iv, $value)
{
return hash_hmac('sha384', $iv.$value, $this->key);
}
}