PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / trunk
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions vtrunk
260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 120219 120301 All 187 releases
s2member / src / includes / classes / utils-encryption.inc.php

utils-encryption.inc.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions trunk, at src/includes/classes/utils-encryption.inc.php

236 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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