Utilize multiple encryption ciphers for remember me
This commit is contained in:
parent
1e1b4b74ad
commit
8c22514758
|
@ -45,15 +45,13 @@ class AuthenticationController extends Controller
|
|||
|
||||
if (RememberMe::hasCookie() && $this->hasDb()) {
|
||||
$authenticated = false;
|
||||
$iv = null;
|
||||
try {
|
||||
$rememberMeOld = RememberMe::fromCookie();
|
||||
$iv = $rememberMeOld->getAesCrypt()->getIv();
|
||||
$authenticated = $rememberMeOld->authenticate();
|
||||
if ($authenticated) {
|
||||
$rememberMe = $rememberMeOld->renew();
|
||||
$this->getResponse()->setCookie($rememberMe->getCookie());
|
||||
$rememberMe->persist($iv);
|
||||
$rememberMe->persist($rememberMeOld->getAesCrypt()->getIv());
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
Logger::error("Can't authenticate user via remember me cookie: %s", $e->getMessage());
|
||||
|
@ -62,7 +60,6 @@ class AuthenticationController extends Controller
|
|||
}
|
||||
|
||||
if (! $authenticated) {
|
||||
(new RememberMe())->remove(bin2hex($iv));
|
||||
$this->getResponse()->setCookie(RememberMe::forget());
|
||||
}
|
||||
}
|
||||
|
@ -110,12 +107,6 @@ class AuthenticationController extends Controller
|
|||
$this->getResponse()->setHttpResponseCode(401);
|
||||
} else {
|
||||
if (RememberMe::hasCookie() && $this->hasDb()) {
|
||||
try {
|
||||
(new RememberMe())->remove(bin2hex(RememberMe::fromCookie()->getAesCrypt()->getIV()));
|
||||
} catch (RuntimeException $e) {
|
||||
// pass
|
||||
}
|
||||
|
||||
$this->getResponse()->setCookie(RememberMe::forget());
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use RuntimeException;
|
|||
* ```php
|
||||
*
|
||||
* // Encryption
|
||||
* $encryptedData = new AesCrypt()->encrypt($data); // Accepts a string
|
||||
* $encryptedData = (new AesCrypt())->encrypt($data); // Accepts a string
|
||||
*
|
||||
*
|
||||
* // Encrypt and encode to Base64
|
||||
|
@ -23,7 +23,7 @@ use RuntimeException;
|
|||
*
|
||||
* // Decryption
|
||||
* $aesCrypt = (new AesCrypt())
|
||||
* ->setTag($tag)
|
||||
* ->setTag($tag) // if exists
|
||||
* ->setIV($iv)
|
||||
* ->setKey($key);
|
||||
*
|
||||
|
@ -35,15 +35,25 @@ use RuntimeException;
|
|||
* ->setIV($iv)
|
||||
* ->setKey($key);
|
||||
*
|
||||
* $decryptedData = $aesCrypt->->decryptFromBase64($data);
|
||||
* $decryptedData = $aesCrypt->decryptFromBase64($data);
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
class AesCrypt
|
||||
{
|
||||
/** @var array The list of cipher methods */
|
||||
const METHODS = [
|
||||
'aes-256-gcm',
|
||||
'aes-256-cbc',
|
||||
'aes-256-ctr'
|
||||
];
|
||||
|
||||
/** @var string The encryption key */
|
||||
private $key;
|
||||
|
||||
/** @var int The length of the key */
|
||||
private $keyLength;
|
||||
|
||||
/** @var string The initialization vector which is not NULL */
|
||||
private $iv;
|
||||
|
||||
|
@ -51,21 +61,15 @@ class AesCrypt
|
|||
private $tag;
|
||||
|
||||
/** @var string The cipher method */
|
||||
private $method = 'AES-128-GCM';
|
||||
private $method;
|
||||
|
||||
const GCM_SUPPORT_VERSION = '7.1';
|
||||
|
||||
public function __construct($random_bytes_len = 128)
|
||||
public function __construct($keyLength = 128)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, self::GCM_SUPPORT_VERSION, '<')) {
|
||||
$this->method = 'AES-128-CBC';
|
||||
}
|
||||
|
||||
$this->key = random_bytes($random_bytes_len);
|
||||
$this->keyLength = $keyLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force set the method
|
||||
* Set the method
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
|
@ -76,6 +80,45 @@ class AesCrypt
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the method
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
if ($this->method === null) {
|
||||
$this->method = $this->getSupportedMethod();
|
||||
}
|
||||
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported method
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws RuntimeException If none of the methods listed in the METHODS array is available
|
||||
*/
|
||||
protected function getSupportedMethod()
|
||||
{
|
||||
$availableMethods = openssl_get_cipher_methods();
|
||||
$methods = self::METHODS;
|
||||
|
||||
if (! $this->isAuthenticatedEncryptionSupported()) {
|
||||
unset($methods[0]);
|
||||
}
|
||||
|
||||
foreach ($methods as $method) {
|
||||
if (in_array($method, $availableMethods)) {
|
||||
return $method;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException('No supported method found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the key
|
||||
*
|
||||
|
@ -93,12 +136,11 @@ class AesCrypt
|
|||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws RuntimeException If the key is not set
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
if (empty($this->key)) {
|
||||
throw new RuntimeException('No key set');
|
||||
$this->key = random_bytes($this->keyLength);
|
||||
}
|
||||
|
||||
return $this->key;
|
||||
|
@ -121,12 +163,11 @@ class AesCrypt
|
|||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws RuntimeException If the IV is not set
|
||||
*/
|
||||
public function getIV()
|
||||
{
|
||||
if (empty($this->iv)) {
|
||||
$len = openssl_cipher_iv_length($this->method);
|
||||
$len = openssl_cipher_iv_length($this->getMethod());
|
||||
$this->iv = random_bytes($len);
|
||||
}
|
||||
|
||||
|
@ -137,9 +178,20 @@ class AesCrypt
|
|||
* Set the Tag
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws RuntimeException If a tag is available but authenticated encryption (AE) is not supported.
|
||||
*
|
||||
* @throws UnexpectedValueException If tag length is less then 16
|
||||
*/
|
||||
public function setTag($tag)
|
||||
{
|
||||
if (! $this->isAuthenticatedEncryptionSupported()) {
|
||||
throw new RuntimeException(sprintf(
|
||||
"The given decryption method is not supported in php version '%s'",
|
||||
PHP_VERSION
|
||||
));
|
||||
}
|
||||
|
||||
if (strlen($tag) !== 16) {
|
||||
throw new UnexpectedValueException(sprintf(
|
||||
'expects tag length to be 16, got instead %s',
|
||||
|
@ -169,7 +221,7 @@ class AesCrypt
|
|||
}
|
||||
|
||||
/**
|
||||
* Decrypt the given data using the key, iv and tag
|
||||
* Decrypt the given string
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
|
@ -179,11 +231,11 @@ class AesCrypt
|
|||
*/
|
||||
public function decrypt($data)
|
||||
{
|
||||
if ($this->method === 'AES-128-CBC') {
|
||||
return $this->decryptCBC($data);
|
||||
if (! $this->isAuthenticatedEncryptionRequired()) {
|
||||
return $this->nonAEDecrypt($data);
|
||||
}
|
||||
|
||||
$decrypt = openssl_decrypt($data, $this->method, $this->getKey(), 0, $this->getIV(), $this->getTag());
|
||||
$decrypt = openssl_decrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV(), $this->getTag());
|
||||
|
||||
if ($decrypt === false) {
|
||||
throw new RuntimeException('Decryption failed');
|
||||
|
@ -193,11 +245,13 @@ class AesCrypt
|
|||
}
|
||||
|
||||
/**
|
||||
* Decode from Base64 and decrypt the given data using the key, iv and tag
|
||||
* Decode from Base64 and decrypt the given string
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return string decrypted data
|
||||
* @return string The base64 decoded and decrypted string
|
||||
*
|
||||
* @deprecated Use decrypt() instead as it also returns a base64 decoded string
|
||||
*
|
||||
* @throws RuntimeException If decryption fails
|
||||
*/
|
||||
|
@ -207,21 +261,21 @@ class AesCrypt
|
|||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given data using the key, iv and tag
|
||||
* Encrypt the given string
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return string encrypted data
|
||||
* @return string encrypted string
|
||||
*
|
||||
* @throws RuntimeException If decryption fails
|
||||
*/
|
||||
public function encrypt($data)
|
||||
{
|
||||
if ($this->method === 'AES-128-CBC') {
|
||||
return $this->encryptCBC($data);
|
||||
if (! $this->isAuthenticatedEncryptionRequired()) {
|
||||
return $this->nonAEEncrypt($data);
|
||||
}
|
||||
|
||||
$encrypt = openssl_encrypt($data, $this->method, $this->getkey(), 0, $this->getIV(), $this->tag);
|
||||
$encrypt = openssl_encrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV(), $this->tag);
|
||||
|
||||
if ($encrypt === false) {
|
||||
throw new RuntimeException('Encryption failed');
|
||||
|
@ -231,11 +285,13 @@ class AesCrypt
|
|||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given string using the the key, iv, tag and encode to Base64
|
||||
* Encrypt the given string and encode to Base64
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return string encrypted and encoded to Base64 data
|
||||
* @return string encrypted and base64 encoded string
|
||||
*
|
||||
* @deprecated Use encrypt() instead as it also returns a base64 encoded string
|
||||
*
|
||||
* @throws RuntimeException If encryption fails
|
||||
*/
|
||||
|
@ -244,18 +300,23 @@ class AesCrypt
|
|||
return base64_encode($this->encrypt($data));
|
||||
}
|
||||
|
||||
private function decryptCBC($data)
|
||||
/**
|
||||
* Decrypt the given string with non Authenticated encryption (AE) cipher method
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return string decrypted string
|
||||
*
|
||||
* @throws RuntimeException If decryption fails
|
||||
*/
|
||||
private function nonAEDecrypt($data)
|
||||
{
|
||||
if (strlen($this->getIV()) !== 16) {
|
||||
throw new RuntimeException('Decryption failed');
|
||||
}
|
||||
|
||||
$c = base64_decode($data);
|
||||
$hmac = substr($c, 0, 32);
|
||||
$data = substr($c, 32);
|
||||
|
||||
$decrypt = openssl_decrypt($data, $this->method, $this->getKey(), 0, $this->getIV());
|
||||
$calcHmac = hash_hmac('sha256', $data, $this->getKey(), true);
|
||||
$decrypt = openssl_decrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV());
|
||||
$calcHmac = hash_hmac('sha256', $this->getIV() . $data, $this->getKey(), true);
|
||||
|
||||
if ($decrypt === false || ! hash_equals($hmac, $calcHmac)) {
|
||||
throw new RuntimeException('Decryption failed');
|
||||
|
@ -264,16 +325,45 @@ class AesCrypt
|
|||
return $decrypt;
|
||||
}
|
||||
|
||||
private function encryptCBC($data)
|
||||
/**
|
||||
* Encrypt the given string with non Authenticated encryption (AE) cipher method
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return string encrypted string
|
||||
*
|
||||
* @throws RuntimeException If encryption fails
|
||||
*/
|
||||
private function nonAEEncrypt($data)
|
||||
{
|
||||
$encrypt = openssl_encrypt($data, $this->method, $this->getkey(), 0, $this->getIV());
|
||||
$encrypt = openssl_encrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV());
|
||||
|
||||
if ($encrypt === false) {
|
||||
throw new RuntimeException('Encryption failed');
|
||||
}
|
||||
|
||||
$hmac = hash_hmac('sha256', $encrypt, $this->getkey(), true);
|
||||
$hmac = hash_hmac('sha256', $this->getIV() . $encrypt, $this->getKey(), true);
|
||||
|
||||
return base64_encode($hmac . $encrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the Authenticated encryption (a tag) is required
|
||||
*
|
||||
* @return bool True if required false otherwise
|
||||
*/
|
||||
public function isAuthenticatedEncryptionRequired()
|
||||
{
|
||||
return $this->getMethod() === 'aes-256-gcm';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the php version supports Authenticated encryption (AE) or not
|
||||
*
|
||||
* @return bool True if supported false otherwise
|
||||
*/
|
||||
public function isAuthenticatedEncryptionSupported()
|
||||
{
|
||||
return PHP_VERSION_ID >= 70100;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,12 +50,18 @@ class RememberMe
|
|||
}
|
||||
|
||||
/**
|
||||
* Unset the remember me cookie from PHP's `$_COOKIE` superglobal and return the invalidation cookie
|
||||
* Remove the database entry if exists and unset the remember me cookie from PHP's `$_COOKIE` superglobal
|
||||
*
|
||||
* @return Cookie Cookie which has to be sent to client in oder to remove the remember me cookie
|
||||
* @return Cookie The invalidation cookie which has to be sent to client in oder to remove the remember me cookie
|
||||
*/
|
||||
public static function forget()
|
||||
{
|
||||
if (self::hasCookie()) {
|
||||
$data = explode('|', $_COOKIE[static::COOKIE]);
|
||||
$iv = base64_decode(array_pop($data));
|
||||
(new self())->remove(bin2hex($iv));
|
||||
}
|
||||
|
||||
unset($_COOKIE[static::COOKIE]);
|
||||
|
||||
return (new Cookie(static::COOKIE))
|
||||
|
@ -92,19 +98,15 @@ class RememberMe
|
|||
->setKey(hex2bin($rs->passphrase))
|
||||
->setIV($iv);
|
||||
|
||||
if (version_compare(PHP_VERSION, AesCrypt::GCM_SUPPORT_VERSION, '>=')) {
|
||||
$tag = array_pop($data);
|
||||
|
||||
if (empty($data)) {
|
||||
$rememberMe->aesCrypt = (new AesCrypt())
|
||||
->setMethod('AES-128-CBC')
|
||||
->setKey(hex2bin($rs->passphrase))
|
||||
->setIV($iv);
|
||||
|
||||
$data[0] = $tag; // encryptedPass
|
||||
} else {
|
||||
$rememberMe->aesCrypt->setTag(base64_decode($tag));
|
||||
}
|
||||
if (count($data) > 1) {
|
||||
$rememberMe->aesCrypt->setTag(
|
||||
base64_decode(array_pop($data))
|
||||
);
|
||||
} elseif ($rememberMe->aesCrypt->isAuthenticatedEncryptionRequired()) {
|
||||
throw new RuntimeException(
|
||||
"The given decryption method needs a tag, but is not specified. "
|
||||
. "You have probably updated the PHP version."
|
||||
);
|
||||
}
|
||||
|
||||
$rememberMe->username = $rs->username;
|
||||
|
@ -125,7 +127,7 @@ class RememberMe
|
|||
{
|
||||
$aesCrypt = new AesCrypt();
|
||||
$rememberMe = new static();
|
||||
$rememberMe->encryptedPassword = $aesCrypt->encryptToBase64($password);
|
||||
$rememberMe->encryptedPassword = $aesCrypt->encrypt($password);
|
||||
$rememberMe->username = $username;
|
||||
$rememberMe->aesCrypt = $aesCrypt;
|
||||
|
||||
|
@ -159,7 +161,7 @@ class RememberMe
|
|||
base64_encode($this->aesCrypt->getIV()),
|
||||
];
|
||||
|
||||
if (version_compare(PHP_VERSION, AesCrypt::GCM_SUPPORT_VERSION, '>=')) {
|
||||
if ($this->aesCrypt->isAuthenticatedEncryptionRequired()) {
|
||||
array_splice($values, 1, 0, base64_encode($this->aesCrypt->getTag()));
|
||||
}
|
||||
|
||||
|
@ -208,7 +210,6 @@ class RememberMe
|
|||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$password = $this->aesCrypt->decryptFromBase64($this->encryptedPassword);
|
||||
$auth = Auth::getInstance();
|
||||
$authChain = $auth->getAuthChain();
|
||||
$authChain->setSkipExternalBackends(true);
|
||||
|
@ -217,7 +218,11 @@ class RememberMe
|
|||
$user->setDomain(Config::app()->get('authentication', 'default_domain'));
|
||||
}
|
||||
|
||||
$authenticated = $authChain->authenticate($user, $password);
|
||||
$authenticated = $authChain->authenticate(
|
||||
$user,
|
||||
$this->aesCrypt->decrypt($this->encryptedPassword)
|
||||
);
|
||||
|
||||
if ($authenticated) {
|
||||
$auth->setAuthenticated($user);
|
||||
}
|
||||
|
@ -228,9 +233,9 @@ class RememberMe
|
|||
/**
|
||||
* Persist the remember me information into the database
|
||||
*
|
||||
* Any previous stored information is automatically removed.
|
||||
* To remove any previous stored information, set the iv
|
||||
*
|
||||
* @param string|null $iv
|
||||
* @param string|null $iv To remove a specific iv record from the database
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
|
@ -254,7 +259,7 @@ class RememberMe
|
|||
}
|
||||
|
||||
/**
|
||||
* Remove remember me information from the database
|
||||
* Remove remember me information from the database on the basis of iv
|
||||
*
|
||||
* @param string $iv
|
||||
*
|
||||
|
@ -278,14 +283,14 @@ class RememberMe
|
|||
{
|
||||
return static::fromCredentials(
|
||||
$this->username,
|
||||
$this->aesCrypt->decryptFromBase64($this->encryptedPassword)
|
||||
$this->aesCrypt->decrypt($this->encryptedPassword)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users using rememberme cookie
|
||||
* Get all users using remember me cookie
|
||||
*
|
||||
* @return array
|
||||
* @return array Array of users
|
||||
*/
|
||||
public static function getAllUser()
|
||||
{
|
||||
|
@ -303,11 +308,11 @@ class RememberMe
|
|||
}
|
||||
|
||||
/**
|
||||
* Get all rememberme cookies of the given user
|
||||
* Get all remember me entries from the database of the given user.
|
||||
*
|
||||
* @param $username
|
||||
*
|
||||
* @return array
|
||||
* @return array Array of database entries
|
||||
*/
|
||||
public static function getAllByUsername($username)
|
||||
{
|
||||
|
@ -325,7 +330,7 @@ class RememberMe
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the encrypton/decryption instance
|
||||
* Get the AesCrypt instance
|
||||
*
|
||||
* @return AesCrypt
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue