<?php if (!defined('PmWiki')) exit();
/*  Copyright 2019-2026 Petko Yotov www.pmwiki.org/petko
    This file is part of PmWiki; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published
    by the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.  See pmwiki.php for full details.

    This class allows to encrypt and decrypt data. It is not included 
    by default, recipes that require it can enable it with such a line:
    
    include_once("$FarmD/scripts/pmcrypto.php");
*/

class PmCrypto {
  protected $key;
  protected $cipher = 'aes-256-cbc';
  protected $ivlen;
  protected $auth = true;
  protected $base64 = true;
  
  public function __construct($key, $options = null) {
    $this->key = $key;
    if (is_string($options)) $this->cipher = $options;
    elseif (is_array($options)) {
      foreach ($options as $k => $v) {
        if (property_exists($this, $k)) $this->$k = $v;
      }
    }
    $this->ivlen = openssl_cipher_iv_length($this->cipher);
  }

  public function encrypt($plaintext, $keypattern = null) {
    if (is_array($plaintext)) {
      $ciphertext = [];
      foreach ($plaintext as $k=>$v) {
        if(!$keypattern || MatchNames($k, $keypattern, false))
          $ciphertext[$k] = $this->encrypt($v);
        else $ciphertext[$k] = $v;
      }
      return $ciphertext;
    }
    $iv = openssl_random_pseudo_bytes($this->ivlen);
    $ciphertext = openssl_encrypt($plaintext, $this->cipher, $this->key, OPENSSL_RAW_DATA, $iv);
    $hmac = $this->auth ? hash_hmac('sha256', $iv . $ciphertext, $this->key, true): '';
    $out = $iv . $hmac . $ciphertext;
    return $this->base64 ? base64_encode($out) : $out;
  }

  public function decrypt($data, $keypattern = null) {
    if (is_array($data)) {
      $plaintext = [];
      foreach ($data as $k=>$v) {
        if(!$keypattern || MatchNames($k, $keypattern, false))
          $plaintext[$k] = $this->decrypt($v);
        else $plaintext[$k] = $v;
      }
      return $plaintext;
    }
    if ($this->base64) $data = base64_decode($data);
    $iv = substr($data, 0, $this->ivlen);
    if ($this->auth) {
      $hmac_received = substr($data, $this->ivlen, 32);
      $ciphertext = substr($data, $this->ivlen+32);
      $hmac_calculated = hash_hmac('sha256', $iv . $ciphertext, $this->key, true);
    
      if (!hash_equals($hmac_received, $hmac_calculated)) return false;
    }
    else $ciphertext = substr($data, $this->ivlen);
    return openssl_decrypt($ciphertext, $this->cipher, $this->key, OPENSSL_RAW_DATA, $iv);
  }
  
  public function __debugInfo() {
    return [
      'key' => '[hidden]',
      'cipher' => $this->cipher,
      'ivlen' => $this->ivlen,
      'auth' => $this->auth,
      'base64' => $this->base64,
    ];
  }

  public function __toString() {
    return print_r($this->__debugInfo(), true);
  }
  
}
