<?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\Encoder;

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

/**
 * Class Index
 * @package LifeStyle\Tools\CipheringBundle\Api\Ciphering\Encoder
 */
class Index
{
    /**
     * @var Configuration\Index
     */
    protected $cypherConfiguration;

    /**
     * @var Configuration\EncryptionDetector
     */
    private $encryptionDetector;

    /**
     * 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 $decryptedString
     * @param string $encryptKey
     * @return string
     */
    public function encode($decryptedString, $encryptKey)
    {
        // Combine static and flexible keys
        $mixedKey = $this->cypherConfiguration->keyMixer($encryptKey);
        $key = substr(hash('sha256', $mixedKey), 0, 32);

        // Serialize data to encrypt
        $encrypt = serialize($decryptedString);

        return $this->encryptionDetector->useOpenSsl() ?
            $this->encryptOpenSsl($encrypt, $key) :
            $this->encryptMcrypt($encrypt, $key);
    }

    /**
     * Encrypt message with open ssl
     *
     * As we are using GCM the cipher should be one of these
     * - aes-128-gcm
     * - aes-192-gcm
     * - aes-256-gcm
     * - id-aes128-GCM
     * - id-aes192-GCM
     * - id-aes256-GCM
     * Make sure the cypher is supported by your installation!
     *
     * @param string $encrypt
     * @param string $key
     * @return string
     */
    private function encryptOpenSsl($encrypt, $key)
    {
        // Key should be in hex format
        $key = pack('H*', $key);

        $openSslCipherName = $this->encryptionDetector->getOpenSslCypherName();
        $openSslIV = openssl_random_pseudo_bytes(openssl_cipher_iv_length($openSslCipherName));

        if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
            $cypherText = openssl_encrypt($encrypt, $openSslCipherName, $key, 0, $openSslIV, $openSslTag);
            return implode('#|#', [base64_encode($cypherText), base64_encode($openSslIV), base64_encode($openSslTag)]);
        }

        $cypherText = openssl_encrypt($encrypt, $openSslCipherName, $key, 0, $openSslIV);
        return implode('#|#', [base64_encode($cypherText), base64_encode($openSslIV)]);
    }

    /**
     * @param string $encrypt
     * @param string $key
     * @return string
     */
    function encryptMcrypt($encrypt, $key)
    {
        //pack the key
        $key = pack('H*', $key);

        $mac = hash_hmac('sha256', $encrypt, substr(bin2hex($key), -32));

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

        $cypherText = mcrypt_encrypt($cypherName, $key, $encrypt . $mac, $modeName, $iv);
        return implode('#|#', [base64_encode($cypherText), base64_encode($iv)]);
    }
}
