ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
class.ilBcryptPasswordEncoder.php
Go to the documentation of this file.
1 <?php declare(strict_types=1);
2 /* Copyright (c) 1998-2014 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 require_once 'Services/Password/classes/encoders/class.ilBcryptPhpPasswordEncoder.php';
5 
12 {
14  const MIN_SALT_SIZE = 16;
15 
17  const SALT_STORAGE_FILENAME = 'pwsalt.txt';
18 
20  private $client_salt = null;
21 
23  private $is_security_flaw_ignored = false;
24 
26  private $backward_compatibility = false;
27 
29  private $data_directory = '';
30 
35  public function __construct(array $config = [])
36  {
37  if (!empty($config)) {
38  foreach ($config as $key => $value) {
39  switch (strtolower($key)) {
40  case 'ignore_security_flaw':
41  $this->setIsSecurityFlawIgnored($value);
42  break;
43 
44  case 'data_directory':
45  $this->setDataDirectory($value);
46  break;
47  }
48  }
49  }
50 
52  }
53 
57  protected function init() : void
58  {
59  $this->readClientSalt();
60  }
61 
65  protected function isBcryptSupported() : bool
66  {
67  return PHP_VERSION_ID >= 50307;
68  }
69 
73  public function getDataDirectory() : string
74  {
75  return $this->data_directory;
76  }
77 
81  public function setDataDirectory(string $data_directory) : void
82  {
83  $this->data_directory = $data_directory;
84  }
85 
89  public function isBackwardCompatibilityEnabled() : bool
90  {
91  return (bool) $this->backward_compatibility;
92  }
93 
99  {
100  $this->backward_compatibility = (bool) $backward_compatibility;
101  }
102 
106  public function isSecurityFlawIgnored() : bool
107  {
108  return (bool) $this->is_security_flaw_ignored;
109  }
110 
115  {
116  $this->is_security_flaw_ignored = (bool) $is_security_flaw_ignored;
117  }
118 
122  public function getClientSalt() : ?string
123  {
124  return $this->client_salt;
125  }
126 
130  public function setClientSalt(?string $client_salt)
131  {
132  $this->client_salt = $client_salt;
133  }
134 
139  public function encodePassword(string $raw, string $salt) : string
140  {
141  if (!$this->getClientSalt()) {
142  throw new ilPasswordException('Missing client salt.');
143  }
144 
145  if ($this->isPasswordTooLong($raw)) {
146  throw new ilPasswordException('Invalid password.');
147  }
148 
149  return $this->encode($raw, $salt);
150  }
151 
156  public function isPasswordValid(string $encoded, string $raw, string $salt) : bool
157  {
158  if (!$this->getClientSalt()) {
159  throw new ilPasswordException('Missing client salt.');
160  }
161 
162  return !$this->isPasswordTooLong($raw) && $this->check($encoded, $raw, $salt);
163  }
164 
168  public function getName() : string
169  {
170  return 'bcrypt';
171  }
172 
176  public function requiresSalt() : bool
177  {
178  return true;
179  }
180 
184  public function requiresReencoding(string $encoded) : bool
185  {
186  return false;
187  }
188 
196  protected function encode(string $raw, string $userSecret) : string
197  {
198  $clientSecret = $this->getClientSalt();
199  $hashedPassword = hash_hmac(
200  'whirlpool',
201  str_pad($raw, strlen($raw) * 4, sha1($userSecret), STR_PAD_BOTH),
202  $clientSecret,
203  true
204  );
205  $salt = substr(
206  str_shuffle(str_repeat('./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 22)),
207  0,
208  22
209  );
210 
215  if ($this->isBcryptSupported() && !$this->isBackwardCompatibilityEnabled()) {
216  $prefix = '$2y$';
217  } else {
218  $prefix = '$2a$';
219  // check if the password contains 8-bit character
220  if (!$this->isSecurityFlawIgnored() && preg_match('/[\x80-\xFF]/', $raw)) {
221  throw new ilPasswordException(
222  'The bcrypt implementation used by PHP can contain a security flaw ' .
223  'using passwords with 8-bit characters. ' .
224  'We suggest to upgrade to PHP 5.3.7+ or use passwords with only 7-bit characters.'
225  );
226  }
227  }
228 
229  $saltedPassword = crypt($hashedPassword, $prefix . $this->getCosts() . '$' . $salt);
230  if (strlen($saltedPassword) <= 13) {
231  throw new ilPasswordException('Error during the bcrypt generation');
232  }
233 
234  return $saltedPassword;
235  }
236 
244  protected function check(string $encoded, string $raw, string $salt) : bool
245  {
246  $hashedPassword = hash_hmac(
247  'whirlpool',
248  str_pad($raw, strlen($raw) * 4, sha1($salt), STR_PAD_BOTH),
249  $this->getClientSalt(),
250  true
251  );
252 
253  return $this->comparePasswords($encoded, crypt($hashedPassword, substr($encoded, 0, 30)));
254  }
255 
259  public function getClientSaltLocation() : string
260  {
261  return $this->getDataDirectory() . '/' . self::SALT_STORAGE_FILENAME;
262  }
263 
267  private function readClientSalt() : void
268  {
269  if (is_file($this->getClientSaltLocation()) && is_readable($this->getClientSaltLocation())) {
270  $contents = file_get_contents($this->getClientSaltLocation());
271  if (strlen(trim($contents))) {
272  $this->setClientSalt($contents);
273  }
274  } else {
275  $this->generateClientSalt();
276  $this->storeClientSalt();
277  }
278  }
279 
283  private function generateClientSalt() : void
284  {
285  $this->setClientSalt(
286  substr(str_replace('+', '.', base64_encode(ilPasswordUtils::getBytes(self::MIN_SALT_SIZE))), 0, 22)
287  );
288  }
289 
293  private function storeClientSalt() : void
294  {
295  $result = @file_put_contents($this->getClientSaltLocation(), $this->getClientSalt());
296  if (!$result) {
297  throw new ilPasswordException(sprintf(
298  "Could not store the client salt in: %s. Please contact an administrator.",
299  $this->getClientSaltLocation()
300  ));
301  }
302  }
303 }
check(string $encoded, string $raw, string $salt)
Verifies a bcrypt encoded string.
setBackwardCompatibility(bool $backward_compatibility)
Set the backward compatibility $2a$ instead of $2y$ for PHP 5.3.7+.
$result
if(!array_key_exists('PATH_INFO', $_SERVER)) $config
Definition: metadata.php:68
comparePasswords(string $knownString, string $userString)
Compares two passwords.
setIsSecurityFlawIgnored(bool $is_security_flaw_ignored)
encodePassword(string $raw, string $salt)
encode(string $raw, string $userSecret)
Generates a bcrypt encoded string.
Class for user password exception handling in ILIAS.
isPasswordTooLong(string $password)
Checks if the password is too long.
static getBytes($length)
Generate random bytes using OpenSSL or Mcrypt and mt_rand() as fallback.
setDataDirectory(string $data_directory)
__construct(Container $dic, ilPlugin $plugin)
isPasswordValid(string $encoded, string $raw, string $salt)