Skip to content

Commit 131421c

Browse files
committed
2.0.0
1 parent afc19a5 commit 131421c

File tree

9 files changed

+199
-43
lines changed

9 files changed

+199
-43
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
vendor/
22
composer.lock
3-
index.php
3+
index.php
4+
.phpunit.result.cache

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2021 Brenno Duarte de Lima
3+
Copyright (c) 2022 Brenno Duarte de Lima
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Unlike just using `password_hash`, SecurePassword adds a secret entry (commonly
88

99
## Requirements
1010

11-
PHP >= 7.4
11+
PHP >= 8.0
1212

1313
## Installing via Composer
1414

@@ -18,13 +18,13 @@ composer require brenno-duarte/php-secure-password
1818

1919
## How to use
2020

21-
The code below shows an example for creating the hash. Simply use the `createHash` method by entering your password.
21+
The code below shows an example for creating the hash. The `createHash` method generates the password hash along with the "peeper", and the `getHash` method returns the generated hash.
2222

2323
```php
2424
use SecurePassword\SecurePassword;
2525

2626
$password = new SecurePassword();
27-
$hash = $password->createHash('my_password');
27+
$hash = $password->createHash('my_password')->getHash();
2828

2929
/** Return string */
3030
var_dump($hash);
@@ -63,39 +63,43 @@ You can change the type of algorithm used to generate the hash. It is possible t
6363

6464
```php
6565
# standard encryption
66-
$hash = $password->useDefault()->createHash('my_password');
66+
$hash = $password->useDefault()->createHash('my_password')->getHash();
6767

6868
# Bcrypt encryption
69-
$hash = $password->useBcrypt()->createHash('my_password');
69+
$hash = $password->useBcrypt()->createHash('my_password')->getHash();
7070

7171
# Argon2 encryption
72-
$hash = $password->useArgon2()->createHash('my_password');
72+
$hash = $password->useArgon2()->createHash('my_password')->getHash();
7373

7474
# Argon2d encryption (with `true`)
75-
$hash = $password->useArgon2(true)->createHash('my_password');
75+
$hash = $password->useArgon2(true)->createHash('my_password')->getHash();
7676
```
7777

7878
If the type of algorithm is not provided, the default encryption will be 'PASSWORD_DEFAULT'.
7979

8080
## Returns information about the given hash
8181

82-
To return the information of the created hash, use `$info` as `true`.
82+
To return the information of the created hash, use `getHashInfo` method.
8383

8484
```php
85-
$hash = $password->createHash('my_password', true);
85+
$hash = $password->createHash('my_password')->getHashInfo();
8686

8787
/** Return array */
8888
var_dump($hash);
8989
```
9090

9191
## Verifies that a password matches a hash
9292

93-
Checks whether the hash in `$hash` is valid. If the hash entered does not match the options received in the `createHash` method, it is possible to regenerate a new hash in `$verify_needs_rehash`. This function also makes timing attacks difficult.
93+
To verify that the hash generated with `createHash` is valid, you can use `verifyHash` in two ways:
9494

9595
```php
96-
$hash = $password->createHash('my_password');
96+
# First way
97+
$hash = $password->createHash('my_password')->getHash();
9798
$res = $password->verifyHash('my_password', $hash);
9899

100+
# Second way
101+
$hash = $password->createHash('my_password')->verifyHash();
102+
99103
/** Return bool */
100104
var_dump($res);
101105
```
@@ -105,7 +109,7 @@ var_dump($res);
105109
You can change the type of algorithm that will be used to check the hash.
106110

107111
```php
108-
$hash = $password->useArgon2()->createHash('my_password');
112+
$hash = $password->useArgon2()->createHash('my_password')->getHash();
109113
$res = $password->useArgon2()->verifyHash('my_password', $hash);
110114

111115
/** Return bool */
@@ -115,7 +119,7 @@ var_dump($res);
115119
If the encryption type has been changed, you can generate a new hash with the new encryption. The `needsHash()` method checks whether the reported hash needs to be regenerated. Otherwise, it will return false.
116120

117121
```php
118-
$hash = $password->useArgon2()->createHash('my_password');
122+
$hash = $password->useArgon2()->createHash('my_password')->getHash();
119123
$needs = $password->useDefault()->needsRehash('my_password', $hash);
120124

121125
/** Return bool or string */

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
"php-password"
99
],
1010
"require": {
11-
"php": "^7.3|8.0"
11+
"php": ">=8.0"
12+
},
13+
"require-dev": {
14+
"phpunit/phpunit": "^9.5"
1215
},
1316
"autoload": {
1417
"psr-4": {

phpunit.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit bootstrap="vendor/autoload.php"
4+
colors="true"
5+
verbose="true"
6+
stopOnFailure="false">
7+
<testsuites>
8+
<testsuite name="Password Test Suite">
9+
<directory>tests</directory>
10+
</testsuite>
11+
</testsuites>
12+
</phpunit>

src/HashAlgorithm.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ abstract class HashAlgorithm
1212
/**
1313
* @var mixed
1414
*/
15-
protected $algo;
15+
protected mixed $algo;
1616

1717
/**
1818
* @var array
1919
*/
2020
protected array $options = [];
2121

2222
/**
23+
* @param array $options
24+
*
2325
* @return SecurePassword
2426
*/
2527
public function useDefault(array $options = []): SecurePassword

src/HashException.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44

55
class HashException extends \Exception
66
{
7+
public function __toString()
8+
{
9+
return __CLASS__ . ": {$this->message}\n";
10+
}
711
}

src/SecurePassword.php

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ class SecurePassword extends HashAlgorithm
1212
*/
1313
private string $pepper;
1414

15+
/**
16+
* @var string
17+
*/
18+
private string $pwd_hashed = "";
19+
20+
/**
21+
* @var string
22+
*/
23+
private string $password = "";
24+
1525
/**
1626
* @var array
1727
*/
@@ -69,39 +79,54 @@ public function getPepper(): string
6979
}
7080

7181
/**
72-
* Creates a password peppered using the entered password and the secret entry. To return the
73-
* information of the created hash, use `$info` as `true`.
82+
* Creates a password peppered using the entered password and the secret entry.
7483
*
7584
* @param string $password
7685
*
77-
* @return mixed
86+
* @return SecurePassword
7887
*/
79-
public function createHash(string $password, bool $info = false)
88+
public function createHash(string $password): SecurePassword
8089
{
81-
$pwd_peppered = $this->passwordPeppered($password);
82-
$pwd_hashed = password_hash($pwd_peppered, $this->algo, $this->options);
90+
$this->password = $password;
8391

84-
if ($info == true) {
85-
return password_get_info($pwd_hashed);
86-
}
92+
$pwd_peppered = $this->passwordPeppered($this->password);
93+
$this->pwd_hashed = password_hash($pwd_peppered, $this->algo, $this->options);
94+
95+
return $this;
96+
}
8797

88-
return $pwd_hashed;
98+
/**
99+
* @return string
100+
*/
101+
public function getHash(): string
102+
{
103+
return $this->pwd_hashed;
89104
}
90105

91106
/**
92-
* Checks whether the hash in `$hash` is valid. If the hash entered does not match the options
93-
* received in the `createHash` method, it is possible to regenerate a new hash in `$verify_needs_rehash`.
94-
* This function also makes timing attacks difficult.
107+
* @return array
108+
*/
109+
public function getHashInfo(): array
110+
{
111+
return password_get_info($this->pwd_hashed);
112+
}
113+
114+
/**
115+
* Verify if the hash generated with `createHash` is valid
95116
*
96-
* @param string $password
97-
* @param string $hash
117+
* @param null|string $password
118+
* @param null|string $hash
98119
*
99120
* @return mixed
100121
*/
101-
public function verifyHash(string $password, $hash)
122+
public function verifyHash(?string $password = null, ?string $hash = null): mixed
102123
{
103-
if (is_array($hash)) {
104-
throw new HashException("You are returning the hash information. Enter 'false' in the 'createHash' method");
124+
if (is_null($password) && $this->password != "") {
125+
$password = $this->password;
126+
}
127+
128+
if (is_null($hash) && $this->pwd_hashed != "") {
129+
$hash = $this->pwd_hashed;
105130
}
106131

107132
$pph_strt = microtime(true);
@@ -151,10 +176,10 @@ public function getOptimalBcryptCost(int $min_ms = 250, string $password = "test
151176
*
152177
* @return mixed
153178
*/
154-
public function needsRehash(string $password, string $hash)
179+
public function needsRehash(string $password, string $hash): mixed
155180
{
156181
if (password_needs_rehash($hash, $this->algo)) {
157-
$newHash = $this->createHash($password);
182+
$newHash = $this->createHash($password)->getHash();
158183

159184
return $newHash;
160185
} else {
@@ -169,9 +194,7 @@ public function needsRehash(string $password, string $hash)
169194
*/
170195
private function createPepper(string $pepper): string
171196
{
172-
$hash = openssl_encrypt($pepper, "AES-128-CBC", pack('a16', 'secure_password_1'), 0, pack('a16', 'secure_password_2'));
173-
174-
return $hash;
197+
return openssl_encrypt($pepper, "AES-128-CBC", pack('a16', 'secure_password_1'), 0, pack('a16', 'secure_password_2'));
175198
}
176199

177200
/**
@@ -183,8 +206,6 @@ private function createPepper(string $pepper): string
183206
*/
184207
private function passwordPeppered(string $password): string
185208
{
186-
$pwd_peppered = hash_hmac("sha256", $password, $this->getPepper());
187-
188-
return $pwd_peppered;
209+
return hash_hmac("sha256", $password, $this->getPepper());
189210
}
190211
}

tests/SecurePasswordTest.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
use PHPUnit\Framework\TestCase;
4+
use SecurePassword\HashAlgorithm;
5+
use SecurePassword\SecurePassword;
6+
7+
class SecurePasswordTest extends TestCase
8+
{
9+
public function testCreateHash()
10+
{
11+
$password = new SecurePassword();
12+
$hash = $password->createHash('my_password')->getHash();
13+
14+
$this->assertIsString($hash);
15+
}
16+
17+
public function testChangePepper()
18+
{
19+
$password = new SecurePassword();
20+
$password->setPepper('new_pepper');
21+
$hash = $password->createHash('my_password')->getHash();
22+
23+
$this->assertIsString($hash);
24+
}
25+
26+
public function testChangeHashAlgorithm()
27+
{
28+
$password = new SecurePassword([
29+
'algo' => HashAlgorithm::ARGON2I
30+
]);
31+
$hash = $password->createHash('my_password')->getHash();
32+
33+
$this->assertIsString($hash);
34+
}
35+
36+
public function testCreateWithAlgorithm()
37+
{
38+
$password = new SecurePassword();
39+
$hash = $password->useArgon2()->createHash('my_password')->getHash();
40+
$res = $password->useArgon2()->verifyHash('my_password', $hash);
41+
42+
$this->assertTrue($res);
43+
}
44+
45+
public function testCreateWithOtherAlgorithm()
46+
{
47+
$password = new SecurePassword();
48+
$hash = $password->useArgon2()->createHash('my_password')->getHash();
49+
$needs = $password->useDefault()->needsRehash('my_password', $hash);
50+
51+
$this->assertIsString($needs);
52+
}
53+
54+
public function testHashInfo()
55+
{
56+
$password = new SecurePassword();
57+
$hash = $password->createHash('my_password')->getHashInfo();
58+
59+
$this->assertIsArray($hash);
60+
}
61+
62+
public function testCreateAndVerifyHashChained()
63+
{
64+
$password = new SecurePassword();
65+
$hash = $password->createHash('my_password')->verifyHash();
66+
67+
$this->assertTrue($hash);
68+
}
69+
70+
public function testCreateAndVerifyHash()
71+
{
72+
$password = new SecurePassword();
73+
$hash = $password->createHash('my_password')->getHash();
74+
$res = $password->verifyHash('my_password', $hash);
75+
76+
$this->assertTrue($res);
77+
}
78+
79+
public function testVerifyHash()
80+
{
81+
$hash = '$2y$10$Er0wYRuY7LTYkmWmL8YMMeuxiRIEZ7Vn/8kPb4.aNkzIFRN/N.qG.';
82+
$res = (new SecurePassword)->verifyHash('my_password', $hash);
83+
84+
$this->assertTrue($res);
85+
}
86+
87+
public function testVerifyHashWrong()
88+
{
89+
$hash = '$2y$10$Er0wYRuY7LTYkmWmL8YMMeuxiRIEZ7Vn/8kPb4.aNkzIFRN/N.qG.';
90+
$res = (new SecurePassword)->verifyHash('mypassword', $hash);
91+
92+
$this->assertFalse($res);
93+
}
94+
95+
public function testVerifyRehash()
96+
{
97+
$hash = '$2y$10$Er0wYRuY7LTYkmWmL8YMMeuxiRIEZ7Vn/8kPb4.aNkzIFRN/N.qG.';
98+
$res = (new SecurePassword)->useArgon2()->needsRehash('mypassword', $hash);
99+
100+
$this->assertIsString($res);
101+
}
102+
103+
public function testOptimalBcryptCost()
104+
{
105+
$res = (new SecurePassword)->getOptimalBcryptCost();
106+
107+
$this->assertIsInt($res);
108+
}
109+
}

0 commit comments

Comments
 (0)