| 1 |
<?php |
| 2 |
// @codingStandardsIgnoreFile |
| 3 |
/** |
| 4 |
* Encryption utilities. |
| 5 |
* |
| 6 |
* @since 3.5 Nearly the first release. |
| 7 |
*/ |
| 8 |
if (!defined('WPINC')) { // MUST have. |
| 9 |
exit('Do not access this file directly.'); |
| 10 |
} |
| 11 |
if (!class_exists('c_ws_plugin__s2member_utils_encryption')) { |
| 12 |
/** |
| 13 |
* Encryption utilities. |
| 14 |
* |
| 15 |
* @since 3.5 Nearly the first release. |
| 16 |
*/ |
| 17 |
class c_ws_plugin__s2member_utils_encryption |
| 18 |
{ |
| 19 |
/** |
| 20 |
* Encryption key. |
| 21 |
* |
| 22 |
* @since 111106 Get key. |
| 23 |
* |
| 24 |
* @param string $key Custom key. |
| 25 |
* |
| 26 |
* @return string Encryption key. |
| 27 |
*/ |
| 28 |
public static function key($key = '') |
| 29 |
{ |
| 30 |
if (($key = trim((string) $key))) { |
| 31 |
return $key; |
| 32 |
} elseif (($key = $GLOBALS['WS_PLUGIN__']['s2member']['o']['sec_encryption_key'])) { |
| 33 |
return $key; |
| 34 |
} elseif (($key = wp_salt())) { |
| 35 |
return $key; |
| 36 |
} |
| 37 |
return $key = md5($_SERVER['HTTP_HOST']); |
| 38 |
} |
| 39 |
|
| 40 |
/** |
| 41 |
* A unique, unguessable, non-numeric, caSe-insensitive key (20 chars max). |
| 42 |
* |
| 43 |
* @since 150124 Adding gift code generation. |
| 44 |
* |
| 45 |
* @note 32-bit systems usually have `PHP_INT_MAX` = `2147483647`. |
| 46 |
* We limit `mt_rand()` to a max of `999999999`. |
| 47 |
* |
| 48 |
* @note A max possible length of 20 chars assumes this function |
| 49 |
* will not be called after `Sat, 20 Nov 2286 17:46:39 GMT`. |
| 50 |
* At which point a UNIX timestamp will grow in size. |
| 51 |
* |
| 52 |
* @note Key always begins with a `k` to prevent PHP's `is_numeric()` |
| 53 |
* function from ever thinking it's a number in a different representation. |
| 54 |
* See: <http://php.net/manual/en/function.is-numeric.php> for further details. |
| 55 |
* |
| 56 |
* @return string A unique, unguessable, non-numeric, caSe-insensitive key (20 chars max). |
| 57 |
*/ |
| 58 |
public static function uunnci_key_20_max() |
| 59 |
{ |
| 60 |
$microtime_19_max = number_format(microtime(true), 9, '.', ''); |
| 61 |
// e.g., `9999999999`.`999999999` (max decimals: `9`, max overall precision: `19`). |
| 62 |
// Assuming timestamp is never > 10 digits; i.e., before `Sat, 20 Nov 2286 17:46:39 GMT`. |
| 63 |
|
| 64 |
list($seconds_10_max, $microseconds_9_max) = explode('.', $microtime_19_max, 2); |
| 65 |
// e.g., `array(`9999999999`, `999999999`)`. Max total digits combined: `19`. |
| 66 |
|
| 67 |
$seconds_base36 = base_convert($seconds_10_max, '10', '36'); // e.g., max `9999999999`, to base 36. |
| 68 |
$microseconds_base36 = base_convert($microseconds_9_max, '10', '36'); // e.g., max `999999999`, to base 36. |
| 69 |
$mt_rand_base36 = base_convert(mt_rand(1, 999999999), '10', '36'); // e.g., max `999999999`, to base 36. |
| 70 |
$key = 'k'.$mt_rand_base36.$seconds_base36.$microseconds_base36; // e.g., `kgjdgxr4ldqpdrgjdgxr`. |
| 71 |
|
| 72 |
return $key; // Max possible value: `kgjdgxr4ldqpdrgjdgxr` (20 chars). |
| 73 |
} |
| 74 |
|
| 75 |
/** |
| 76 |
* Encrypt w/ best possible technique. |
| 77 |
* |
| 78 |
* @since 3.5 Nearly the first release. |
| 79 |
* |
| 80 |
* @param string $string String to encrypt. |
| 81 |
* @param string $key Optional custom encryption key. |
| 82 |
* @param bool $w_md5_cs Defaults to true. When true, an MD5 checksum. |
| 83 |
* @param bool|null $allow_defuse Allow Defuse encryption as a better alternative? |
| 84 |
* |
| 85 |
* @return string Encrypted string. |
| 86 |
*/ |
| 87 |
public static function encrypt($string = '', $key = '', $w_md5_cs = true, $allow_defuse = null) |
| 88 |
{ |
| 89 |
$allow_defuse = isset($allow_defuse) ? $allow_defuse |
| 90 |
: apply_filters('c_ws_plugin__s2member_allow_defuse', true); |
| 91 |
|
| 92 |
if ($allow_defuse && version_compare(PHP_VERSION, '7.0.4', '>=')) { |
| 93 |
return c_ws_plugin__s2member_utils_defuse::encrypt($string, $key); |
| 94 |
} // This is a new/improved way of handling encryption. |
| 95 |
|
| 96 |
if (function_exists('mcrypt_encrypt') |
| 97 |
&& in_array('rijndael-256', @mcrypt_list_algorithms()) |
| 98 |
&& in_array('cbc', @mcrypt_list_modes())) { |
| 99 |
// |
| 100 |
$string = is_string($string) ? $string : ''; |
| 101 |
$string = isset($string[0]) ? '~r2|'.$string : ''; |
| 102 |
|
| 103 |
$key = self::key($key); // Get encryption key. |
| 104 |
$key = substr($key, 0, @mcrypt_get_key_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC)); |
| 105 |
$iv = c_ws_plugin__s2member_utils_strings::random_str_gen(@mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), false); |
| 106 |
|
| 107 |
if (isset($string[0]) && is_string($e = @mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $string, MCRYPT_MODE_CBC, $iv)) && isset($e[0])) { |
| 108 |
$e = '~r2:'.$iv.($w_md5_cs ? ':'.md5($e) : '').'|'.$e; |
| 109 |
} |
| 110 |
return isset($e) && is_string($e) && isset($e[0]) |
| 111 |
? ($base64 = c_ws_plugin__s2member_utils_strings::base64_url_safe_encode($e)) |
| 112 |
: ''; // Default to empty string. |
| 113 |
} |
| 114 |
return self::xencrypt($string, $key, $w_md5_cs); |
| 115 |
} |
| 116 |
|
| 117 |
/** |
| 118 |
* Decrypt w/ best possible technique. |
| 119 |
* |
| 120 |
* @since 3.5 Nearly the first release. |
| 121 |
* @since 260810 Allows Defuse decryption on PHP 5.6+. |
| 122 |
* |
| 123 |
* @param string $base64 String to decrypt (base64). |
| 124 |
* @param string $key Optional custom decryption key. |
| 125 |
* @param bool|null $allow_defuse Allow Defuse decryption as a better alternative? |
| 126 |
* |
| 127 |
* @return string Decrypted string, else empty string. |
| 128 |
*/ |
| 129 |
public static function decrypt($base64 = '', $key = '', $allow_defuse = null) |
| 130 |
{ |
| 131 |
if (!is_string($base64) || !isset($base64[0])) { |
| 132 |
return ''; // Not possible. |
| 133 |
} // Fail when not a string or empty. |
| 134 |
|
| 135 |
$allow_defuse = isset($allow_defuse) ? $allow_defuse |
| 136 |
: apply_filters('c_ws_plugin__s2member_allow_defuse', true); |
| 137 |
|
| 138 |
//260810 Allow PHP 5.6 to read Defuse ciphertext created on newer PHP versions; decryption was previously gated to PHP 7.0.4+. |
| 139 |
// Defuse encryption remains gated separately so PHP 5.6 does not generate new Defuse ciphertext yet. |
| 140 |
if ($allow_defuse && ($_d = c_ws_plugin__s2member_utils_defuse::decrypt($base64, $key))) { |
| 141 |
return $string = $_d; // Defuse success. |
| 142 |
} |
| 143 |
|
| 144 |
if (function_exists('mcrypt_decrypt') |
| 145 |
&& in_array('rijndael-256', @mcrypt_list_algorithms()) |
| 146 |
&& in_array('cbc', @mcrypt_list_modes())) { |
| 147 |
// |
| 148 |
$e = c_ws_plugin__s2member_utils_strings::base64_url_safe_decode($base64); |
| 149 |
|
| 150 |
if (preg_match('/^~r2\:([a-zA-Z0-9]+)(?:\:([a-zA-Z0-9]+))?\|(.*)$/s', $e, $iv_md5_e)) { |
| 151 |
$key = self::key($key); // Get encryption key. |
| 152 |
$key = substr($key, 0, @mcrypt_get_key_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC)); |
| 153 |
|
| 154 |
if (isset($iv_md5_e[3][0]) && (empty($iv_md5_e[2]) || $iv_md5_e[2] === md5($iv_md5_e[3]))) { |
| 155 |
$d = @mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $iv_md5_e[3], MCRYPT_MODE_CBC, $iv_md5_e[1]); |
| 156 |
} |
| 157 |
if (!isset($d)) { // Failed above? |
| 158 |
return ''; // Empty string on failure. |
| 159 |
} elseif (!strlen($d = preg_replace('/^~r2\|/', '', $d, 1, $r2)) || !$r2) { |
| 160 |
return ''; // Empty string on failure. |
| 161 |
} |
| 162 |
return $string = rtrim($d, "\0\4"); |
| 163 |
} |
| 164 |
} |
| 165 |
return self::xdecrypt($base64, $key); |
| 166 |
} |
| 167 |
|
| 168 |
/** |
| 169 |
* XOR two-way encryption/decryption, with a base64 wrapper. |
| 170 |
* |
| 171 |
* @since 3.5 Nearly the first release. |
| 172 |
* |
| 173 |
* @param string $string A string of data to encrypt. |
| 174 |
* @param string $key Optional. Key used for encryption. Defaults to the one configured for s2Member. Short of that, defaults to: ``wp_salt()``. |
| 175 |
* @param bool $w_md5_cs Optional. Defaults to true. When true, an MD5 checksum is used in the encrypted string *(recommended)*. |
| 176 |
* |
| 177 |
* @return string Encrypted string. |
| 178 |
*/ |
| 179 |
public static function xencrypt($string = '', $key = '', $w_md5_cs = true) |
| 180 |
{ |
| 181 |
$string = is_string($string) ? $string : ''; |
| 182 |
$string = isset($string[0]) ? '~xe|'.$string : ''; |
| 183 |
$key = self::key($key); // Get encryption key. |
| 184 |
|
| 185 |
for ($i = 1, $e = ''; $i <= strlen($string); ++$i) { |
| 186 |
$char = substr($string, $i - 1, 1); |
| 187 |
$keychar = substr($key, ($i % strlen($key)) - 1, 1); |
| 188 |
//260830.2134 PHP 8.5 deprecates chr() values outside 0..255; mask explicitly to preserve chr()'s historical byte-wrap behavior. |
| 189 |
$e .= chr((ord($char) + ord($keychar)) & 0xFF); |
| 190 |
} |
| 191 |
$e = isset($e[0]) ? '~xe'.($w_md5_cs ? ':'.md5($e) : '').'|'.$e : ''; |
| 192 |
return $base64 = isset($e[0]) ? ($base64 = c_ws_plugin__s2member_utils_strings::base64_url_safe_encode($e)) : ''; |
| 193 |
} |
| 194 |
|
| 195 |
/** |
| 196 |
* XOR decryption. |
| 197 |
* |
| 198 |
* @since 3.5 Nearly the first release. |
| 199 |
* |
| 200 |
* @param string $base64 String to decrypt (base64). |
| 201 |
* @param string $key Optional custom decryption key. |
| 202 |
* |
| 203 |
* @return string Decrypted string. |
| 204 |
*/ |
| 205 |
public static function xdecrypt($base64 = '', $key = '') |
| 206 |
{ |
| 207 |
if (!is_string($base64) || !isset($base64[0])) { |
| 208 |
return ''; // Not possible. |
| 209 |
} // Fail when not a string or empty. |
| 210 |
|
| 211 |
$e = c_ws_plugin__s2member_utils_strings::base64_url_safe_decode($base64); |
| 212 |
|
| 213 |
if (preg_match('/^~xe(?:\:([a-zA-Z0-9]+))?\|(.*)$/s', $e, $md5_e)) { |
| 214 |
$key = self::key($key); // Get encryption key. |
| 215 |
|
| 216 |
if (isset($md5_e[2][0]) && (empty($md5_e[1]) || $md5_e[1] === md5($md5_e[2]))) { |
| 217 |
for ($i = 1, $d = ''; $i <= strlen($md5_e[2]); ++$i) { |
| 218 |
$char = substr($md5_e[2], $i - 1, 1); |
| 219 |
$keychar = substr($key, ($i % strlen($key)) - 1, 1); |
| 220 |
//260830.2134 PHP 8.5 deprecates chr() values outside 0..255; mask explicitly to preserve chr()'s historical byte-wrap behavior. |
| 221 |
$d .= chr((ord($char) - ord($keychar)) & 0xFF); |
| 222 |
} // Reverse XOR encryption. |
| 223 |
} // Else the checksum was not a match. |
| 224 |
|
| 225 |
if (!isset($d)) { // Failed above? |
| 226 |
return ''; // Empty string on failure. |
| 227 |
} elseif (!strlen($d = preg_replace('/^~xe\|/', '', $d, 1, $xe)) || !$xe) { |
| 228 |
return ''; // Empty string on failure. |
| 229 |
} |
| 230 |
return $string = $d; // Decryption success. |
| 231 |
} |
| 232 |
return ''; // Empty string on failure. |
| 233 |
} |
| 234 |
} |
| 235 |
} |
| 236 |
|