The TryingToScale PHP framework.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

245 lines
7.8 KiB

<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @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.
*/