weave_hash.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. #
  5. # The contents of this file are subject to the Mozilla Public License Version
  6. # 1.1 (the "License"); you may not use this file except in compliance with
  7. # the License. You may obtain a copy of the License at
  8. # http://www.mozilla.org/MPL/
  9. #
  10. # Software distributed under the License is distributed on an "AS IS" basis,
  11. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. # for the specific language governing rights and limitations under the
  13. # License.
  14. #
  15. # The Original Code is http://stackoverflow.com/a/6337021/833893
  16. #
  17. # Contributor(s):
  18. # Daniel Triendl <daniel@pew.cc>
  19. #
  20. # Alternatively, the contents of this file may be used under the terms of
  21. # either the GNU General Public License Version 2 or later (the "GPL"), or
  22. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  23. # in which case the provisions of the GPL or the LGPL are applicable instead
  24. # of those above. If you wish to allow use of your version of this file only
  25. # under the terms of either the GPL or the LGPL, and not to allow others to
  26. # use your version of this file under the terms of the MPL, indicate your
  27. # decision by deleting the provisions above and replace them with the notice
  28. # and other provisions required by the GPL or the LGPL. If you do not delete
  29. # the provisions above, a recipient may use your version of this file under
  30. # the terms of any one of the MPL, the GPL or the LGPL.
  31. #
  32. # ***** END LICENSE BLOCK *****
  33. interface WeaveHash {
  34. public function hash($input);
  35. public function verify($input, $existingHash);
  36. public function needsUpdate($existingHash);
  37. }
  38. class WeaveHashMD5 implements WeaveHash {
  39. public function hash($input) {
  40. return md5($input);
  41. }
  42. public function verify($input, $existingHash) {
  43. return $this->hash($input) == $existingHash;
  44. }
  45. public function needsUpdate($existingHash) {
  46. return substr($existingHash, 0, 4) == "$2a$";
  47. }
  48. }
  49. class WeaveHashBCrypt implements WeaveHash {
  50. private $_rounds;
  51. public function __construct($rounds = 12) {
  52. if(CRYPT_BLOWFISH != 1) {
  53. throw new Exception("bcrypt not available");
  54. }
  55. $this->_rounds = $rounds;
  56. }
  57. public function hash($input) {
  58. $hash = crypt($input, $this->getSalt());
  59. if (strlen($hash) <= 13) {
  60. throw new Exception("error while generating hash");
  61. }
  62. return $hash;
  63. }
  64. public function verify($input, $existingHash) {
  65. if ($this->isMD5($existingHash)) {
  66. $md5 = new WeaveHashMD5();
  67. return $md5->verify($input, $existingHash);
  68. }
  69. $hash = crypt($input, $existingHash);
  70. return $hash === $existingHash;
  71. }
  72. public function needsUpdate($existingHash) {
  73. $identifier = $this->getIdentifier();
  74. return substr($existingHash, 0, strlen($identifier)) != $identifier;
  75. }
  76. private function isMD5($existingHash) {
  77. return substr($existingHash, 0, 4) != "$2a$";
  78. }
  79. private function getSalt() {
  80. $salt = $this->getIdentifier();
  81. $bytes = $this->getRandomBytes(16);
  82. $salt .= $this->encodeBytes($bytes);
  83. return $salt;
  84. }
  85. private function getIdentifier() {
  86. return sprintf("$2a$%02d$", $this->_rounds);
  87. }
  88. private $randomState;
  89. private function getRandomBytes($count) {
  90. $bytes = '';
  91. if(function_exists('openssl_random_pseudo_bytes') &&
  92. (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
  93. $bytes = openssl_random_pseudo_bytes($count);
  94. }
  95. if($bytes === '' && is_readable('/dev/urandom') &&
  96. ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
  97. $bytes = fread($hRand, $count);
  98. fclose($hRand);
  99. }
  100. if(strlen($bytes) < $count) {
  101. $bytes = '';
  102. if($this->randomState === null) {
  103. $this->randomState = microtime();
  104. if(function_exists('getmypid')) {
  105. $this->randomState .= getmypid();
  106. }
  107. }
  108. for($i = 0; $i < $count; $i += 16) {
  109. $this->randomState = md5(microtime() . $this->randomState);
  110. if (PHP_VERSION >= '5') {
  111. $bytes .= md5($this->randomState, true);
  112. } else {
  113. $bytes .= pack('H*', md5($this->randomState));
  114. }
  115. }
  116. $bytes = substr($bytes, 0, $count);
  117. }
  118. return $bytes;
  119. }
  120. private function encodeBytes($input) {
  121. // The following is code from the PHP Password Hashing Framework
  122. $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  123. $output = '';
  124. $i = 0;
  125. do {
  126. $c1 = ord($input[$i++]);
  127. $output .= $itoa64[$c1 >> 2];
  128. $c1 = ($c1 & 0x03) << 4;
  129. if ($i >= 16) {
  130. $output .= $itoa64[$c1];
  131. break;
  132. }
  133. $c2 = ord($input[$i++]);
  134. $c1 |= $c2 >> 4;
  135. $output .= $itoa64[$c1];
  136. $c1 = ($c2 & 0x0f) << 2;
  137. $c2 = ord($input[$i++]);
  138. $c1 |= $c2 >> 6;
  139. $output .= $itoa64[$c1];
  140. $output .= $itoa64[$c2 & 0x3f];
  141. } while (1);
  142. return $output;
  143. }
  144. }
  145. class WeaveHashFactory {
  146. public static function factory() {
  147. if (defined("BCRYPT") && BCRYPT) {
  148. return new WeaveHashBCrypt(BCRYPT_ROUNDS);
  149. } else {
  150. return new WeaveHashMD5();
  151. }
  152. }
  153. }
  154. ?>