<?php
namespace DynCom\mysyde\common\classes;
/**
 * A class to handle secure encryption and decryption of arbitrary data
 *
 * Note that this is not just straight encryption.  It also has a few other
 *  features in it to make the encrypted data far more secure.  Note that any
 *  other implementations used to decrypt data will have to do the same exact
 *  operations.
 *
 * Security Benefits:
 *
 * - Uses Key stretching
 * - Hides the Initialization Vector
 * - Does HMAC verification of source data
 *
 */
class Encryption
{

    /**
     * @var string $cipher The mcrypt cipher to use for this instance
     */
    protected $cipher = '';

    /**
     * @var int $mode The mcrypt cipher mode to use
     */
    protected $mode = '';

    /**
     * @var int $rounds The number of rounds to feed into PBKDF2 for key generation
     */
    protected $rounds = 100;

    /**
     * Constructor!
     *
     * @param string $cipher The MCRYPT_* cypher to use for this instance
     * @param int $mode The MCRYPT_MODE_* mode to use for this instance
     * @param int $rounds The number of PBKDF2 rounds to do on the key
     */
    public function __construct($cipher, $mode, $rounds = 100)
    {
        $this->cipher = $cipher;
        $this->mode = $mode;
        $this->rounds = (int)$rounds;
    }

    /**
     * Decrypt the data with the provided key
     *
     * @param string $data The encrypted datat to decrypt
     * @param string $key The key to use for decryption
     *
     * @returns string|false The returned string if decryption is successful
     *                           false if it is not
     */
    // public function decrypt($data, $key)
    // {
    //     $salt = substr($data, 0, 128);
    //     $enc = substr($data, 128, -64);
    //     $mac = substr($data, -64);

    //     list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

    //     if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
    //         return false;
    //     }

    //     $dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);

    //     $data = $this->unpad($dec);

    //     return $data;
    // }

    /**
     * Encrypt the supplied data using the supplied key
     *
     * @param string $data The data to encrypt
     * @param string $key The key to encrypt with
     *
     * @returns string The encrypted data
     */
    // public function encrypt($data, $key)
    // {
    //     $salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
    //     list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

    //     $data = $this->pad($data);

    //     $enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);

    //     $mac = hash_hmac('sha512', $enc, $macKey, true);
    //     return $salt . $enc . $mac;
    // }

    /**
     * Generates a set of keys given a random salt and a master key
     *
     * @param string $salt A random string to change the keys each encryption
     * @param string $key The supplied key to encrypt with
     *
     * @returns array An array of keys (a cipher key, a mac key, and a IV)
     */
    protected function getKeys($salt, $key)
    {
        // $ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
        // $keySize = mcrypt_get_key_size($this->cipher, $this->mode);
        $length = 2 * $keySize + $ivSize;

        $key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);

        $cipherKey = substr($key, 0, $keySize);
        $macKey = substr($key, $keySize, $keySize);
        $iv = substr($key, 2 * $keySize);
        return array($cipherKey, $macKey, $iv);
    }

    /**
     * Stretch the key using the PBKDF2 algorithm
     *
     * @see http://en.wikipedia.org/wiki/PBKDF2
     *
     * @param string $algo The algorithm to use
     * @param string $key The key to stretch
     * @param string $salt A random salt
     * @param int $rounds The number of rounds to derive
     * @param int $length The length of the output key
     *
     * @returns string The derived key.
     */
    protected function pbkdf2($algo, $key, $salt, $rounds, $length)
    {
        $size = strlen(hash($algo, '', true));
        $len = ceil($length / $size);
        $result = '';
        for ($i = 1; $i <= $len; $i++) {
            $tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
            $res = $tmp;
            for ($j = 1; $j < $rounds; $j++) {
                $tmp = hash_hmac($algo, $tmp, $key, true);
                $res ^= $tmp;
            }
            $result .= $res;
        }
        return substr($result, 0, $length);
    }

    /**
     * @param $data
     * @return string
     */
    protected function pad($data)
    {
        // $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $padAmount = $length - strlen($data) % $length;
        if ($padAmount == 0) {
            $padAmount = $length;
        }
        return $data . str_repeat(chr($padAmount), $padAmount);
    }

    /**
     * @param $data
     * @return bool|string
     */
    protected function unpad($data)
    {
        // $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $last = ord($data[strlen($data) - 1]);
        if ($last > $length) return false;
        if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
            return false;
        }
        return substr($data, 0, -1 * $last);
    }

    /**
     * @param $a
     * @param $b
     * @return bool
     */
    function hash_equals($a, $b) {
        // $key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
        return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
    }
}