Last active
March 15, 2024 15:01
-
-
Save leonjza/9add68e267f2348a3968a2ac4b86c5ec to your computer and use it in GitHub Desktop.
Laravel ~5.6 encryptor / decryptor.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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