1 <?php
  2 /**
  3  * @package     Joomla.Platform
  4  * @subpackage  Crypt
  5  *
  6  * @copyright   Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
  7  * @license     GNU General Public License version 2 or later; see LICENSE
  8  */
  9 
 10 defined('JPATH_PLATFORM') or die;
 11 
 12 /**
 13  * JCrypt cipher for mcrypt algorithm encryption, decryption and key generation.
 14  *
 15  * @since       12.1
 16  * @deprecated  4.0   Without replacment use JCryptCipherCrypto
 17  */
 18 abstract class JCryptCipherMcrypt implements JCryptCipher
 19 {
 20     /**
 21      * @var    integer  The mcrypt cipher constant.
 22      * @link   https://secure.php.net/manual/en/mcrypt.ciphers.php
 23      * @since  12.1
 24      */
 25     protected $type;
 26 
 27     /**
 28      * @var    integer  The mcrypt block cipher mode.
 29      * @link   https://secure.php.net/manual/en/mcrypt.constants.php
 30      * @since  12.1
 31      */
 32     protected $mode;
 33 
 34     /**
 35      * @var    string  The JCrypt key type for validation.
 36      * @since  12.1
 37      */
 38     protected $keyType;
 39 
 40     /**
 41      * Constructor.
 42      *
 43      * @since   12.1
 44      * @throws  RuntimeException
 45      */
 46     public function __construct()
 47     {
 48         if (!is_callable('mcrypt_encrypt'))
 49         {
 50             throw new RuntimeException('The mcrypt extension is not available.');
 51         }
 52     }
 53 
 54     /**
 55      * Method to decrypt a data string.
 56      *
 57      * @param   string     $data  The encrypted string to decrypt.
 58      * @param   JCryptKey  $key   The key object to use for decryption.
 59      *
 60      * @return  string  The decrypted data string.
 61      *
 62      * @since   12.1
 63      * @throws  InvalidArgumentException
 64      */
 65     public function decrypt($data, JCryptKey $key)
 66     {
 67         // Validate key.
 68         if ($key->type != $this->keyType)
 69         {
 70             throw new InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected ' . $this->keyType . '.');
 71         }
 72 
 73         // Decrypt the data.
 74         $decrypted = trim(mcrypt_decrypt($this->type, $key->private, $data, $this->mode, $key->public));
 75 
 76         return $decrypted;
 77     }
 78 
 79     /**
 80      * Method to encrypt a data string.
 81      *
 82      * @param   string     $data  The data string to encrypt.
 83      * @param   JCryptKey  $key   The key object to use for encryption.
 84      *
 85      * @return  string  The encrypted data string.
 86      *
 87      * @since   12.1
 88      * @throws  InvalidArgumentException
 89      */
 90     public function encrypt($data, JCryptKey $key)
 91     {
 92         // Validate key.
 93         if ($key->type != $this->keyType)
 94         {
 95             throw new InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected ' . $this->keyType . '.');
 96         }
 97 
 98         // Encrypt the data.
 99         $encrypted = mcrypt_encrypt($this->type, $key->private, $data, $this->mode, $key->public);
100 
101         return $encrypted;
102     }
103 
104     /**
105      * Method to generate a new encryption key object.
106      *
107      * @param   array  $options  Key generation options.
108      *
109      * @return  JCryptKey
110      *
111      * @since   12.1
112      * @throws  InvalidArgumentException
113      */
114     public function generateKey(array $options = array())
115     {
116         // Create the new encryption key object.
117         $key = new JCryptKey($this->keyType);
118 
119         // Generate an initialisation vector based on the algorithm.
120         $key->public = mcrypt_create_iv(mcrypt_get_iv_size($this->type, $this->mode), MCRYPT_DEV_URANDOM);
121 
122         // Get the salt and password setup.
123         $salt = (isset($options['salt'])) ? $options['salt'] : substr(pack('h*', md5(JCrypt::genRandomBytes())), 0, 16);
124 
125         if (!isset($options['password']))
126         {
127             throw new InvalidArgumentException('Password is not set.');
128         }
129 
130         // Generate the derived key.
131         $key->private = $this->pbkdf2($options['password'], $salt, mcrypt_get_key_size($this->type, $this->mode));
132 
133         return $key;
134     }
135 
136     /**
137      * PBKDF2 Implementation for deriving keys.
138      *
139      * @param   string   $p   Password
140      * @param   string   $s   Salt
141      * @param   integer  $kl  Key length
142      * @param   integer  $c   Iteration count
143      * @param   string   $a   Hash algorithm
144      *
145      * @return  string  The derived key.
146      *
147      * @link    https://en.wikipedia.org/wiki/PBKDF2
148      * @link    http://www.ietf.org/rfc/rfc2898.txt
149      * @since   12.1
150      */
151     public function pbkdf2($p, $s, $kl, $c = 10000, $a = 'sha256')
152     {
153         // Hash length.
154         $hl = strlen(hash($a, null, true));
155 
156         // Key blocks to compute.
157         $kb = ceil($kl / $hl);
158 
159         // Derived key.
160         $dk = '';
161 
162         // Create the key.
163         for ($block = 1; $block <= $kb; $block++)
164         {
165             // Initial hash for this block.
166             $ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true);
167 
168             // Perform block iterations.
169             for ($i = 1; $i < $c; $i++)
170             {
171                 $ib ^= ($b = hash_hmac($a, $b, $p, true));
172             }
173 
174             // Append the iterated block.
175             $dk .= $ib;
176         }
177 
178         // Return derived key of correct length.
179         return substr($dk, 0, $kl);
180     }
181 }
182