Hmac.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\storage\session\strategy;
  9. use RuntimeException;
  10. use lithium\core\ConfigException;
  11. use lithium\storage\session\strategy\MissingSignatureException;
  12. use lithium\util\String;
  13. /**
  14. * This strategy allows you to sign your `Session` and / or `Cookie` data with a passphrase
  15. * to ensure that it has not been tampered with.
  16. *
  17. * Example configuration:
  18. *
  19. * {{{
  20. * Session::config(array('default' => array(
  21. * 'adapter' => 'Cookie',
  22. * 'strategies' => array('Hmac' => array('secret' => 'foobar'))
  23. * )));
  24. * }}}
  25. *
  26. * This will configure the `HMAC` strategy to be used for all `Session` operations with the
  27. * `default` named configuration. A hash-based message authentication code (HMAC) will be
  28. * calculated for all data stored in your cookies, and will be compared to the signature
  29. * stored in your cookie data. If the two do not match, then your data has been tampered with
  30. * (or you have modified the data directly _without_ passing through the `Session` class, which
  31. * amounts to the same), then a catchable `RuntimeException` is thrown.
  32. *
  33. * Please note that this strategy is very finnicky, and is so by design. If you attempt to access
  34. * or modify the stored data in any way other than through the `Session` class configured with the
  35. * `Hmac` strategy with the properly configured `secret`, then it will probably blow up.
  36. *
  37. * @link http://en.wikipedia.org/wiki/HMAC Wikipedia: Hash-based Message Authentication Code
  38. */
  39. class Hmac extends \lithium\core\Object {
  40. /**
  41. * The HMAC secret.
  42. *
  43. * @var string HMAC secret string.
  44. */
  45. protected static $_secret = null;
  46. /**
  47. * Constructor.
  48. *
  49. * @param array $config Configuration array. Will throw an exception if the 'secret'
  50. * configuration key is not set.
  51. */
  52. public function __construct(array $config = array()) {
  53. if (!isset($config['secret'])) {
  54. throw new ConfigException("HMAC strategy requires a secret key.");
  55. }
  56. static::$_secret = $config['secret'];
  57. }
  58. /**
  59. * Write strategy method.
  60. * Adds an HMAC signature to the data. Note that this will transform the
  61. * passed `$data` to an array, and add a `__signature` key with the HMAC-calculated
  62. * value.
  63. *
  64. * @see lithium\storage\Session
  65. * @see lithium\core\Adaptable::config()
  66. * @link http://php.net/manual/en/function.hash-hmac.php PHP Manual: hash_hmac()
  67. * @param mixed $data The data to be signed.
  68. * @param array $options Options for this method.
  69. * @return array Data & signature.
  70. */
  71. public function write($data, array $options = array()) {
  72. $class = $options['class'];
  73. $futureData = $class::read(null, array('strategies' => false));
  74. $futureData = array($options['key'] => $data) + $futureData;
  75. unset($futureData['__signature']);
  76. $signature = static::_signature($futureData);
  77. $class::write('__signature', $signature, array('strategies' => false) + $options);
  78. return $data;
  79. }
  80. /**
  81. * Read strategy method.
  82. * Validates the HMAC signature of the stored data. If the signatures match, then
  83. * the data is safe, and the 'valid' key in the returned data will be
  84. *
  85. * If the store being read does not contain a `__signature` field, a `MissingSignatureException`
  86. * is thrown. When catching this exception, you may choose to handle it by either writing
  87. * out a signature (e.g. in cases where you know that no pre-existing signature may exist), or
  88. * you can blackhole it as a possible tampering attempt.
  89. *
  90. * @param array $data the Data being read.
  91. * @param array $options Options for this method.
  92. * @return array validated data
  93. */
  94. public function read($data, array $options = array()) {
  95. $class = $options['class'];
  96. $currentData = $class::read(null, array('strategies' => false));
  97. if (!isset($currentData['__signature'])) {
  98. throw new MissingSignatureException('HMAC signature not found.');
  99. }
  100. $currentSignature = $currentData['__signature'];
  101. $signature = static::_signature($currentData);
  102. if (!String::compare($signature, $currentSignature)) {
  103. $message = "Possible data tampering: HMAC signature does not match data.";
  104. throw new RuntimeException($message);
  105. }
  106. return $data;
  107. }
  108. /**
  109. * Delete strategy method.
  110. *
  111. * @see lithium\storage\Session
  112. * @see lithium\core\Adaptable::config()
  113. * @link http://php.net/manual/en/function.hash-hmac.php PHP Manual: hash_hmac()
  114. * @param mixed $data The data to be signed.
  115. * @param array $options Options for this method.
  116. * @return array Data & signature.
  117. */
  118. public function delete($data, array $options = array()) {
  119. $class = $options['class'];
  120. $futureData = $class::read(null, array('strategies' => false));
  121. unset($futureData[$options['key']]);
  122. $signature = static::_signature($futureData);
  123. $class::write('__signature', $signature, array('strategies' => false) + $options);
  124. return $data;
  125. }
  126. /**
  127. * Calculate the HMAC signature based on the data and a secret key.
  128. *
  129. * @param mixed $data
  130. * @param null|string $secret Secret key for HMAC signature creation.
  131. * @return string HMAC signature.
  132. */
  133. protected static function _signature($data, $secret = null) {
  134. unset($data['__signature']);
  135. $secret = ($secret) ?: static::$_secret;
  136. return hash_hmac('sha1', serialize($data), $secret);
  137. }
  138. }
  139. ?>