Completed
Push — master ( 23153f...eb881d )
by Angus
03:14
created

Bcrypt::hashEquals()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 33
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 6
nop 2
dl 0
loc 33
rs 8.439
c 0
b 0
f 0
1
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2
3
4
class Bcrypt {
5
  private $rounds;
6
  private $salt_prefix;
7
8
  /**
9
   * Bcrypt constructor.
10
   *
11
   * @param array $params
12
   * @throws Exception
13
   */
14
  public function __construct($params=array('rounds'=>7, 'salt_prefix'=>'$2y$')) {
15
16
    if(CRYPT_BLOWFISH != 1) {
17
      throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
18
    }
19
20
    $this->rounds = $params['rounds'];
21
    $this->salt_prefix = $params['salt_prefix'];
22
  }
23
24
  public function hash($input) {
25
    $hash = crypt($input, $this->getSalt());
26
27
    if(strlen($hash) > 13) {
28
      return $hash;
29
    }
30
31
    return false;
32
  }
33
34
  /**
35
   * @param $input
36
   * @param $existingHash
37
   * @return bool
38
     */
39
  public function verify($input, $existingHash) {
40
    $hash = crypt($input, $existingHash);
41
    return $this->hashEquals($existingHash, $hash);
42
  }
43
  
44
   /**
45
   * Polyfill for hash_equals()
46
   * Code mainly taken from hash_equals() compat function of CodeIgniter 3
47
   *
48
   * @param  string  $known_string
49
   * @param  string  $user_string
50
   * @return  bool
51
   */
52
  private function hashEquals($known_string, $user_string)
53
  {
54
    // For CI3 or PHP >= 5.6
55
    if (function_exists('hash_equals')) 
56
    {
57
      return hash_equals($known_string, $user_string);
58
    }
59
    
60
    // For CI2 with PHP < 5.6
61
    // Code from CI3 https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/compat/hash.php
62
    if ( ! is_string($known_string))
63
    {
64
      trigger_error('hash_equals(): Expected known_string to be a string, '.strtolower(gettype($known_string)).' given', E_USER_WARNING);
65
      return FALSE;
66
    }
67
    elseif ( ! is_string($user_string))
68
    {
69
      trigger_error('hash_equals(): Expected user_string to be a string, '.strtolower(gettype($user_string)).' given', E_USER_WARNING);
70
      return FALSE;
71
    }
72
    elseif (($length = strlen($known_string)) !== strlen($user_string))
73
    {
74
      return FALSE;
75
    }
76
77
    $diff = 0;
78
    for ($i = 0; $i < $length; $i++)
79
    {
80
      $diff |= ord($known_string[$i]) ^ ord($user_string[$i]);
81
    }
82
83
    return ($diff === 0);
84
  }
85
86
  private function getSalt() {
87
    $salt = sprintf($this->salt_prefix.'%02d$', $this->rounds);
88
89
    $bytes = $this->getRandomBytes(16);
90
91
    $salt .= $this->encodeBytes($bytes);
92
93
    return $salt;
94
  }
95
96
  private $randomState;
97
98
99
  /**
100
   * @param $count
101
   * @return string
102
     */
103
  private function getRandomBytes($count) {
104
    $bytes = '';
105
106
    if(function_exists('openssl_random_pseudo_bytes') &&
107
        (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
108
      $bytes = openssl_random_pseudo_bytes($count);
109
    }
110
111
    if($bytes === '' && @is_readable('/dev/urandom') &&
112
       ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
113
      $bytes = fread($hRand, $count);
114
      fclose($hRand);
115
    }
116
117
    if(strlen($bytes) < $count) {
118
      $bytes = '';
119
120
      if($this->randomState === null) {
121
        $this->randomState = microtime();
122
        if(function_exists('getmypid')) {
123
          $this->randomState .= getmypid();
124
        }
125
      }
126
127
      for($i = 0; $i < $count; $i += 16) {
128
        $this->randomState = md5(microtime() . $this->randomState);
129
130
        if (PHP_VERSION >= '5') {
131
          $bytes .= md5($this->randomState, true);
132
        } else {
133
          $bytes .= pack('H*', md5($this->randomState));
134
        }
135
      }
136
137
      $bytes = substr($bytes, 0, $count);
138
    }
139
140
    return $bytes;
141
  }
142
143
  /**
144
   * @param $input
145
   * @return string
146
     */
147
  private function encodeBytes($input) {
148
    // The following is code from the PHP Password Hashing Framework
149
    $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
150
151
    $output = '';
152
    $i = 0;
153
    do {
154
      $c1 = ord($input[$i++]);
155
      $output .= $itoa64[$c1 >> 2];
156
      $c1 = ($c1 & 0x03) << 4;
157
      if ($i >= 16) {
158
        $output .= $itoa64[$c1];
159
        break;
160
      }
161
162
      $c2 = ord($input[$i++]);
163
      $c1 |= $c2 >> 4;
164
      $output .= $itoa64[$c1];
165
      $c1 = ($c2 & 0x0f) << 2;
166
167
      $c2 = ord($input[$i++]);
168
      $c1 |= $c2 >> 6;
169
      $output .= $itoa64[$c1];
170
      $output .= $itoa64[$c2 & 0x3f];
171
    } while (1);
172
173
    return $output;
174
  }
175
}
176
177
178
/***** End of BCrypt.php ***********/
179