Skip to content

Instantly share code, notes, and snippets.

@leonjza
Last active March 15, 2024 15:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leonjza/9add68e267f2348a3968a2ac4b86c5ec to your computer and use it in GitHub Desktop.
Save leonjza/9add68e267f2348a3968a2ac4b86c5ec to your computer and use it in GitHub Desktop.
Laravel ~5.6 encryptor / decryptor.
<?php
/*
* Quick 'n Dirty Laravel ~5.6 decrypter.
* @leonjza
*
* Typically, if you see a key with the format base64:key_string then
* you know you need this decrypter.
*
* For Laravel ~5.1, use: https://gist.github.com/leonjza/ce27aa7435f8d131d93f
*
* Based directly off the source code at:
* https://github.com/laravel/framework/blob/v5.6.3/src/Illuminate/Encryption/Encrypter.php
* https://github.com/laravel/framework/blob/v5.6.3/src/Illuminate/Encryption/EncryptionServiceProvider.php
*
* Have access to an application key from a .env?
* Have some encrypted data you want to decrypt?
* Well:
* (new Crypt($key))->decryptString($payload); should have you sorted
* Or, if its using AES-256-CBC:
* (new Crypt($key, 'AES-256-CBC'))->decryptString($payload); should have you sorted
*
* Have access to an application key from a .env?
* Want to replace an encrypted value?
* Well:
* (new Crypt($key))->encryptString($payload); should have you sorted
* Or, if the app is using AES-256-CBC:
* (new Crypt($key, 'AES-256-CBC'))->encryptString($payload); should have you sorted
*
*/
# replace these values with yours.
$payload = 'payload_string';
$key = 'base64:base64_encoded_key_value';
# if the key starts with base64:, strip it.
if (substr( $string_n, 0, 7 ) === 'base64:')
$key = base64_decode(substr($key, 7));
#$decrypted_data = (new Crypt($key))->decryptString($payload);
$decrypted_data = (new Crypt($key, 'AES-256-CBC'))->decryptString($payload);
$encrypted_data = (new Crypt($key))->encryptString($payload);
#$encrypted_data = (new Crypt($key, 'AES-256-CBC'))->encryptString($payload);
/**
* Class Crypt
*/
class Crypt
{
/**
* The encryption key.
*
* @var string
*/
protected $key;
/**
* The algorithm used for encryption.
*
* @var string
*/
protected $cipher;
/**
* Create a new encrypter instance.
*
* @param string $key
* @param string $cipher
* @return void
*
* @throws \RuntimeException
*/
public function __construct($key, $cipher = 'AES-128-CBC')
{
$key = (string) $key;
if (static::supported($key, $cipher)) {
$this->key = $key;
$this->cipher = $cipher;
} else {
throw new RuntimeException('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.');
}
}
/**
* Determine if the given key and cipher combination is valid.
*
* @param string $key
* @param string $cipher
* @return bool
*/
public static function supported($key, $cipher)
{
$length = mb_strlen($key, '8bit');
return ($cipher === 'AES-128-CBC' && $length === 16) ||
($cipher === 'AES-256-CBC' && $length === 32);
}
/**
* Create a new encryption key for the given cipher.
*
* @param string $cipher
* @return string
*/
public static function generateKey($cipher)
{
return random_bytes($cipher == 'AES-128-CBC' ? 16 : 32);
}
/**
* Encrypt the given value.
*
* @param mixed $value
* @param bool $serialize
* @return string
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function encrypt($value, $serialize = true)
{
$iv = random_bytes(openssl_cipher_iv_length($this->cipher));
// First we will encrypt the value using OpenSSL. After this is encrypted we
// will proceed to calculating a MAC for the encrypted value so that this
// value can be verified later as not having been changed by the users.
$value = \openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
// Once we get the encrypted value we'll go ahead and base64_encode the input
// vector and create the MAC for the encrypted value so we can then verify
// its authenticity. Then, we'll JSON the data into the "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (json_last_error() !== JSON_ERROR_NONE) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
/**
* Encrypt a string without serialization.
*
* @param string $value
* @return string
*/
public function encryptString($value)
{
return $this->encrypt($value, false);
}
/**
* Decrypt the given value.
*
* @param mixed $payload
* @param bool $unserialize
* @return string
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function decrypt($payload, $unserialize = true)
{
$payload = $this->getJsonPayload($payload);
$iv = base64_decode($payload['iv']);
// Here we will decrypt the value. If we are able to successfully decrypt it
// we will then unserialize it and return it out to the caller. If we are
// unable to decrypt this value we will throw out an exception message.
$decrypted = \openssl_decrypt(
$payload['value'], $this->cipher, $this->key, 0, $iv
);
if ($decrypted === false) {
throw new Exception('Could not decrypt the data.');
}
return $unserialize ? unserialize($decrypted) : $decrypted;
}
/**
* Decrypt the given string without unserialization.
*
* @param string $payload
* @return string
*/
public function decryptString($payload)
{
return $this->decrypt($payload, false);
}
/**
* Create a MAC for the given value.
*
* @param string $iv
* @param mixed $value
* @return string
*/
protected function hash($iv, $value)
{
return hash_hmac('sha256', $iv.$value, $this->key);
}
/**
* Get the JSON array from the given payload.
*
* @param string $payload
* @return array
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
protected function getJsonPayload($payload)
{
$payload = json_decode(base64_decode($payload), true);
// If the payload is not valid JSON or does not have the proper keys set we will
// assume it is invalid and bail out of the routine since we will not be able
// to decrypt the given value. We'll also check the MAC for this encryption.
if (! $this->validPayload($payload)) {
throw new Exception('The payload is invalid.');
}
if (! $this->validMac($payload)) {
throw new Exception('The MAC is invalid.');
}
return $payload;
}
/**
* Verify that the encryption payload is valid.
*
* @param mixed $payload
* @return bool
*/
protected function validPayload($payload)
{
return is_array($payload) && isset(
$payload['iv'], $payload['value'], $payload['mac']
);
}
/**
* Determine if the MAC for the given payload is valid.
*
* @param array $payload
* @return bool
*/
protected function validMac(array $payload)
{
$calculated = $this->calculateMac($payload, $bytes = random_bytes(16));
return hash_equals(
hash_hmac('sha256', $payload['mac'], $bytes, true), $calculated
);
}
/**
* Calculate the hash of the given payload.
*
* @param array $payload
* @param string $bytes
* @return string
*/
protected function calculateMac($payload, $bytes)
{
return hash_hmac(
'sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true
);
}
/**
* Get the encryption key.
*
* @return string
*/
public function getKey()
{
return $this->key;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment