Openssl.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. /**
  3. * The Encrypt Openssl engine provides two-way encryption of text and binary strings
  4. * using the [OpenSSL](http://php.net/openssl) extension, which consists of two
  5. * parts: the key and the cipher.
  6. *
  7. * The Key
  8. * : A secret passphrase that is used for encoding and decoding
  9. *
  10. * The Cipher
  11. * : A [cipher](http://php.net/manual/en/openssl.ciphers.php) determines how the encryption
  12. * is mathematically calculated. By default, the "AES-256-CBC" cipher
  13. * is used.
  14. *
  15. *
  16. * @package Kohana/Encrypt
  17. * @author Kohana Team
  18. * @copyright (c) Kohana Team
  19. * @license https://koseven.ga/LICENSE.md
  20. */
  21. class Kohana_Encrypt_Engine_Openssl extends Kohana_Encrypt_Engine {
  22. /**
  23. * @var int the size of the Initialization Vector (IV) in bytes
  24. */
  25. protected $_iv_size;
  26. /**
  27. * Creates a new openssl wrapper.
  28. *
  29. * @param string $key encryption key
  30. * @param string $mode openssl mode
  31. * @param string $cipher openssl cipher
  32. */
  33. public function __construct($key_config, $mode = NULL, $cipher = NULL)
  34. {
  35. if ($cipher === NULL)
  36. {
  37. // Add the default cipher
  38. $cipher = 'AES-256-CBC';
  39. }
  40. parent::__construct($key_config, $mode, $cipher);
  41. $this->_iv_size = openssl_cipher_iv_length($this->_cipher);
  42. $length = mb_strlen($this->_key, '8bit');
  43. // Validate configuration
  44. if ($this->_cipher === 'AES-128-CBC')
  45. {
  46. if ($length !== 16)
  47. {
  48. // No valid encryption key is provided!
  49. throw new Kohana_Exception('No valid encryption key is defined in the encryption configuration: length should be 16 for AES-128-CBC');
  50. }
  51. }
  52. elseif ($this->_cipher === 'AES-256-CBC')
  53. {
  54. if ($length !== 32)
  55. {
  56. // No valid encryption key is provided!
  57. throw new Kohana_Exception('No valid encryption key is defined in the encryption configuration: length should be 32 for AES-256-CBC');
  58. }
  59. }
  60. else
  61. {
  62. // No valid encryption cipher is provided!
  63. throw new Kohana_Exception('No valid encryption cipher is defined in the encryption configuration.');
  64. }
  65. }
  66. /**
  67. * Encrypts a string and returns an encrypted string that can be decoded.
  68. *
  69. * @param string $data data to be encrypted
  70. * @return string
  71. */
  72. public function encrypt($data, $iv)
  73. {
  74. // First we will encrypt the value using OpenSSL. After this is encrypted we
  75. // will proceed to calculating a MAC for the encrypted value so that this
  76. // value can be verified later as not having been changed by the users.
  77. $value = \openssl_encrypt($data, $this->_cipher, $this->_key, 0, $iv);
  78. if ($value === FALSE)
  79. {
  80. // Encryption failed
  81. return FALSE;
  82. }
  83. // Once we have the encrypted value we will go ahead base64_encode the input
  84. // vector and create the MAC for the encrypted value so we can verify its
  85. // authenticity. Then, we'll JSON encode the data in a "payload" array.
  86. $mac = $this->hash($iv = base64_encode($iv), $value);
  87. $json = json_encode(compact('iv', 'value', 'mac'));
  88. if (! is_string($json))
  89. {
  90. // Encryption failed
  91. return FALSE;
  92. }
  93. return base64_encode($json);
  94. }
  95. /**
  96. * Decrypts an encoded string back to its original value.
  97. *
  98. * @param string $data encoded string to be decrypted
  99. * @return FALSE if decryption fails
  100. * @return string
  101. */
  102. public function decrypt($data)
  103. {
  104. // Convert the data back to binary
  105. $data = json_decode(base64_decode($data), TRUE);
  106. // If the payload is not valid JSON or does not have the proper keys set we will
  107. // assume it is invalid and bail out of the routine since we will not be able
  108. // to decrypt the given value. We'll also check the MAC for this encryption.
  109. if ( ! $this->valid_payload($data))
  110. {
  111. // Decryption failed
  112. return FALSE;
  113. }
  114. if ( ! $this->valid_mac($data))
  115. {
  116. // Decryption failed
  117. return FALSE;
  118. }
  119. $iv = base64_decode($data['iv']);
  120. if ( ! $iv)
  121. {
  122. // Invalid base64 data
  123. return FALSE;
  124. }
  125. // Here we will decrypt the value. If we are able to successfully decrypt it
  126. // we will then unserialize it and return it out to the caller. If we are
  127. // unable to decrypt this value we will throw out an exception message.
  128. $decrypted = \openssl_decrypt($data['value'], $this->_cipher, $this->_key, 0, $iv);
  129. if ($decrypted === FALSE)
  130. {
  131. return FALSE;
  132. }
  133. return $decrypted;
  134. }
  135. /**
  136. * Create a MAC for the given value.
  137. *
  138. * @param string $iv
  139. * @param mixed $value
  140. * @return string
  141. */
  142. protected function hash($iv, $value)
  143. {
  144. return hash_hmac('sha256', $iv.$value, $this->_key);
  145. }
  146. /**
  147. * Verify that the encryption payload is valid.
  148. *
  149. * @param mixed $payload
  150. * @return bool
  151. */
  152. protected function valid_payload($payload)
  153. {
  154. return is_array($payload) AND
  155. isset($payload['iv'], $payload['value'], $payload['mac']) AND
  156. strlen(base64_decode($payload['iv'], TRUE)) === $this->_iv_size;
  157. }
  158. /**
  159. * Determine if the MAC for the given payload is valid.
  160. *
  161. * @param array $payload
  162. * @return bool
  163. */
  164. protected function valid_mac(array $payload)
  165. {
  166. $bytes = $this->create_iv($this->_iv_size);
  167. $calculated = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, TRUE);
  168. return hash_equals(hash_hmac('sha256', $payload['mac'], $bytes, TRUE), $calculated);
  169. }
  170. /**
  171. * Proxy for the random_bytes function - to allow mocking and testing against KAT vectors
  172. *
  173. * @return string the initialization vector or FALSE on error
  174. */
  175. public function create_iv()
  176. {
  177. if (function_exists('random_bytes'))
  178. {
  179. return random_bytes($this->_iv_size);
  180. }
  181. if (function_exists('mcrypt_create_iv'))
  182. {
  183. $key = mcrypt_create_iv($this->_iv_size, MCRYPT_DEV_URANDOM);
  184. if (mb_strlen($key, '8bit') === $this->_iv_size)
  185. {
  186. return $key;
  187. }
  188. }
  189. throw new Kohana_Exception('Could not create initialization vector.');
  190. }
  191. }