Skip to content

Commit 26ce438

Browse files
NEW Add password hasher for pbkdf2_sha512 (#11644)
1 parent a8792b8 commit 26ce438

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

_config/encryptors.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ name: coreencryptors
1111
'SilverStripe\Security\PasswordEncryptor_PHPHash': md5
1212
sha1_v2.4:
1313
'SilverStripe\Security\PasswordEncryptor_PHPHash': sha1
14+
pbkdf2_sha512:
15+
'SilverStripe\Security\PasswordEncryptorPBKDF2': sha512
1416
blowfish:
1517
'SilverStripe\Security\PasswordEncryptor_Blowfish':
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace SilverStripe\Security;
4+
5+
use RuntimeException;
6+
7+
/**
8+
* Provides Password-Based Key Derivation Function hashing for passwords, using the provided algorithm (default
9+
* is SHA512), which is NZISM compliant under version 3.2 section 17.2.
10+
*/
11+
class PasswordEncryptorPBKDF2 extends PasswordEncryptor
12+
{
13+
private string $algorithm = 'sha512';
14+
15+
/**
16+
* The number of internal iterations for hash_pbkdf2() to perform for the derivation. Please note that if you
17+
* change this from the default value you will break existing hashes stored in the database, so these would
18+
* need to be regenerated.
19+
*/
20+
private int $iterations = 30000;
21+
22+
/**
23+
* @throws RuntimeException If the provided algorithm is not available in the current environment
24+
*/
25+
public function __construct(string $algorithm, ?int $iterations = null)
26+
{
27+
if (!in_array($algorithm, hash_hmac_algos())) {
28+
throw new RuntimeException(
29+
sprintf('Hash algorithm "%s" not found in hash_hmac_algos()', $algorithm)
30+
);
31+
}
32+
33+
$this->algorithm = $algorithm;
34+
35+
if ($iterations !== null) {
36+
$this->iterations = $iterations;
37+
}
38+
}
39+
40+
/**
41+
* Get the name of the algorithm that will be used to hash the password
42+
*/
43+
public function getAlgorithm(): string
44+
{
45+
return $this->algorithm;
46+
}
47+
48+
/**
49+
* Get the number of iterations that will be used to hash the password
50+
*/
51+
public function getIterations(): int
52+
{
53+
return $this->iterations;
54+
}
55+
56+
public function encrypt($password, $salt = null, $member = null)
57+
{
58+
return hash_pbkdf2(
59+
$this->getAlgorithm() ?? '',
60+
(string) $password,
61+
(string) $salt,
62+
$this->getIterations() ?? 0
63+
);
64+
}
65+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace SilverStripe\Security\Tests;
4+
5+
use League\Flysystem\Exception;
6+
use SilverStripe\Dev\SapphireTest;
7+
use SilverStripe\Security\PasswordEncryptorPBKDF2;
8+
9+
class PasswordEncryptorPBKDF2Test extends SapphireTest
10+
{
11+
public function testGetIterations()
12+
{
13+
$encryptor = new PasswordEncryptorPBKDF2('sha512', 12345);
14+
$this->assertSame(12345, $encryptor->getIterations());
15+
}
16+
17+
public function testEncrypt()
18+
{
19+
$encryptor = new PasswordEncryptorPBKDF2('sha512', 10000);
20+
$salt = 'predictablesaltforunittesting';
21+
$result = $encryptor->encrypt('opensesame', $salt);
22+
$this->assertSame(
23+
'6bafcacb90',
24+
substr($result ?? '', 0, 10),
25+
'Hashed password with predictable salt did not match fixtured expectation'
26+
);
27+
}
28+
29+
public function testThrowsExceptionWhenInvalidAlgorithmIsProvided()
30+
{
31+
$this->expectException(\Exception::class);
32+
$this->expectExceptionMessage('Hash algorithm "foobar" not found in hash_hmac_algos()');
33+
new PasswordEncryptorPBKDF2('foobar');
34+
}
35+
}

0 commit comments

Comments
 (0)