# s2member/261011/src/includes/classes/utils-defuse.inc.php

s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls &amp; Member Access Subscriptions, version 261011. 360 lines.

- Page: https://pluginprobe.com/plugins/s2member/261011/code/src/includes/classes/utils-defuse.inc.php
- Raw: https://pluginprobe.com/plugins/s2member/261011/raw/src/includes/classes/utils-defuse.inc.php
- Modified: 2026-10-11T05:35:48+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/s2member/261011/code/src/includes/classes/utils-defuse.inc.php#L10-L20`.

```php
<?php
// @codingStandardsIgnoreFile
/**
 * Encryption utilities.
 *
 * @since 170418 Defuse.
 */
use Defuse\Crypto\Key;
use Defuse\Crypto\Crypto;
use c_ws_plugin__s2member_utils_strings as s;

if (!defined('WPINC')) { // MUST have.
    exit('Do not access this file directly.');
}
if (!class_exists('c_ws_plugin__s2member_utils_defuse')) {
    /**
     * Encryption utilities.
     *
     * @since 170418 Defuse.
     */
    class c_ws_plugin__s2member_utils_defuse
    {
        /**
         * Creates a one-way hash for a secret key.
         *
         * @since 260809
         *
         * @param string $secret_key Secret encryption key.
         * @return string Secret key hash, else an empty string.
         */
        private static function secret_key_hash($secret_key)
        {
            $secret_key = (string) $secret_key;

            // Identify a secret key without storing the secret key itself in the new mapping.
            return isset($secret_key[0]) ? s::hmac_sha256_sign('s2member:defuse:key-mapping', $secret_key) : '';
        }

        /**
         * Converts one legacy default Defuse key mapping.
         *
         * @since 260809
         *
         * @param string $legacy_mapping Legacy `secret-key\nDefuse-key` mapping.
         * @return array Hashed secret-key-to-Defuse-key mapping, else an empty array.
         */
        private static function convert_legacy_defuse_key_mapping($legacy_mapping)
        {
            $legacy_mapping = (string) $legacy_mapping;
            $separator_pos  = strrpos($legacy_mapping, "\ndef00000");

            if ($separator_pos === false) {
                return array();
            }
            $secret_key = substr($legacy_mapping, 0, $separator_pos);
            $defuse_key = substr($legacy_mapping, $separator_pos + 1);

            if (!isset($secret_key[0]) || !isset($defuse_key[0])) {
                return array();
            }
            // Validate the existing Defuse key before preserving its mapping.
            try {
                Key::loadFromAsciiSafeString($defuse_key);
            }
            // Catch Defuse exceptions and other Throwables on PHP 7+.
            catch (Throwable $Exception) {
                return array();
            }
            // Catch Defuse exceptions on PHP 5.6, where Throwable is unavailable.
            catch (Exception $Exception) {
                return array();
            }
            return array(self::secret_key_hash($secret_key) => $defuse_key);
        }

        /**
         * Checks whether a stored Defuse key can be loaded.
         *
         * @since 261006
         *
         * @param string $defuse_key Stored ASCII-safe Defuse key.
         * @return bool True for a valid key, else false.
         */
        private static function valid_defuse_key($defuse_key)
        {
            if (!is_string($defuse_key) || !isset($defuse_key[0])) {
                return false;
            }
            try {
                Key::loadFromAsciiSafeString($defuse_key);
                return true;
            } catch (Throwable $Exception) {
                return false;
            } catch (Exception $Exception) {
                return false;
            }
        }

        /**
         * Migrates legacy Defuse key mappings to hashed mappings.
         *
         * Preserves the existing Defuse keys while creating hashed mappings for
         * the current default key, recovery history, and custom secret keys.
         * Legacy mappings remain untouched for rollback compatibility.
         *
         * @since 260809
         *
         * @return void
         */
        public static function migrate_legacy_defuse_key_mappings()
        {
            $options = array();

            //261006.0424 Fingerprinted associations are authoritative; legacy data fills only missing or invalid keys, including direct upgrades.
            $legacy_mapping        = $GLOBALS['WS_PLUGIN__']['s2member']['o']['def_combo_encryption_key'];
            $converted_defuse_keys = self::convert_legacy_defuse_key_mapping($legacy_mapping);
            $defuse_keys           = $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key'];
            foreach ($converted_defuse_keys as $_secret_key_hash => $_defuse_key) {
                if (!isset($defuse_keys[$_secret_key_hash]) || !self::valid_defuse_key($defuse_keys[$_secret_key_hash])) {
                    $defuse_keys[$_secret_key_hash] = $_defuse_key;
                }
            }
            if ($defuse_keys !== $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key']) {
                $defuse_keys_update = $defuse_keys;
                array_unshift($defuse_keys_update, 'update-signal');
                $options['ws_plugin__s2member_secret_key_to_defuse_key'] = $defuse_keys_update;
            }

            //261006.0424 Keep canonical recovery history ahead of legacy additions; conflicting keys remain available for explicit recovery, not normal lookup.
            $defuse_key_history = $defuse_keys ? array($defuse_keys) : array();
            foreach ($GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key_history'] as $_defuse_keys) {
                if (is_array($_defuse_keys) && count($_defuse_keys) === 1 && !in_array($_defuse_keys, $defuse_key_history, true)) {
                    $defuse_key_history[] = $_defuse_keys;
                }
            }
            if ($converted_defuse_keys && !in_array($converted_defuse_keys, $defuse_key_history, true)) {
                $defuse_key_history[] = $converted_defuse_keys;
            }
            foreach ($GLOBALS['WS_PLUGIN__']['s2member']['o']['def_combo_encryption_key_history'] as $_legacy_mapping) {
                $_defuse_keys = self::convert_legacy_defuse_key_mapping($_legacy_mapping);
                if ($_defuse_keys && !in_array($_defuse_keys, $defuse_key_history, true)) {
                    $defuse_key_history[] = $_defuse_keys;
                }
            }
            $defuse_key_history = array_slice($defuse_key_history, 0, 10);
            if ($defuse_key_history !== $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key_history']) {
                $defuse_key_history_update = $defuse_key_history;
                array_unshift($defuse_key_history_update, 'update-signal');
                $options['ws_plugin__s2member_secret_key_to_defuse_key_history'] = $defuse_key_history_update;
            }

            // Migrate custom mappings without storing custom secret keys in the new mapping.
            $custom_defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['custom_secret_key_to_defuse_key'];
            foreach ($GLOBALS['WS_PLUGIN__']['s2member']['o']['def_custom_combo_encryption_keys'] as $_secret_key => $_defuse_key) {
                $_secret_key = (string) $_secret_key;
                $_defuse_key = (string) $_defuse_key;

                if (isset($_secret_key[0]) && self::valid_defuse_key($_defuse_key)) {
                    $_secret_key_hash = self::secret_key_hash($_secret_key);
                    if (!isset($custom_defuse_keys[$_secret_key_hash]) || !self::valid_defuse_key($custom_defuse_keys[$_secret_key_hash])) {
                        $custom_defuse_keys[$_secret_key_hash] = $_defuse_key;
                    }
                }
            }
            if ($custom_defuse_keys !== $GLOBALS['WS_PLUGIN__']['s2member']['o']['custom_secret_key_to_defuse_key']) {
                array_unshift($custom_defuse_keys, 'update-signal');
                $options['ws_plugin__s2member_custom_secret_key_to_defuse_key'] = $custom_defuse_keys;
            }
            if ($options) {
                c_ws_plugin__s2member_menu_pages::update_all_options($options, true, false, false, false, false);
            }
        }

        /**
         * Gets an existing Defuse encryption key.
         *
         * @since 260809
         *
         * @param string $secret_key Optional custom encryption secret key, or an existing Defuse key.
         * @return string Defuse encryption key, else an empty string.
         */
        public static function get_defuse_key($secret_key = '')
        {
            $secret_key = (string) $secret_key;

            if (isset($secret_key[0]) && strpos($secret_key, 'def00000') === 0) {
                return $secret_key; // Preserve direct Defuse-key input supported by the original accessor.
            }
            $has_custom_secret_key = isset($secret_key[0]);
            $secret_key            = c_ws_plugin__s2member_utils_encryption::key($secret_key);
            $secret_key_hash       = self::secret_key_hash($secret_key);

            if ($has_custom_secret_key) {
                //261006.0424 Prefer valid fingerprinted keys while retaining legacy lookup for installations not yet migrated.
                $custom_defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['custom_secret_key_to_defuse_key'];
                if ($secret_key_hash && isset($custom_defuse_keys[$secret_key_hash]) && self::valid_defuse_key($custom_defuse_keys[$secret_key_hash])) {
                    return $custom_defuse_keys[$secret_key_hash];
                }
                $legacy_custom_defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['def_custom_combo_encryption_keys'];
                if ($secret_key && isset($legacy_custom_defuse_keys[$secret_key]) && self::valid_defuse_key($legacy_custom_defuse_keys[$secret_key])) {
                    return $legacy_custom_defuse_keys[$secret_key];
                }
                return '';
            }
            $defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key'];
            if ($secret_key_hash && isset($defuse_keys[$secret_key_hash]) && self::valid_defuse_key($defuse_keys[$secret_key_hash])) {
                return $defuse_keys[$secret_key_hash];
            }
            $legacy_mapping = $GLOBALS['WS_PLUGIN__']['s2member']['o']['def_combo_encryption_key'];
            if ($secret_key && $legacy_mapping && strpos($legacy_mapping, $secret_key."\n") === 0) {
                $legacy_defuse_key = substr($legacy_mapping, strlen($secret_key) + 1);
                return self::valid_defuse_key($legacy_defuse_key) ? $legacy_defuse_key : '';
            }
            return '';
        }

        /**
         * Creates and stores a new Defuse encryption key.
         *
         * @since 260809
         *
         * @param string $secret_key Optional custom encryption secret key.
         * @return string New Defuse encryption key.
         */
        public static function create_defuse_key($secret_key = '')
        {
            $secret_key            = (string) $secret_key;
            $has_custom_secret_key = isset($secret_key[0]);
            $secret_key            = c_ws_plugin__s2member_utils_encryption::key($secret_key);
            $secret_key_hash       = self::secret_key_hash($secret_key);
            $options               = array();

            // Generate a new Defuse encryption key.
            try {
                if (!($defuse_key = Key::createNewRandomKey()->saveToAsciiSafeString())) {
                    throw new Exception('Defuse keygen failure.');
                }
            }
            // Catch Defuse exceptions and other Throwables on PHP 7+.
            catch (Throwable $Exception) {
                throw new Exception($Exception->getMessage());
            }
            // Catch Defuse exceptions on PHP 5.6, where Throwable is unavailable.
            catch (Exception $Exception) {
                throw new Exception($Exception->getMessage());
            }
            if ($has_custom_secret_key) {
                $legacy_custom_defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['def_custom_combo_encryption_keys'];
                $custom_defuse_keys        = $GLOBALS['WS_PLUGIN__']['s2member']['o']['custom_secret_key_to_defuse_key'];

                // Dual-write legacy and hashed custom mappings for rollback compatibility.
                $legacy_custom_defuse_keys[$secret_key] = $defuse_key;
                $custom_defuse_keys[$secret_key_hash]    = $defuse_key;
                array_unshift($legacy_custom_defuse_keys, 'update-signal');
                array_unshift($custom_defuse_keys, 'update-signal');
                $options['ws_plugin__s2member_def_custom_combo_encryption_keys'] = $legacy_custom_defuse_keys;
                $options['ws_plugin__s2member_custom_secret_key_to_defuse_key']  = $custom_defuse_keys;
            } else {
                // Dual-write legacy and hashed default mappings for rollback compatibility.
                $defuse_keys = array($secret_key_hash => $defuse_key);
                array_unshift($defuse_keys, 'update-signal');
                $options['ws_plugin__s2member_def_combo_encryption_key'] = $secret_key."\n".$defuse_key;
                $options['ws_plugin__s2member_secret_key_to_defuse_key'] = $defuse_keys;
            }
            c_ws_plugin__s2member_menu_pages::update_all_options($options, true, false, false, false, false);

            return $defuse_key;
        }

        /**
         * Gets an existing Defuse key, or creates one when needed.
         *
         * @since 260809
         *
         * @param string $secret_key Optional custom encryption secret key, or an existing Defuse key.
         * @return string Defuse encryption key.
         */
        public static function get_or_create_defuse_key($secret_key = '')
        {
            // Keep lookup read-only; persist a Defuse key only when one must be created.
            return ($defuse_key = self::get_defuse_key($secret_key)) ? $defuse_key : self::create_defuse_key($secret_key);
        }

        /**
         * Gets or creates a Defuse encryption key.
         *
         * @since 170418 Defuse.
         * @since 260809 Retained as a compatibility wrapper around the explicit Defuse key routines.
         *
         * @param string $key Optional custom encryption secret key, or an existing Defuse key.
         * @return string Defuse encryption key.
         */
        public static function key($key = '')
        {
            //260809 Preserve the original public method while making new internal key handling explicit.
            return self::get_or_create_defuse_key($key);
        }

        /**
         * Defuse encryption.
         *
         * @since 170418 Defuse.
         *
         * @param string $string String to encrypt.
         * @param string $key    Optional custom encryption secret key, or an existing Defuse key.
         * @return string Encrypted string w/ a URL-safe base64 wrapper.
         */
        public static function encrypt($string, $key = '')
        {
            $string = (string) $string;

            if (!isset($string[0])) {
                return ''; // Not possible.
            } // Empty string is an empty string.

            try {
                //260809 Create a Defuse key only when encryption needs one.
                $defuse_crypto = Key::loadFromAsciiSafeString(self::get_or_create_defuse_key($key));
                $encrypted     = Crypto::encrypt($string, $defuse_crypto, false);
                return $base64 = s::base64_url_safe_encode($encrypted);
            } catch (Throwable $Exception) {
                throw new Exception($Exception->getMessage());
            }
            //260809 Catch Defuse exceptions on PHP 5.6, where Throwable is unavailable.
            catch (Exception $Exception) {
                throw new Exception($Exception->getMessage());
            }
        }

        /**
         * Defuse decryption.
         *
         * @since 170418 Defuse.
         *
         * @param string $base64 String to decrypt.
         * @param string $key    Optional custom encryption secret key, or an existing Defuse key.
         * @return string Decrypted string.
         */
        public static function decrypt($base64, $key = '')
        {
            if (!is_string($base64) || !isset($base64[0])) {
                return ''; // Not possible.
            } // Fail when not a string or empty.

            try {
                //260809 Keep decryption read-only by retrieving only an existing Defuse key.
                $defuse_crypto = Key::loadFromAsciiSafeString(self::get_defuse_key($key));
                $encrypted     = s::base64_url_safe_decode($base64);
                return $string = Crypto::decrypt($encrypted, $defuse_crypto, false);
            } catch (Throwable $Exception) {
                return ''; // Soft failure.
            }
            //260809 Catch Defuse exceptions on PHP 5.6, where Throwable is unavailable.
            catch (Exception $Exception) {
                return ''; // Soft failure.
            }
        }
    }
}

```
