Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.71% covered (warning)
85.71%
12 / 14
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Password
85.71% covered (warning)
85.71%
12 / 14
50.00% covered (danger)
50.00%
2 / 4
10.29
0.00% covered (danger)
0.00%
0 / 1
 encrypt
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 validate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 verify
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 shorten
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/** @noinspection PhpUnhandledExceptionInspection */
3declare(strict_types=1);
4
5class Password {
6  static function encrypt(string $password, string $pepper, bool $insecure = false): string {
7    // dont' use raw output of hash_hmac inside of password_hash
8    // https://blog.ircmaxell.com/2015/03/security-issue-combining-bcrypt-with.html
9    $hash = password_hash(hash_hmac('sha256', $password, $pepper), PASSWORD_BCRYPT, ['cost' => $insecure ? 4 : 10]);
10
11    if (!$hash) {
12      // very unlikely in 7.3, but still possible (in future versions):
13      // https://stackoverflow.com/questions/39729941/php-password-hash-returns-false/61611426#61611426
14      throw new Error("Fatal error when encrypting the password");
15    }
16
17    return $hash;
18  }
19
20  static function validate(string $password): void {
21    // NIST SP 800-63 recommends longer passwords, at least 8 characters...
22    // to accept the test-account with user123, we take 7 as minimum
23    // 60 is maximum to avoid DDOS attacks based on encryption time.
24
25    if ((strlen($password) < 7)) {
26      throw new HttpError("Password must have at least 7 characters.", 400);
27    }
28
29    if ((strlen($password) > 60)) {
30      // don't let attackers know the maximum too easy
31      throw new HttpError("Password too long", 400);
32    }
33  }
34
35  static function verify(string $password, string $hash, string $saltOrPepper): bool {
36    // for legacy passwords.
37    if (strlen($hash) == 40) {
38      $legacyHash = sha1($saltOrPepper . $password);
39
40      if (hash_equals($legacyHash, $hash)) {
41        return true;
42      }
43    }
44
45    return password_verify(hash_hmac('sha256', $password, $saltOrPepper), $hash);
46  }
47
48  static function shorten(string $password): string {
49    return preg_replace('/(^.).*(.$)/m', '$1***$2', $password);
50  }
51}