Skip to content

Instantly share code, notes, and snippets.

@Radiergummi
Last active June 24, 2024 19:54
Show Gist options
  • Save Radiergummi/b326219f55edb33759791a78a1c134c3 to your computer and use it in GitHub Desktop.
Save Radiergummi/b326219f55edb33759791a78a1c134c3 to your computer and use it in GitHub Desktop.
A PHP class to encrypt and decrypt strings using a static application secret. Input is converted to hex strings to enable easier handling
<?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
Copy link

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 '';

@Radiergummi
Copy link
Author

@markusand I included the condition. Thank you!

@FireEmerald
Copy link

@Radiergummi You should verify the $input before you call hex2bin( $input );.

@silikidi
Copy link

@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;

@Radiergummi
Copy link
Author

@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.

@Radiergummi
Copy link
Author

@sukmabudi Glad it's useful to you 👍

@duzun
Copy link

duzun commented Apr 17, 2020

Why not use $key = hash_pbkdf2(self::HASHING_ALGORITHM, $this->secret, $iv, $this->iterations, 32, true); ?

@Radiergummi
Copy link
Author

@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.

@duzun
Copy link

duzun commented Apr 17, 2020

I see.
Well, ‘hash_pbkdf2’ generates a more secure key than a hash, mainly because it renders rainbow table attacks useless.
Thanks!

@Radiergummi
Copy link
Author

@duzun do you have any sources for that claim? I've been researching a little but could not find anything reliable yet.

@duzun
Copy link

duzun commented Apr 29, 2020

PBKDF2
Rainbow table

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).

source

You can see for yourself how it works by reading the code of PBKDF.

@Flower7C3
Copy link

Hi. Here is my implementation of this code in simple web application https://github.com/Flower7C3/php-cryptor-form

@Radiergummi
Copy link
Author

Awesome, thanks for sharing! 👍

@mdestafadilah
Copy link

How to implment with CI 2/3 ? any idea how to?

@Radiergummi
Copy link
Author

Sorry, I've got no idea what CI 2/3 is?

@mdestafadilah
Copy link

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.

@lycifep
Copy link

lycifep commented Mar 3, 2023

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?

@Radiergummi
Copy link
Author

Support for PHP 7.2 ended years ago - if it does in 7.4, that's your best option 🤷‍♂️

@lycifep
Copy link

lycifep commented Mar 3, 2023

We can't switch to the older version of PHP yet (a lot of legacy code). Can you suggest what could be the issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment