yf_encryption classSymmetric encryption for cookies, hand-off links and files.
Two drivers: 'cast' is what every yf site has been writing (CAST-128/256 in CBC through the bundled phpcrypt
library, no authentication tag) and stays the default; 'sodium' (XSalsa20-Poly1305 secretbox, authenticated,
random nonce) is opt-in via $DRIVER / $PROJECT_CONF['encryption']['DRIVER'] and becomes the default in 4.0.
Decryption of the base64 form detects the driver from the 's1|' prefix, so a site can switch the writer while
the old cookies and links still decode. The mcrypt branch is gone (removed from PHP in 7.2); USE_MCRYPT stays
as a property so project configs keep loading, it has no effect. The sodium driver takes its key from YF_SECRET_KEY /
$PROJECT_CONF['encryption']['SECRET_KEY']; the cast driver keeps whatever key it had (the historical built-in one
unless the site calls set_key()), so existing cookies and links are untouched by setting the constant.
| File | plugins/sys/classes/yf_encryption.class.php |
|---|---|
| Use | _class('encryption') |
A site sets them in its config with $PROJECT_CONF['encryption']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$DRIVERstring |
'cast' |
'cast' (previous behaviour, default until 4.0) or 'sodium' |
$SECRET_KEYstring |
'' |
Per-install secret for the sodium driver (32+ random bytes); YF_SECRET_KEY constant or project conf. The cast driver ignores it |
$USE_CIPHERint |
4 |
Define which cryptographic algorithm to use ('cast' driver) |
$USE_MCRYPTbool |
false |
Ignored: mcrypt no longer exists in PHP; kept so old project configs still load |
$_avail_ciphersarray |
[4 => 'CAST_256', 0 => 'CAST_128'] |
Available algorithms (sorted in speed descending order) |
$_cur_ciphermixed |
null |
@conf_skip Current cipher processor |
$_cur_cipher_idmixed |
null |
@conf_skip Current cipher id |
$_cur_cipher_keymixed |
null |
@conf_skip Key the current cipher object was built with (phpcrypt keys at construction) |
$_default_keystring |
'secret__13457890' |
@conf_skip The built-in key, to warn when the sodium driver runs with it |
$_ivmixed |
null |
@conf_skip Initialization vector / nonce of the last encrypt() |
$_key_assignedmixed |
false |
@conf_skip Key assigned |
$_secret_keystring |
'secret__13457890' |
Secret key |
$_warned_default_keybool |
false |
@conf_skip |
public function decrypt($data, $secret = null, $cipher = null, $iv = null, $driver = null)
$driver null|string public function decrypt($data, $secret = null, $cipher = null, $iv = null, $driver = null)
{
if ( ! $iv) {
return false;
}
if (isset($secret)) {
$this->set_key($secret);
}
if (isset($cipher)) {
$this->set_cipher($cipher);
}
$driver = $this->_driver($driver);
if ($driver === 'sodium') {
if ( ! function_exists('sodium_crypto_secretbox_open') || strlen($iv) !== SODIUM_CRYPTO_SECRETBOX_NONCEBYTES) {
return false;
}
$this->init();
// false on a wrong key or a modified ciphertext: the tag check is the point of the driver
return sodium_crypto_secretbox_open((string) $data, $iv, $this->_sodium_key($this->_sodium_secret()));
}
$this->init();
if ( ! is_object($this->_cur_cipher)) {
return false;
}
$this->_cur_cipher->cipherKey($this->_secret_key);
$this->_cur_cipher->IV($iv);
return rtrim($this->_cur_cipher->decrypt($data));
}
public function decrypt_file($source_path, $decrypted_path, $secret = null, $cipher = null, $iv = null)
Decrypt specified file using private key.
public function decrypt_file($source_path, $decrypted_path, $secret = null, $cipher = null, $iv = null)
{
file_put_contents($decrypted_path, $this->decrypt(file_get_contents($source_path), $secret, $cipher, $iv));
return $this;
}
public function encrypt($data, $secret = null, $cipher = null, $driver = null)
$driver null|string public function encrypt($data, $secret = null, $cipher = null, $driver = null)
{
if (isset($secret)) {
$this->set_key($secret);
}
if (isset($cipher)) {
$this->set_cipher($cipher);
}
$driver = $this->_driver($driver);
if ($driver === 'sodium' && function_exists('sodium_crypto_secretbox')) {
$this->init();
$this->_iv = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
return sodium_crypto_secretbox((string) $data, $this->_iv, $this->_sodium_key($this->_sodium_secret()));
}
$this->init();
if ( ! is_object($this->_cur_cipher)) {
return false;
}
$this->_cur_cipher->cipherKey($this->_secret_key);
$this->_iv = $this->_cur_cipher->createIV();
$this->_cur_cipher->IV($this->_iv);
return $this->_cur_cipher->encrypt($data);
}
public function encrypt_file($source_path, $encrypted_path, $secret = null, $cipher = null)
Encrypt specified file using private key.
public function encrypt_file($source_path, $encrypted_path, $secret = null, $cipher = null)
{
file_put_contents($encrypted_path, $this->encrypt(file_get_contents($source_path), $secret, $cipher));
return $this;
}
public function get_cipher()
public function get_cipher()
{
return $this->_avail_ciphers[$this->USE_CIPHER];
}
public function get_driver()
public function get_driver()
{
return $this->_driver();
}
public function get_iv()
public function get_iv()
{
return $this->_iv;
}
public function get_key()
public function get_key()
{
return $this->_secret_key;
}
public function get_secret()
Alias.
public function get_secret()
{
return $this->get_key();
}
public function init()
public function init()
{
if ($this->_driver() === 'sodium') {
if ( ! function_exists('sodium_crypto_secretbox')) {
trigger_error(__CLASS__ . ': ext-sodium missing, falling back to the cast driver', E_USER_WARNING);
$this->DRIVER = 'cast';
} elseif ($this->_sodium_secret() === $this->_default_key && ! $this->_warned_default_key) {
$this->_warned_default_key = true;
trigger_error(__CLASS__ . ': sodium driver with the built-in default key, set YF_SECRET_KEY', E_USER_NOTICE);
}
}
if ($this->_driver() === 'cast' && ( ! is_object($this->_cur_cipher) || $this->_cur_cipher_id !== $this->USE_CIPHER || $this->_cur_cipher_key !== $this->_secret_key)) {
require_php_lib('phpcrypt');
$cipher_id_to_name = [
0 => PHP_Crypt\PHP_Crypt::CIPHER_CAST_128,
4 => PHP_Crypt\PHP_Crypt::CIPHER_CAST_256,
];
$this->_cur_cipher = new PHP_Crypt\PHP_Crypt($this->_secret_key, $cipher_id_to_name[$this->USE_CIPHER], PHP_Crypt\PHP_Crypt::MODE_CBC);
$this->_cur_cipher_id = $this->USE_CIPHER;
$this->_cur_cipher_key = $this->_secret_key;
}
}
public function set_cipher($name)
Choose encoding/decoding algorithm by its name. Examples: _class('encyption')->set_cipher('cast128'), _class('encyption')->set_cipher('CAST_128').
public function set_cipher($name)
{
$name = str_replace(['_', '-', ' '], '', strtolower(trim($name)));
if ( ! strlen($name)) {
return $this;
}
$name_to_id = [];
foreach ((array) $this->_avail_ciphers as $id => $n) {
$n = str_replace(['_', '-', ' '], '', strtolower(trim($n)));
$name_to_id[$n] = $id;
}
if (isset($name_to_id[$name])) {
$this->USE_CIPHER = $name_to_id[$name];
}
return $this;
}
public function set_driver($driver)
$driver null|string public function set_driver($driver)
{
$this->DRIVER = $this->_driver($driver);
return $this;
}
public function set_key($value)
public function set_key($value)
{
$this->_secret_key = $value;
return $this;
}
public function set_secret($value)
Alias.
public function set_secret($value)
{
return $this->set_key($value);
}
_ hooks and helpers; not routable as page actionspublic function __call($name, $args)
Catch missing method call.
public function __call($name, $args)
{
return main()->extend_call($this, $name, $args);
}
public function __construct()
public function __construct()
{
$crypto_use_cipher = conf('crypto_use_cipher');
if (isset($crypto_use_cipher)) {
$this->USE_CIPHER = $crypto_use_cipher;
}
$crypto_driver = conf('crypto_driver');
if (isset($crypto_driver)) {
$this->DRIVER = $crypto_driver;
}
if (defined('YF_SECRET_KEY') && strlen((string) YF_SECRET_KEY)) {
$this->SECRET_KEY = YF_SECRET_KEY;
}
}
public function _driver($driver = null)
$driver null|string public function _driver($driver = null)
{
$driver = strtolower(trim((string) ($driver ?: $this->DRIVER)));
return $driver === 'sodium' ? 'sodium' : 'cast';
}
public function _safe_base64_decode($text)
public function _safe_base64_decode($text)
{
$r = [
'*' => '/',
' ' => '+',
'%20' => '+',
];
return base64_decode(str_replace(array_keys($r), array_values($r), $text));
}
public function _safe_base64_encode($text)
public function _safe_base64_encode($text)
{
$r = [
'/' => '*',
];
return str_replace(array_keys($r), array_values($r), base64_encode($text));
}
public function _safe_decrypt_with_base64($input, $secret = null, $cipher = null)
Safe decrypt data from base64 string (replace '/' symbol); the driver is taken from the string, not from $DRIVER, so data written before a driver switch still decodes.
public function _safe_decrypt_with_base64($input, $secret = null, $cipher = null)
{
$input = (string) $input;
$driver = 'cast';
if (strpos($input, 's1|') === 0) {
$driver = 'sodium';
$input = substr($input, 3);
}
if (strpos($input, '|') === false) {
return false;
}
list($iv, $encrypted) = explode('|', $input, 2);
return $this->decrypt($this->_safe_base64_decode($encrypted), $secret, $cipher, $this->_safe_base64_decode($iv), $driver);
}
public function _safe_encrypt_with_base64($input, $secret = null, $cipher = null)
Safe encrypt data into base64 string (replace '/' symbol). The sodium driver prefixes 's1|' so decoding can tell the formats apart.
public function _safe_encrypt_with_base64($input, $secret = null, $cipher = null)
{
$encrypted = $this->encrypt($input, $secret, $cipher);
if ($encrypted === false) {
return false;
}
$prefix = $this->_driver() === 'sodium' && function_exists('sodium_crypto_secretbox') ? 's1|' : '';
return $prefix . $this->_safe_base64_encode($this->_iv) . '|' . $this->_safe_base64_encode($encrypted);
}
public function _sodium_key($secret)
32-byte key for secretbox from a secret of any length (generichash, not a KDF: use 32+ random bytes).
public function _sodium_key($secret)
{
return sodium_crypto_generichash((string) $secret, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
}
public function _sodium_secret()
An explicit set_key()/encrypt($d, $secret) wins; otherwise the configured SECRET_KEY; otherwise the historical key (with a notice).
public function _sodium_secret()
{
if ($this->_secret_key !== $this->_default_key) {
return $this->_secret_key;
}
return $this->SECRET_KEY !== '' ? $this->SECRET_KEY : $this->_secret_key;
}