<?php
namespace ModulesAbstract\ElectronicInvoice\Encryption;

/**
 * Encryption class that uses OpenSSL
 * https://www.php.net/manual/en/book.openssl.php
 */
class OpenSSL extends EncryptionAbstract
{
    /**
     * Cipher algorithm. Default is 'aes-128-cbc', as it's the first result of  openssl_get_cipher_methods();
     * @see https://www.php.net/manual/en/function.openssl-get-cipher-methods.php
     *
     * @var string
     */
    protected $cipher_algo = null;

    /**
     * Passphrase used
     * @see https://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php
     *
     * @var string
     */
    protected $passphrase = null;

    /**
     * Initialization Vector
     * @see https://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php
     *
     * @var string
     */
    protected $iv = null;

    public function destroy()
    {
        // this is sensitive data. Make sure it's removed from memory
        $this->cipher_algo = null;
        $this->passphrase = null;
        $this->iv = null;
        return parent::destroy();
    }

    /**
     * Set the cipher algorithm
     * @see https://www.php.net/manual/en/function.openssl-get-cipher-methods.php
     *
     * @param string $cipher_algo
     * @return \ModulesAbstract\ElectronicInvoice\Encryption\OpenSSL
     */
    public function setCipherAlgo($cipher_algo)
    {
        $this->cipher_algo = $cipher_algo;

        return $this;
    }

    /**
     * Set the used passphrase
     *
     * @param string $passphrase
     * @return \ModulesAbstract\ElectronicInvoice\Encryption\OpenSSL
     */
    public function setPassphrase($passphrase)
    {
        $this->passphrase = $passphrase;

        return $this;
    }

    /**
     * Set the initialization vector
     *
     * @param string $iv
     * @return \ModulesAbstract\ElectronicInvoice\Encryption\OpenSSL
     */
    public function setIV($iv)
    {
        $this->iv = $iv;

        return $this;
    }

    /**
     * Do the actual encryption
     *
     * @param string $data
     * @return string, array
     */
    public function encrypt($data)
    {
        $cipher_algo = ($this->cipher_algo!==null?$this->cipher_algo:reset(openssl_get_cipher_methods()));
        $ivlen = openssl_cipher_iv_length($cipher_algo);

        $passphrase =   ($this->passphrase!==null?$this->passphrase:openssl_random_pseudo_bytes($ivlen));
        $iv =           ($this->iv!==null?$this->iv:openssl_random_pseudo_bytes($ivlen));

        $encryptedData = openssl_encrypt($data, $this->cipher_algo, $passphrase, OPENSSL_RAW_DATA, $iv);

        return [$encryptedData, [
            'cipher_algo' =>    $cipher_algo,
            'passphrase' =>     $passphrase,
            'iv' =>             $iv,
        ]];
    }

    /**
     * Do the actual decryption
     *
     * @param string $encryptedData
     * @param array $params
     * @return string
     */
    public function decrypt($encryptedData, $params = [])
    {
        if (!$encryptedData) {
            throw new \RuntimeException(sprintf("Cannot decrypt provided data. No data provided"));
        }

        $data = openssl_decrypt($encryptedData,
            ($params['cipher_algo']!==null?$params['cipher_algo']:$this->cipher_algo),
            ($params['passphrase']!==null?$params['passphrase']:$this->passphrase),
            OPENSSL_RAW_DATA,
            ($params['iv']!==null?$params['iv']:$this->iv)
        );
        if ($data===false) {
            throw new \RuntimeException(sprintf("Cannot decrypt provided data. Data length: %d. Please also check provided cipher_algo,passphrase,iv", strlen($encryptedData)));
        }

        return $data;
    }

}
