namespace Ice;
/**
* The Crypt library provides two-way encryption of text.
*
* @package Ice/Crypt
* @category Library
* @author Ice Team
* @copyright (c) 2014-2025 Ice Team
* @license http://iceframework.org/license
* @uses openSSL
*/
class Crypt
{
protected key { set };
protected cipher = "aes-256" { set };
protected mode = "cbc" { set };
protected block = 16 { set };
/**
* Create a new encrypter instance.
*
* @param string key
* @return void
*/
public function __construct(string key = null)
{
let this->key = key;
}
/**
* Encrypt the given value.
*
* @param string text
* @return string
*/
public function encrypt(string text) -> string
{
var iv, value, mac;
let iv = this->generateInputVector(),
value = this->addPadding(serialize(text)),
value = base64_encode(this->doEncrypt(value, iv));
// Once we have the encrypted value we will go ahead base64_encode the input
// vector and create the MAC for the encrypted value so we can verify its
// authenticity. Then, we'll JSON encode the data in a "payload" array.
let iv = base64_encode(iv),
mac = this->hash(value);
return base64_encode(json_encode([
"iv": iv,
"value": value,
"mac": mac
]));
}
/**
* Generate an input vector.
*
* @return string
*/
protected function generateInputVector() ->string
{
return openssl_random_pseudo_bytes(this->getIvSize());
}
/**
* Actually encrypt the value using the given Iv with the openssl library encrypt function.
*
* @param string value
* @param string iv
* @return string
*/
protected function doEncrypt(string value, string iv) -> string
{
return openssl_encrypt(value, this->cipher . "-" . this->mode, this->key, OPENSSL_RAW_DATA, iv);
}
/**
* Decrypt the given value.
*
* @param string text payload
* @return string
*/
public function decrypt(string text) -> string
{
var value, payload, iv;
let payload = this->getJsonPayload(text);
// We'll go ahead and remove the PKCS7 padding from the encrypted value before
// we decrypt it. Once we have the de-padded value, we will grab the vector
// and decrypt the data, passing back the unserialized from of the value.
let value = base64_decode(payload["value"]),
iv = base64_decode(payload["iv"]);
return unserialize(this->stripPadding(this->doDecrypt(value, iv)));
}
/**
* Actually decrypt the value using the given Iv with the openssl library decrypt function.
*
* @param string value
* @param string iv
* @return string
*/
protected function doDecrypt(string value, string iv) -> string
{
return openssl_decrypt(value, this->cipher . "-" . this->mode, this->key, OPENSSL_RAW_DATA, iv);
}
/**
* Get the JSON array from the given payload.
*
* @param string text payload
* @return array
*/
protected function getJsonPayload(string text) -> array
{
var payload;
let payload = [],
payload = json_decode(base64_decode(text), 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 !payload || this->invalidPayload(payload) {
throw new Exception("Invalid data passed to encrypter.");
}
if payload["mac"] != this->hash(payload["value"]) {
throw new Exception("MAC for payload is invalid.");
}
return payload;
}
/**
* Create a MAC for the given value.
*
* @param string value
* @return string
*/
protected function hash(string value) -> string
{
return hash_hmac("sha256", value, this->key);
}
/**
* Add PKCS7 padding to a given value.
*
* @param string value
* @return string
*/
protected function addPadding(string value) -> string
{
var pad, len;
let len = strlen(value),
pad = this->block - (len % this->block);
return value . str_repeat(chr(pad), pad);
}
/**
* Remove the padding from the given value.
*
* @param string value
* @return string
*/
protected function stripPadding(string value) -> string
{
var pad, len;
let len = (int) strlen(value),
pad = (int) ord(value[len - 1]);
return this->paddingIsValid(pad, value) ? substr(value, 0, len - pad) : value;
}
/**
* Determine if the given padding for a value is valid.
*
* @param int pad
* @param string value
* @return bool
*/
protected function paddingIsValid(int pad, string value) -> boolean
{
var beforePad;
let beforePad = strlen(value) - pad;
return substr(value, beforePad) == str_repeat(substr(value, -1), pad);
}
/**
* Verify that the encryption payload is valid.
*
* @param array data
* @return bool
*/
protected function invalidPayload(array data) -> boolean
{
return !isset data["iv"] || !isset data["value"] || !isset data["mac"];
}
/**
* Get the IV size for the cipher.
*
* @return int
*/
protected function getIvSize() -> int
{
return openssl_cipher_iv_length(this->cipher . "-" . this->mode);
}
}