/library/DS/Password/Protector.php

https://github.com/cahnory/DrySocks · PHP · 210 lines · 73 code · 19 blank · 118 comment · 7 complexity · 12b081b0cd37b139763a1d68815d5497 MD5 · raw file

  1. <?php
  2. /**
  3. * DrySocks Framework
  4. *
  5. * LICENSE
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. *
  24. * @category DS
  25. * @package DS\Password
  26. * @author François "cahnory" Germain <cahnory@gmail.com>
  27. * @copyright Copyright (c) 2011 François "cahnory" Germain
  28. * @license http://www.opensource.org/licenses/mit-license.php
  29. */
  30. namespace DS\Password;
  31. /**
  32. * Class for password protection
  33. *
  34. * @category DS
  35. * @package DS\Password
  36. * @author François "cahnory" Germain <cahnory@gmail.com>
  37. * @copyright Copyright (c) 2011 François "cahnory" Germain
  38. * @license http://www.opensource.org/licenses/mit-license.php
  39. */
  40. class Protector
  41. {
  42. protected $hashAlgorithm = 'sha512';
  43. protected $hashKey;
  44. protected $bcryptCost = 7;
  45. protected $bcryptSalt = NULL;
  46. protected $salts = array();
  47. protected $preSalts = array();
  48. protected $processor;
  49. /**
  50. * Add salt
  51. *
  52. * The salt value is generate on hashing
  53. *
  54. * @param SaltInterface $salt the salt
  55. *
  56. * @return void
  57. *
  58. * @access public
  59. */
  60. public function addSalt(SaltInterface $salt) {
  61. $this->salts[] = $salt;
  62. }
  63. /**
  64. * Add preSalt
  65. *
  66. * The salt has to be manually set in order
  67. * to make matching tests work (non variable)
  68. *
  69. * @param SaltInterface $salt the salt
  70. *
  71. * @return void
  72. *
  73. * @access public
  74. */
  75. public function addPreSalt(SaltInterface $salt) {
  76. $this->preSalts[] = $salt;
  77. }
  78. /**
  79. * Set hash properties
  80. *
  81. * @param string $algorithm algorithm to use
  82. * @param string key shared secret key
  83. *
  84. * @return void
  85. *
  86. * @access public
  87. */
  88. public function setHash($algorithm, $key = NULL) {
  89. if(!in_array($algorithm, hash_algos())) {
  90. throw new \Exception('Unknown algorithm '.$algorithm);
  91. }
  92. $this->hashAlgorithm = $algorithm;
  93. $this->hashKey = $key;
  94. }
  95. /**
  96. * Set bcrypt properties
  97. *
  98. * @param string $cost number of iterations
  99. * @param string salt shared secret salt
  100. *
  101. * @return void
  102. *
  103. * @access public
  104. */
  105. public function setBcrypt($cost, $salt = NULL)
  106. {
  107. if(preg_match('#^[./0-9A-Za-z]{0,22}$#', $salt) === 0) {
  108. throw new \Exception('bcrypt expects a salt of 0 to 22 digits of the alphabet [./0-9A-Za-z]');
  109. }
  110. if($cost < 4 || $cost > 31) {
  111. throw new \Exception('bcrypt expects cost parameter between 4 and 31');
  112. }
  113. $this->bcryptCost = $cost;
  114. $this->bcryptSalt = $salt;
  115. }
  116. /**
  117. * Set user function to alterate the hash
  118. *
  119. * @param callback $processor alteration function
  120. *
  121. * @return void
  122. *
  123. * @access public
  124. */
  125. public function setProcessor($processor) {
  126. if(!is_callable($processor)) {
  127. throw new \Exception('Processor must be callable');
  128. }
  129. $this->processor = $processor;
  130. }
  131. /**
  132. * Set user function to alterate the hash
  133. *
  134. * @param string $password the password to hash
  135. *
  136. * @return string the hashed password
  137. *
  138. * @access public
  139. */
  140. public function hash($password) {
  141. // Add shared salts
  142. foreach($this->preSalts as $key => $salt) {
  143. $password = $salt->apply($password);
  144. }
  145. // Hash the password
  146. $password = hash_hmac($this->hashAlgorithm, $password, $this->hashKey);
  147. // Crypt the hash
  148. $password = substr(crypt($password, '$2a$'.sprintf('%02d', $this->bcryptCost).'$'.$this->bcryptSalt), 29);
  149. // Apply user processing function
  150. if($this->processor) {
  151. $password = call_user_func($this->processor, $password);
  152. }
  153. // Add password specific salts
  154. foreach($this->salts as $salt) {
  155. $salt->generate();
  156. $password = $salt->apply($password);
  157. }
  158. return $password;
  159. }
  160. /**
  161. * Tel if a password and a hash match
  162. *
  163. * @param string $password the password to test
  164. * @param string $hash the hash
  165. *
  166. * @return void
  167. *
  168. * @access public
  169. */
  170. public function match($password, $hash) {
  171. // Remove salts from hash
  172. $salts = array_reverse($this->salts);
  173. foreach($salts as $salt) {
  174. $hash = $salt->remove($hash);
  175. }
  176. // Add shared salts
  177. foreach($this->preSalts as $key => $salt) {
  178. $password = $salt->apply($password);
  179. }
  180. // Hash the password
  181. $password = hash_hmac($this->hashAlgorithm, $password, $this->hashKey);
  182. // Crypt the password hash
  183. $password = substr(crypt($password, '$2a$'.sprintf('%02d', $this->bcryptCost).'$'.$this->bcryptSalt), 29);
  184. // Apply user processing function
  185. if($this->processor) {
  186. $password = call_user_func($this->processor, $password);
  187. }
  188. return $password === $hash;
  189. }
  190. }
  191. ?>