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
4require_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
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 {
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 {
92 }
93
99 {
100 $this->backward_compatibility = (bool) $backward_compatibility;
101 }
102
106 public function isSecurityFlawIgnored() : bool
107 {
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}
$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