-
-
Save Radiergummi/b326219f55edb33759791a78a1c134c3 to your computer and use it in GitHub Desktop.
<?php | |
namespace Vendor\Library; | |
use function bin2hex; | |
use function hex2bin; | |
use function openssl_decrypt; | |
use function openssl_encrypt; | |
use function random_bytes; | |
/** | |
* Cryptor class | |
* | |
* The encrypt/decrypt methods are largely taken from here: | |
* | |
* @link https://stackoverflow.com/a/46872528/2532203 | |
*/ | |
class Cryptor | |
{ | |
/** | |
* Holds the encryption algorithm to use | |
*/ | |
protected const ENCRYPTION_ALGORITHM = 'AES-256-CBC'; | |
/** | |
* Holds the hash algorithm to use | |
*/ | |
protected const HASHING_ALGORITHM = 'sha256'; | |
/** | |
* Holds the application encryption secret | |
* | |
* @var string | |
*/ | |
protected $secret; | |
/** | |
* Cryptor constructor | |
* | |
* @param string $secret application encryption secret | |
*/ | |
public function __construct(string $secret) | |
{ | |
$this->secret = $secret; | |
} | |
/** | |
* Decrypts a string using the application secret. | |
* | |
* @param string $input hex representation of the cipher text | |
* | |
* @return string UTF-8 string containing the plain text input | |
*/ | |
public function decrypt(string $input): string | |
{ | |
// prevent decrypt failing when $input is not hex or has odd length | |
// added via suggestion from @markusand, see comment: | |
// https://gist.github.com/Radiergummi/b326219f55edb33759791a78a1c134c3#gistcomment-2803128 | |
if (strlen($input) % 2 || ! ctype_xdigit($input)) { | |
return ''; | |
} | |
// we'll need the binary cipher | |
$binaryInput = hex2bin($input); | |
$iv = substr($binaryInput, 0, 16); | |
$hash = substr($binaryInput, 16, 32); | |
$cipherText = substr($binaryInput, 48); | |
$key = hash(self::HASHING_ALGORITHM, $this->secret, true); | |
// if the HMAC hash doesn't match the hash string, something has gone wrong | |
if (hash_hmac(self::HASHING_ALGORITHM, $cipherText, $key, true) !== $hash) { | |
return ''; | |
} | |
return openssl_decrypt( | |
$cipherText, | |
self::ENCRYPTION_ALGORITHM, | |
$key, | |
OPENSSL_RAW_DATA, | |
$iv | |
); | |
} | |
/** | |
* Encrypts a string using the application secret. This returns a hex representation of the binary cipher text | |
* | |
* @param string $input plain text input to encrypt | |
* | |
* @return string hex representation of the binary cipher text | |
* @throws \Exception | |
*/ | |
public function encrypt(string $input): string | |
{ | |
$key = hash(self::HASHING_ALGORITHM, $this->secret, true); | |
$iv = random_bytes(16); | |
$cipherText = openssl_encrypt( | |
$input, | |
self::ENCRYPTION_ALGORITHM, | |
$key, | |
OPENSSL_RAW_DATA, | |
$iv | |
); | |
$hash = hash_hmac(self::HASHING_ALGORITHM, $cipherText, $key, true); | |
return bin2hex($iv . $hash . $cipherText); | |
} | |
} |
@markusand I included the condition. Thank you!
@Radiergummi You should verify the $input
before you call hex2bin( $input );
.
@Radiergummi excellent work! For anyone who needs an example of its use:
require 'Cryptor.php';
$secret = 'MySecret';
$message = 'Hello world!';
$Cryptor = new \Vendor\Library\Cryptor($secret);
$crypted = $Cryptor->encrypt($message);
$decrypted = $Cryptor->decrypt($crypted);
echo 'Message : ' . $message . '<br>';
echo 'Crypted : ' . $crypted . '<br>';
echo 'Decrypted : ' . $decrypted;
@FireEmerald of course, you're right. Must've been in a hurry back then. I also just updated the gist to use random_bytes
instead of the legacy openssl_random_pseudo_bytes
, further improving security here.
@SukmaBudi Glad it's useful to you 👍
Why not use $key = hash_pbkdf2(self::HASHING_ALGORITHM, $this->secret, $iv, $this->iterations, 32, true);
?
@duzun Because this was originally just a class version of a code snippet in a stack overflow post :) hash_pbkdf2
has not always had as widespread support as it has today, and the hash
function is still perfectly valid to use for generating a hash.
I see.
Well, ‘hash_pbkdf2’ generates a more secure key than a hash, mainly because it renders rainbow table attacks useless.
Thanks!
@duzun do you have any sources for that claim? I've been researching a little but could not find anything reliable yet.
Short Introduction to PBKDF2
It aims to reduce the vulnerability of encryption keys to brute force and dictionary attacks. When is it used? A key derivation function is typically used to secure a password storage and to create a cryptographic key from a password or passphrase (i.e. key stretching).
You can see for yourself how it works by reading the code of PBKDF.
Hi. Here is my implementation of this code in simple web application https://github.com/Flower7C3/php-cryptor-form
Awesome, thanks for sharing! 👍
How to implment with CI 2/3 ? any idea how to?
Sorry, I've got no idea what CI 2/3 is?
Sorry, I've got no idea what CI 2/3 is?
sorry, it's Codeigniter i mean. i have no idea implement this, put as thirt_party or libraries it' self.
Hello.
Decrypt not working in PHP 7.2.
Error, return '':
if (hash_hmac(self::HASHING_ALGORITHM, $cipherText, $key, true) !== $hash) {
return '';
}
Works well in php 7.4.
How to be?
Support for PHP 7.2 ended years ago - if it does in 7.4, that's your best option 🤷♂️
We can't switch to the older version of PHP yet (a lot of legacy code). Can you suggest what could be the issue?
I would add an extra line to prevent decrypt failing when $input is not hex or has odd length.
if (strlen($input) % 2 || !ctype_xdigit($input)) return '';