yf_encryption class

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

Fileplugins/sys/classes/yf_encryption.class.php
Use_class('encryption')

Public properties

A site sets them in its config with $PROJECT_CONF['encryption']['NAME'] = ...;

PropertyDefaultDescription
$DRIVER
string
'cast' 'cast' (previous behaviour, default until 4.0) or 'sodium'
$SECRET_KEY
string
'' Per-install secret for the sodium driver (32+ random bytes); YF_SECRET_KEY constant or project conf. The cast driver ignores it
$USE_CIPHER
int
4 Define which cryptographic algorithm to use ('cast' driver)
$USE_MCRYPT
bool
false Ignored: mcrypt no longer exists in PHP; kept so old project configs still load
$_avail_ciphers
array
[4 => 'CAST_256', 0 => 'CAST_128'] Available algorithms (sorted in speed descending order)
$_cur_cipher
mixed
null @conf_skip Current cipher processor
$_cur_cipher_id
mixed
null @conf_skip Current cipher id
$_cur_cipher_key
mixed
null @conf_skip Key the current cipher object was built with (phpcrypt keys at construction)
$_default_key
string
'secret__13457890' @conf_skip The built-in key, to warn when the sodium driver runs with it
$_iv
mixed
null @conf_skip Initialization vector / nonce of the last encrypt()
$_key_assigned
mixed
false @conf_skip Key assigned
$_secret_key
string
'secret__13457890' Secret key
$_warned_default_key
bool
false @conf_skip

Public methods 23

decrypt

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));
    }

decrypt_file

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

encrypt

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);
    }

encrypt_file

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

get_cipher

public function get_cipher()
    public function get_cipher()
    {
        return $this->_avail_ciphers[$this->USE_CIPHER];
    }

get_driver

public function get_driver()
    public function get_driver()
    {
        return $this->_driver();
    }

get_iv

public function get_iv()
    public function get_iv()
    {
        return $this->_iv;
    }

get_key

public function get_key()
    public function get_key()
    {
        return $this->_secret_key;
    }

get_secret

public function get_secret()

Alias.

    public function get_secret()
    {
        return $this->get_key();
    }

init

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

set_cipher

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

set_driver

public function set_driver($driver)
  • $driver null|string
    public function set_driver($driver)
    {
        $this->DRIVER = $this->_driver($driver);
        return $this;
    }

set_key

public function set_key($value)
    public function set_key($value)
    {
        $this->_secret_key = $value;
        return $this;
    }

set_secret

public function set_secret($value)

Alias.

    public function set_secret($value)
    {
        return $this->set_key($value);
    }

Methods starting with _ hooks and helpers; not routable as page actions

__call

public function __call($name, $args)

Catch missing method call.

    public function __call($name, $args)
    {
        return main()->extend_call($this, $name, $args);
    }

__construct

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

_driver

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);
    }

_sodium_key

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