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