Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  /**
   3   * Provides an API for encrypting and decrypting small pieces of data with the
   4   * use of a shared key stored in a cookie.
   5   *
   6   * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
   7   *
   8   * See the enclosed file LICENSE for license information (LGPL). If you
   9   * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  10   *
  11   * @author   Chuck Hagenbuch <chuck@horde.org>
  12   * @author   Michael Slusarz <slusarz@horde.org>
  13   * @category Horde
  14   * @license  http://www.horde.org/licenses/lgpl21 LGPL
  15   * @package  Secret
  16   */
  17  class Horde_Secret
  18  {
  19      /** Generic, default keyname. */
  20      const DEFAULT_KEY = 'generic';
  21  
  22      /**
  23       * Configuration parameters.
  24       *
  25       * @var array
  26       */
  27      protected $_params = array(
  28          'cookie_domain' => '',
  29          'cookie_path' => '',
  30          'cookie_ssl' => false,
  31          'session_name' => 'horde_secret'
  32      );
  33  
  34      /**
  35       * Cipher cache.
  36       *
  37       * @var array
  38       */
  39      protected $_cipherCache = array();
  40  
  41      /**
  42       * Key cache.
  43       *
  44       * @var array
  45       */
  46      protected $_keyCache = array();
  47  
  48      /**
  49       * Constructor.
  50       *
  51       * @param array $params  Configuration parameters:
  52       *   - cookie_domain: (string) The cookie domain.
  53       *   - cookie_path: (string) The cookie path.
  54       *   - cookie_ssl: (boolean) Only transmit cookie securely?
  55       *   - session_name: (string) The cookie session name.
  56       */
  57      public function __construct($params = array())
  58      {
  59          $this->_params = array_merge($this->_params, $params);
  60      }
  61  
  62      /**
  63       * Take a small piece of data and encrypt it with a key.
  64       *
  65       * @param string $key      The key to use for encryption.
  66       * @param string $message  The plaintext message.
  67       *
  68       * @return string  The ciphertext message.
  69       * @throws Horde_Secret_Exception
  70       */
  71      public function write($key, $message)
  72      {
  73          $message = strval($message);
  74          return (strlen($key) && strlen($message))
  75              ? $this->_getCipherOb($key)->encrypt($message)
  76              : '';
  77      }
  78  
  79      /**
  80       * Decrypt a message encrypted with write().
  81       *
  82       * @param string $key      The key to use for decryption.
  83       * @param string $message  The ciphertext message.
  84       *
  85       * @return string  The plaintext message.
  86       * @throws Horde_Secret_Exception
  87       */
  88      public function read($key, $ciphertext)
  89      {
  90          $ciphertext = strval($ciphertext);
  91          return (strlen($key) && strlen($ciphertext))
  92              ? $this->_getCipherOb($key)->decrypt($ciphertext)
  93              : '';
  94      }
  95  
  96      /**
  97       * Returns the cached crypt object.
  98       *
  99       * @param string $key  The key to use for [de|en]cryption. Only the first
 100       *                     56 bytes of this string is used.
 101       *
 102       * @return Horde_Crypt_Blowfish  The crypt object.
 103       * @throws Horde_Secret_Exception
 104       */
 105      protected function _getCipherOb($key)
 106      {
 107          if (!is_string($key)) {
 108              throw new Horde_Secret_Exception('Key must be a string', Horde_Secret_Exception::KEY_NOT_STRING);
 109          }
 110  
 111          if (!strlen($key)) {
 112              throw new Horde_Secret_Exception('Key must be non-zero.', Horde_Secret_Exception::KEY_ZERO_LENGTH);
 113          }
 114  
 115          $key = substr($key, 0, 56);
 116  
 117          $idx = hash('md5', $key);
 118          if (!isset($this->_cipherCache[$idx])) {
 119              $this->_cipherCache[$idx] = new Horde_Crypt_Blowfish($key);
 120          }
 121  
 122          return $this->_cipherCache[$idx];
 123      }
 124  
 125      /**
 126       * Generate a secret key (for encryption), either using a random
 127       * string and storing it in a cookie if the user has cookies
 128       * enabled, or munging some known values if they don't.
 129       *
 130       * @param string $keyname  The name of the key to set.
 131       *
 132       * @return string  The secret key that has been generated.
 133       */
 134      public function setKey($keyname = self::DEFAULT_KEY)
 135      {
 136          $set = true;
 137  
 138          if (isset($_COOKIE[$this->_params['session_name']])) {
 139              if (isset($_COOKIE[$keyname . '_key'])) {
 140                  $key = $_COOKIE[$keyname . '_key'];
 141                  $set = false;
 142              } else {
 143                  $key = $_COOKIE[$keyname . '_key'] = strval(new Horde_Support_Randomid());
 144              }
 145          } else {
 146              $key = session_id();
 147          }
 148  
 149          if ($set) {
 150              $this->_setCookie($keyname, $key);
 151          }
 152  
 153          return $key;
 154      }
 155  
 156      /**
 157       * Return a secret key, either from a cookie, or if the cookie
 158       * isn't there, assume we are using a munged version of a known
 159       * base value.
 160       *
 161       * @param string $keyname  The name of the key to get.
 162       *
 163       * @return string  The secret key.
 164       */
 165      public function getKey($keyname = self::DEFAULT_KEY)
 166      {
 167          if (!isset($this->_keyCache[$keyname])) {
 168              if (isset($_COOKIE[$keyname . '_key'])) {
 169                  $key = $_COOKIE[$keyname . '_key'];
 170              } else {
 171                  $key = session_id();
 172                  $this->_setCookie($keyname, $key);
 173              }
 174  
 175              $this->_keyCache[$keyname] = $key;
 176          }
 177  
 178          return $this->_keyCache[$keyname];
 179      }
 180  
 181      /**
 182       * Clears a secret key entry from the current cookie.
 183       *
 184       * @param string $keyname  The name of the key to clear.
 185       *
 186       * @return boolean  True if key existed, false if not.
 187       */
 188      public function clearKey($keyname = self::DEFAULT_KEY)
 189      {
 190          if (isset($_COOKIE[$this->_params['session_name']]) &&
 191              isset($_COOKIE[$keyname . '_key'])) {
 192              $this->_setCookie($keyname, false);
 193              return true;
 194          }
 195  
 196          return false;
 197      }
 198  
 199      /**
 200       * Sets the cookie with the given keyname/key.
 201       *
 202       * @param string $keyname  The name of the key to set.
 203       * @param string $key      The key to use for encryption.
 204       */
 205      protected function _setCookie($keyname, $key)
 206      {
 207          @setcookie(
 208              $keyname . '_key',
 209              $key,
 210              0,
 211              $this->_params['cookie_path'],
 212              $this->_params['cookie_domain'],
 213              $this->_params['cookie_ssl'],
 214              true
 215          );
 216  
 217          if ($key === false) {
 218              unset($_COOKIE[$keyname . '_key'], $this->_keyCache[$keyname]);
 219          } else {
 220              $_COOKIE[$keyname . '_key'] = $this->_keyCache[$keyname] = $key;
 221          }
 222      }
 223  
 224  }