getBytes($bytes)); } public static function base64PWD(int $bytes = 16): string { $r = new RandomEngine(); return rtrim(strtr( base64_encode($r->getBytes($bytes)), '+/', '-_' ), '='); } /** * Get unique IDs for database * @return int */ public static function getUniqueNumber(): int { return abs(crc32(microtime())); } /** * Get token * @return string */ public static function getUniqueId(): string { $moreEntropy = true; $prefix = ""; // Blank is a rand string return md5(uniqid($prefix, $moreEntropy)); } public static function useHmac(string $algo, string $pepper) { if (!function_exists("hash_hmac_algos")) { throw new \Exception("hash_hmac not installed!"); } if (strpos($algo, "md") !== false) { throw new \Exception("MD is too weak!"); } if (strpos($algo, "sha1") !== false) { throw new \Exception("sha1 is too weak!"); } $allowed = hash_hmac_algos(); if (in_array(strtolower($algo), $allowed)) { return hash_hmac($algo, $pepper); } throw new \Exception("hmac algo not found!"); } /* * Consider MD5 and SHA1 which are fast and efficient, * making them ideal for check summing and file verification. * However, their speed makes them unsuitable for hashing a * user’s password. With today’s computational power of * modern CPUs/GPUs and cloud computing, a hashed password * can be cracked by brute force in a matter of minutes. * It’s fairly trivial to quickly generate billions of MD5 * hashes from random words until a match is found, thereby * revealing the original plain-text password. Instead, * intentionally slower hashing algorithms such as bcrypt * or Argon2 should be used. */ public static function findDefaultHashAlgo() { if (defined("PASSWORD_ARGON2ID")) return PASSWORD_ARGON2ID; if (defined("PASSWORD_ARGON2")) return PASSWORD_ARGON2; if (defined("PASSWORD_DEFAULT")) return PASSWORD_DEFAULT; if (defined("PASSWORD_BCRYPT")) return PASSWORD_BCRYPT; return false; } private static function isValidHashAlgo($algo): bool { return (in_array($algo, password_algos())); } /* * The password_hash() function not only uses a secure * one-way hashing algorithm, but it automatically handles * salt and prevents time based side-channel attacks. */ public static function doPasswordHash(#[\SensitiveParameter] string $password): bool|string { $pwdPeppered = self::makeHash($password); $hashAlgo = Configure::get( "security", "hash_algo" ) ?? false; if ($hashAlgo === false) { throw new \Exception("Security Hash Algo not set!"); } if (!self::isValidHashAlgo($hashAlgo)) { throw new \Exception("Invalid Security Hash Alogo set"); } return password_hash($pwdPeppered, $hashAlgo); } public static function doPasswordVerify( #[\SensitiveParameter] string $inputPwd, #[\SensitiveParameter] $dbPassword ): bool { $pwdPeppered = self::makeHash($inputPwd); return password_verify($pwdPeppered, $dbPassword); } /** * Make a secure Hash * @param string $text to encode * @param string $level (weak, low, high, max) * @return string new Hashed */ public static function makeHash(#[\SensitiveParameter] string $text): string { $level = Configure::get('security', 'hash_level'); if (empty($level)) { $level = "normal"; } $pepper = Configure::get('security', 'pepper_pwd'); if (strlen($pepper) < 12) { throw new \Exception("Pepper Password, too short!"); } $salt = Configure::get('security', 'salt_pwd'); if (strlen($salt) < 5) { throw new \Exception("Salt Password, too short!"); } switch (strtolower($level)) { case 'max': // Prefer computing using HMAC if (function_exists("hash_hmac")) { return hash_hmac("sha512", $text, $pepper); } // Sha512 hash is the next best thing if (function_exists("hash")) { return hash("sha512", $salt . $text . $pepper); } case 'normal': // Prefer computing using HMAC if (function_exists("hash_hmac")) { return hash_hmac("sha256", $text, $pepper); } // Sha256 hash is the next best thing if (function_exists("hash")) { return hash("sha256", $salt . $text . $pepper); } case 'weak': throw \Exception("Too weak of a Hash FN"); // return sha1($salt . $text . $pepper); case 'low': throw \Exception("Too weak of a Hash FN"); // return md5($salt . md5($text . $pepper)); default: break; } return self::useHmac($level, $pepper); } /** * @method filter_class * @param type $class * Please NEVER add a period or SLASH as it will allow BAD things! * IT should be a-zA-Z0-9_ and that's it. * @retval string of safe class name */ public static function filterClass(string $class): string { if (Requires::isDangerous($class)) { throw new \Exception("Dangerious URI!"); } return preg_replace('/[^a-zA-Z0-9_]/', '', $class); } /** * Filter possible unsafe URI, prevent ../up-level hackers * @param string $uri * @return string Safe URI */ public static function filterUri(string $uri): string { if (Requires::isDangerous($uri) === true) { throw new \Exception("Dangerious URI!"); } return Requires::filterFileName($uri); } public static function idHash(): string { return crc32($_SESSION['user_id']); } public static function isPrivateOrLocalIPSimple(string $ip): bool { if (!self::getValidIp($ip)) { return false; // Invalid } return ( $ip === '::1' || // IPv6 localhost preg_match('/^127\./', $ip) || // IPv4 localhost preg_match('/^10\./', $ip) || // 10.0.0.0/8 preg_match('/^172\.(1[6-9]|2[0-9]|3[0-1])\./', $ip) || // 172.16.0.0/12 preg_match('/^192\.168\./', $ip) || // 192.168.0.0/16 preg_match('/^fd[0-9a-f]{2}:/i', $ip) // IPv6 ULA (fc00::/7) ); } /** * Filter IP return good IP or False! * @param string $ip * @return string | false */ public static function getValidIp(string $ip) { return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)); } public static function getValidPublicIp(string $ip) { return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE)); } /** * Is the server on the local test domain name * @return bool SERVER Domain name is on whitelist */ public static function isServerNameOnDomainList(array $whitelist): bool { if (!isset($_SERVER['SERVER_NAME'])) { return false; } return (in_array($_SERVER['SERVER_NAME'], $whitelist)); } /** * Check if same Domain as Server * @return bool */ public static function requestIsSameDomain(): bool { if (!isset($_SERVER['HTTP_REFERER'])) { // No referer send, so can't be same domain! return false; } else { $refererHost = parse_url($_SERVER['HTTP_REFERER'] . PHP_URL_HOST); if ($refererHost === false) { return false; // Malformed URL } $refed_host = $refererHost['host'] ?? ""; $server_host = $_SERVER['HTTP_HOST']; return ($refed_host === $server_host); } } public static function safeForEval(string $s): string { //new line check $nl = chr(10); if (strpos($s, $nl)) { throw new \Exception("String CR/LF not permitted"); } $meta = ['$', '{', '}', '[', ']', '`', ';']; $escaped = ['$', '{', '}', '[', '`', ';']; // add slashed for quotes and blackslashes $out = addslashes($s); // replace php meta chrs $out = str_repeat($meta, $escaped, $out); return $out; } public static function getClientIpAddress(): string { $ipaddress = ''; if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ipaddress = $_SERVER['HTTP_CLIENT_IP']; } else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR']; } else if (isset($_SERVER['HTTP_X_FORWARDED'])) { $ipaddress = $_SERVER['HTTP_X_FORWARDED']; } else if (isset($_SERVER['HTTP_FORWARDED_FOR'])) { $ipaddress = $_SERVER['HTTP_FORWARDED_FOR']; } else if (isset($_SERVER['HTTP_FORWARDED'])) { $ipaddress = $_SERVER['HTTP_FORWARDED']; } else if (isset($_SERVER['REMOTE_ADDR'])) { $ipaddress = $_SERVER['REMOTE_ADDR']; } else { $ipaddress = 'UNKNOWN'; } return $ipaddress; } public static function doesStringContainsPhp(string $data): bool { $pos = strpos($data, '