<?php namespace HashOver;

// Copyright (C) 2010-2019 Jacob Barkdull
// This file is part of HashOver.
//
// HashOver is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// HashOver is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with HashOver.  If not, see <http://www.gnu.org/licenses/>.


// Encryption methods
class Crypto extends Secrets
{
	protected $prefix;
	protected $cost = '$10$';
	protected $encryptionHash;
	protected $ivSize;
	protected $cipher = 'aes-128-cbc';
	protected $options;
	protected $alphabet = 'aAbBcCdDeEfFgGhHiIjJkKlLmM.nNoOpPqQrRsStTuUvVwWxXyYzZ/0123456789';

	public function __construct ()
	{
		// Throw exception if encryption key isn't at least 8 characters long
		if (mb_strlen ($this->encryptionKey, '8bit') < 8) {
			throw new \Exception (
				'Encryption key must by at least 8 characters long.'
			);
		}

		// Blowfish prefix
		$this->prefix = (version_compare (PHP_VERSION, '5.3.7') < 0) ? '$2a' : '$2y';

		// OpenSSL raw output/options
		$this->options = defined ('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true;

		// SHA-256 hash array
		$this->encryptionHash = str_split (hash ('sha256', $this->encryptionKey));

		// OpenSSL cipher IV
		$this->ivSize = openssl_cipher_iv_length ($this->cipher);
	}

	// Creates Blowfish hash for passwords
	public function createHash ($string)
	{
		// Alphanumeric character array
		$alphabet = str_split ($this->alphabet);

		// Shuffle alphanumeric character array as to randomize it
		shuffle ($alphabet);

		// Initial salt
		$salt = '';

		// Generate random 20 character alphanumeric string
		foreach (array_rand ($alphabet, 20) as $character) {
			$salt .= $alphabet[$character];
		}

		// Blowfish hash
		$hash = crypt ($string, $this->prefix . $this->cost . $salt . '$$');

		// Return hashed string
		return $hash;
	}

	// Creates Blowfish hash with salt from supplied hash
	public function verifyHash ($string, $compare)
	{
		// Split string by dollar sign
		$parts = explode ('$', $compare);

		// Hash salt
		$salt = !empty ($parts[3]) ? $parts[3] : '';

		// Encryption string as Blowfish hash
		$hash = crypt ($string, $this->prefix . $this->cost . $salt . '$$');

		// Returns true if both match
		return ($hash === $compare);
	}

	// Generates a random encryption key
	protected function createKey ($string)
	{
		// Shuffle alphanumeric character array as to randomize it
		shuffle ($string);

		// Initial array keys
		$keys = array ();

		// Initial key
		$encryption_key = '';

		// Generate random string from encryption key SHA-256 hash
		for ($k = 0; $k < 16; $k++) {
			// Add encryption key character index to keys array
			$keys[] = array_search ($string[$k], $this->encryptionHash);

			// Add encryption key character to key
			$encryption_key .= $string[$k];
		}

		// Convert encryption hash array keys to string
		$list = join (',', $keys);

		// Return encryption key info
		return array (
			// Randomly generated encryption key
			'key' => $encryption_key,

			// List of encryption hash array keys
			'keys' => $list
		);
	}

	// OpenSSL encrypt with random key from SHA-256 hash for e-mails
	public function encrypt ($string)
	{
		// Get a random encryption key
		$key_pair = $this->createKey ($this->encryptionHash);

		// Get pseudo-random bytes for OpenSSL IV
		$iv = openssl_random_pseudo_bytes ($this->ivSize);

		// OpenSSL encrypt using random encryption key
		$encrypted = openssl_encrypt (
			// String being encrypted
			$string,

			// Encryption cipher method
			$this->cipher,

			// Generated encryption key
			$key_pair['key'],

			// OpenSSL options
			$this->options,

			// Initialization vector
			$iv
		);

		// Encode encrypted text as base64
		$encoded = base64_encode ($iv . $encrypted);

		// Return encryption info
		return array (
			// Encrypted string
			'encrypted' => $encoded,

			// List of encryption hash array keys
			'keys' => $key_pair['keys']
		);
	}

	// Decrypt OpenSSL encrypted string
	public function decrypt ($string, $keys)
	{
		// Return false if string or keys is empty
		if (empty ($string) or empty ($keys)) {
			return false;
		}

		// Initial key
		$decryption_key = '';

		// Split keys string into array
		$keys = explode (',', $keys);

		// Retrieve random key from array
		foreach ($keys as $value) {
			// Cast key to integer
			$hash_key = (int)($value);

			// Check if encryption hash key exists
			if (isset ($this->encryptionHash[$hash_key])) {
				// If so, add character to decryption key
				$decryption_key .= $this->encryptionHash[$hash_key];
			} else {
				// If not, give up and return false
				return false;
			}
		}

		// Decode base64 encoded string
		$decoded = base64_decode ($string, true);

		// Get length of decoded string
		$length = mb_strlen ($decoded, '8bit');

		// Get decipher text from decoded string
		$decrypted = mb_substr ($decoded, $this->ivSize, $length, '8bit');

		// Setup OpenSSL IV
		$iv = mb_substr ($decoded, 0, $this->ivSize, '8bit');

		// Return OpenSSL decrypted string
		return openssl_decrypt (
			// String being decrypted
			$decrypted,

			// Encryption decipher method
			$this->cipher,

			// Retrieved decryption key
			$decryption_key,

			// OpenSSL options
			$this->options,

			// Initialization vector
			$iv
		);
	}
}