<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2018 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace LifeStyle\Tools\CipheringBundle\Api\Ciphering\Decoder;

use LifeStyle\Tools\CipheringBundle\Api\Ciphering\Configuration\Index as CypherConfig;

use LifeStyle\Tools\CipheringBundle\Api\Ciphering\Configuration;

/**
 * Class Index
 * @package LifeStyle\Tools\CipheringBundle\Api\Ciphering\Decoder
 */
class Index
{
    /**
     * @var Configuration\EncryptionDetector
     */
    private $encryptionDetector;

    /**
     * @var Configuration\Index
     */
    protected $cypherConfiguration;

    /**
     * Index constructor.
     * @param Configuration\EncryptionDetector $encryptionDetector
     * @param Configuration\Index $cypherConfiguration
     */
    public function __construct(
        Configuration\EncryptionDetector $encryptionDetector,
        Configuration\Index $cypherConfiguration
    ) {
        $this->encryptionDetector = $encryptionDetector;
        $this->cypherConfiguration = $cypherConfiguration;
    }

    /**
     * @param string $data
     * @param string $key
     * @return string|false
     */
    public function decode($data, $key)
    {
        return $this->encryptionDetector->useOpenSsl() ?
            $this->openSslDecode($data, $key) :
            $this->mcryptDecode($data, $key);
    }

    /**
     * @param string $data
     * @param string $key
     * @return bool|string
     */
    private function mcryptDecode($data, $key)
    {
        //combine static and flexible keys
        $mixedKey = $this->cypherConfiguration->keyMixer($key);
        $key = substr(hash('sha256', $mixedKey), 0, 32);

        // split data
        $decrypt = explode('#|#', $data . '#|#');
        $decoded = base64_decode($decrypt[0]);
        $iv = base64_decode($decrypt[1]);

        $cypherName = constant($this->cypherConfiguration->getMcryptCipherName());
        $modeName = constant($this->cypherConfiguration->getMcryptModeName());

        if (strlen($iv) !== mcrypt_get_iv_size($cypherName, $modeName)) {
            return false;
        }

        // pack key
        $key = pack('H*', $key);
        $decrypted = trim(mcrypt_decrypt($cypherName, $key, $decoded, $modeName, $iv));
        $mac = substr($decrypted, -64);
        $decrypted = substr($decrypted, 0, -64);
        $calcmac = hash_hmac('sha256', $decrypted, substr(bin2hex($key), -32));
        if ($calcmac !== $mac) {
            return false;
        }

        return unserialize($decrypted);
    }

    /**
     * @param string $data
     * @param string $key
     * @return bool|string
     */
    private function openSslDecode($data, $key)
    {
        //combine static and flexible keys
        $mixedKey = $this->cypherConfiguration->keyMixer($key);
        $key = substr(hash('sha256', $mixedKey), 0, 32);

        // split data
        $decrypt = explode('#|#', $data . '#|##|#');
        $ciphertext = base64_decode($decrypt[0]);
        $openSslIV = base64_decode($decrypt[1]);
        $openSslTag = base64_decode($decrypt[2]);

        // Key should be in hex format
        $key = pack('H*', $key);

        $openSslCypherName = $this->encryptionDetector->getOpenSslCypherName();

        if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
            $decrypted = openssl_decrypt($ciphertext, $openSslCypherName, $key, 0, $openSslIV, $openSslTag);
        } else {
            $decrypted = openssl_decrypt($ciphertext, $openSslCypherName, $key, 0, $openSslIV);
        }

        if (false === $decrypted) {
            return false;
        }

        return unserialize($decrypted);
    }
}
