* @copyright Copyright (c) 2022, Robert Strutts. * @license https://mit-license.org/ */ namespace tts\services; /* * NOTICE: This file is just for PLAY, not for PRODUCTION system! * * var_dump($enc->list_ssl_methods()); * var_dump($enc->list_hashes()); */ final class encryption { const iterations = 81952; // should be over 80K. The number of internal iterations to perform for the derivation. const length = 64; // The length of the output string. If raw_output is TRUE this corresponds to the byte-length of the derived key, if raw_output is FALSE this corresponds to twice the byte-length of the derived key (as every byte of the key is returned as two hexits). const raw = true; // When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits. const key_bits = 32; // 256 bit encryption key private $binary = false; private $url_encode = false; private $method = 'AES-256-CBC'; private $default_hash = 'sha256'; // should be sha256 or higher private $_iterations = self::iterations, $_length=self::length, $_raw=self::raw, $_key_bits=self::key_bits; public function change_security_level(string $level): bool { switch (strtolower($level)) { case 'blaze': $this->set_loops(1843); $this->set_length(32); $this->set_key_bits(16); $this->method = 'blowfish'; $this->default_hash = 'md5'; return true; case 'normal': $this->set_loops(81952); $this->set_length(64); $this->set_key_bits(32); $this->method = 'AES-256-CBC'; $this->default_hash = 'sha256'; return true; case 'max': case 'high': case 'slow-secure': $this->set_loops(77891); $this->set_length(128); $this->set_key_bits(64); $this->method = 'AES-256-XTS'; $this->default_hash = 'sha512'; return true; } return false; } public function set_loops(int $iterations=self::iterations): void { $this->_iterations = $iterations; } public function set_length(int $length=self::length): void { $this->_length = $length; } public function set_raw_output(bool $output_raw=self::raw): void { $this->_raw = $output_raw; } public function set_key_bits(int $key_bits=self::key_bits): void { $this->_key_bits = $key_bits; } public function list_ssl_methods(): array { $ciphers = openssl_get_cipher_methods(); //ECB mode should be avoided $ciphers = array_filter( $ciphers, function($n) { return stripos($n,"ecb")===FALSE; } ); //At least as early as Aug 2016, Openssl declared the following weak: RC2, RC4, DES, 3DES, MD5 based $ciphers = array_filter( $ciphers, function($c) { return stripos($c,"des")===FALSE; } ); $ciphers = array_filter( $ciphers, function($c) { return stripos($c,"rc2")===FALSE; } ); $ciphers = array_filter( $ciphers, function($c) { return stripos($c,"rc4")===FALSE; } ); $ciphers = array_filter( $ciphers, function($c) { return stripos($c,"md5")===FALSE; } ); $ciphers = array_map('strtoupper', $ciphers); return array_unique($ciphers); } /** * Change OpenSSl Security Level * @param string $level (low, medium, medium-high, high) or algorithm Method name */ public function change_openssl(string $level): bool { switch (strtolower($level)) { case 'low': case 'blowfish': $this->method = 'blowfish'; return true; case 'medium': $this->method = 'AES-128-CBC'; return true; case 'medium-high': $this->method = 'AES-192-CBC'; return true; case 'high': $this->method = 'AES-256-CBC'; return true; } // end of switch if (in_array(strtoupper($level), $this->list_ssl_methods() )) { $this->method = strtoupper($level); return true; } return false; } public function list_hashes(): array { return hash_algos(); } public function change_hash(string $hash): bool { if (in_array(strtolower($hash), $this->list_hashes() )) { $this->default_hash = strtolower($hash); return true; } return false; } public function set_binary_output(bool $bin) { $this->binary = $bin; } public function set_url_encode(bool $url_encode) { $this->url_encode = $url_encode; } /** * OpenSSL Encrypt text with key * @param string $key password * @param string $text data * @return string encoded text */ public function encrypt(string $key, string $text, bool $validate = true): string { $key = ($validate) ? $this->get_valid_key($key) : $key; $ivsize = openssl_cipher_iv_length($this->method); $iv = random_bytes($ivsize); // Requires PHP 7 // Encryption key generated by PBKDF2 (since PHP 5.5) $keys = hash_pbkdf2($this->default_hash, $key, $iv, $this->_iterations, $this->_length, $this->_raw); $encKey = substr($keys, 0, $this->_key_bits); // X bit encryption key $hmacKey = substr($keys, $this->_key_bits); // X bit hmac key $ciphertext = openssl_encrypt( $text, $this->method, $encKey, OPENSSL_RAW_DATA, $iv ); $hmac = hash_hmac($this->default_hash, $iv . $ciphertext, $hmacKey); if (! $this->binary) { return (! $this->url_encode) ? base64_encode($hmac . $iv . $ciphertext) : px_base64url_encode($hmac . $iv . $ciphertext); } else { return $hmac . $iv . $ciphertext; } } /** * OpenSSL Decrypt data with key * @param string $key password * @param string $data encoded text * @return string plain text */ public function decrypt(string $key, string $data, bool $validate = true): string { $key = ($validate) ? $this->get_valid_key($key) : $key; if (! $this->binary) { $text = (! $this->url_encode) ? base64_decode($data) : px_base64url_decode($data); } else { $text = $data; } $hmac = substr($text, 0, $this->_length); $ivsize = openssl_cipher_iv_length($this->method); $iv = (! $this->binary) ? substr($text, $this->_length, $ivsize) : $ivsize; $ciphertext = substr($text, $ivsize + $this->_length); // Generate the encryption and hmac keys $keys = hash_pbkdf2($this->default_hash, $key, $iv, $this->_iterations, $this->_length, $this->_raw); $encKey = substr($keys, 0, $this->_key_bits); // X bit encryption key $hmacNew = hash_hmac($this->default_hash, $iv . $ciphertext, substr($keys, $this->_key_bits)); if (! hash_equals($hmac, $hmacNew)) { // to prevent timing attacks return 'FALSE'; // Note: hash_equals() requires PHP5.6+ } return openssl_decrypt( $ciphertext, $this->method, $encKey, OPENSSL_RAW_DATA, $iv ); } /** * Try to make sure you have a valid key * @param string $key as input * @return string new key * @throws Exception */ public function get_valid_key(string $key): string { $ivsize = openssl_cipher_iv_length($this->method); $key = substr($key, 0, $ivsize * 2); $keysize = strlen($key); if ($keysize != $ivsize * 2) { throw new \Exception('Unable to use ENC key: Bad key size!'); } if (! ctype_xdigit($key)) { throw new \Exception('Unable to use ENC key: None HEX Digits!'); } return bin2hex(pack('H*', $key)); } /** * Make a new Secure key * @return string key */ public function generate_valid_key(): string { $ivsize = openssl_cipher_iv_length($this->method); return bin2hex(random_bytes($ivsize)); } } /* * INSERT: INSERT INTO users (username, password) VALUES ('root', AES_ENCRYPT('somepassword', 'key12346123')); and SELECT: SELECT AES_DECRYPT(password, 'key12346123') AS pwd FROM users WHERE username = 'root'; Also, this requires SSL connection to the database. */