-
-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
422 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Hash; | ||
|
||
use HashContext; | ||
use Psl; | ||
use Psl\Arr; | ||
use Psl\Str; | ||
|
||
use function hash_final; | ||
use function hash_init; | ||
use function hash_update; | ||
|
||
use const HASH_HMAC; | ||
|
||
/** | ||
* Incremental hashing context. | ||
* | ||
* Example: | ||
* | ||
* Hash\Context::forAlgorithm('md5') | ||
* ->update('The quick brown fox ') | ||
* ->update('jumped over the lazy dog.') | ||
* ->finalize() | ||
* => Str("5c6ffbdd40d9556b73a21e63c3e0e904") | ||
* | ||
* @psalm-immutable | ||
*/ | ||
final class Context | ||
{ | ||
private HashContext $internalContext; | ||
|
||
private function __construct(HashContext $internal_context) | ||
{ | ||
$this->internalContext = $internal_context; | ||
} | ||
|
||
/** | ||
* Initialize an incremental hashing context. | ||
* | ||
* @throws Psl\Exception\InvariantViolationException If the given algorithm is unsupported. | ||
* | ||
* @psalm-pure | ||
*/ | ||
public static function forAlgorithm(string $algorithm): Context | ||
{ | ||
Psl\invariant(Arr\contains(algorithms(), $algorithm), 'Expected a valid hashing algorithm, "%s" given.', $algorithm); | ||
$internal_context = hash_init($algorithm); | ||
|
||
return new self($internal_context); | ||
} | ||
|
||
/** | ||
* Initialize an incremental HMAC hashing context. | ||
* | ||
* @throws Psl\Exception\InvariantViolationException If the given algorithm is unsupported. | ||
* | ||
* @psalm-pure | ||
*/ | ||
public static function hmac(string $algorithm, string $key): Context | ||
{ | ||
Psl\invariant(Arr\contains(Hmac\algorithms(), $algorithm), 'Expected a hashing algorithms suitable HMAC, "%s" given.', $algorithm); | ||
Psl\invariant(!Str\is_empty($key), 'Expected a non-empty shared secret key.'); | ||
|
||
$internal_context = hash_init($algorithm, HASH_HMAC, $key); | ||
|
||
return new self($internal_context); | ||
} | ||
|
||
/** | ||
* Pump data into an active hashing context. | ||
*/ | ||
public function update(string $data): Context | ||
{ | ||
/** @var HashContext $internal_context */ | ||
$internal_context = hash_copy($this->internalContext); | ||
hash_update($internal_context, $data); | ||
|
||
return new self($internal_context); | ||
} | ||
|
||
/** | ||
* Finalize an incremental hash and return resulting digest. | ||
*/ | ||
public function finalize(): string | ||
{ | ||
/** @var HashContext $internal_context */ | ||
$internal_context = hash_copy($this->internalContext); | ||
|
||
return hash_final($internal_context, false); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Hash\Hmac; | ||
|
||
use function hash_hmac_algos; | ||
|
||
/** | ||
* Return a list of registered hashing algorithms suitable for `Psl\Hash\Hmac\hash()` | ||
* | ||
* @psalm-return list<string> | ||
* | ||
* @psalm-pure | ||
*/ | ||
function algorithms(): array | ||
{ | ||
/** @psalm-suppress ImpureFunctionCall - hash_hmac_algos is pure. */ | ||
return hash_hmac_algos(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Hash\Hmac; | ||
|
||
use Psl; | ||
use Psl\Hash; | ||
|
||
/** | ||
* Generate a keyed hash value using the HMAC method. | ||
* | ||
* @throws Psl\Exception\InvariantViolationException If the given algorithm is unsupported. | ||
* | ||
* @psalm-pure | ||
*/ | ||
function hash(string $data, string $algorithm, string $key): string | ||
{ | ||
return Hash\Context::hmac($algorithm, $key)->update($data)->finalize(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Hash; | ||
|
||
use function hash_algos; | ||
|
||
/** | ||
* Return a list of registered hashing algorithms. | ||
* | ||
* @psalm-return list<string> | ||
* | ||
* @psalm-pure | ||
*/ | ||
function algorithms(): array | ||
{ | ||
/** @psalm-suppress ImpureFunctionCall - hash_algos is pure. */ | ||
return hash_algos(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Hash; | ||
|
||
use function hash_equals; | ||
|
||
/** | ||
* Timing attack safe string comparison. | ||
* | ||
* @psalm-pure | ||
*/ | ||
function equals(string $known_string, string $user_string): bool | ||
{ | ||
return hash_equals($known_string, $user_string); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Hash; | ||
|
||
use Psl; | ||
|
||
/** | ||
* Generate a hash value (message digest). | ||
* | ||
* @throws Psl\Exception\InvariantViolationException If the given algorithm is unsupported. | ||
* | ||
* @psalm-pure | ||
*/ | ||
function hash(string $data, string $algorithm): string | ||
{ | ||
return Context::forAlgorithm($algorithm)->update($data)->finalize(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Tests\Hash; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Psl\Hash; | ||
|
||
use function hash_algos; | ||
|
||
final class AlgorithmsTest extends TestCase | ||
{ | ||
public function testAlgorithms(): void | ||
{ | ||
static::assertSame(hash_algos(), Hash\algorithms()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Tests\Hash; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Psl\Exception\InvariantViolationException; | ||
use Psl\Hash; | ||
|
||
final class ContextTest extends TestCase | ||
{ | ||
public function testForAlgorithm(): void | ||
{ | ||
$context = Hash\Context::forAlgorithm('md5') | ||
->update('The quick brown fox ') | ||
->update('jumped over the lazy dog.'); | ||
|
||
static::assertSame('5c6ffbdd40d9556b73a21e63c3e0e904', $context->finalize()); | ||
} | ||
|
||
public function testForAlgorithmThrowsForInvalidAlgorithm(): void | ||
{ | ||
$this->expectException(InvariantViolationException::class); | ||
$this->expectExceptionMessage('Expected a valid hashing algorithm, "base64" given.'); | ||
|
||
Hash\Context::forAlgorithm('base64'); | ||
} | ||
|
||
public function testHmac(): void | ||
{ | ||
$context = Hash\Context::hmac('md5', 'secret') | ||
->update('The quick brown fox ') | ||
->update('jumped over the lazy dog.'); | ||
|
||
static::assertSame('7eb2b5c37443418fc77c136dd20e859c', $context->finalize()); | ||
} | ||
|
||
public function testHmacThrowsForInvalidAlgorithm(): void | ||
{ | ||
$this->expectException(InvariantViolationException::class); | ||
$this->expectExceptionMessage('Expected a hashing algorithms suitable HMAC, "base64" given.'); | ||
|
||
Hash\Context::hmac('base64', 'secret'); | ||
} | ||
|
||
public function testHmacThrowsForEmptySecretKey(): void | ||
{ | ||
$this->expectException(InvariantViolationException::class); | ||
$this->expectExceptionMessage('Expected a non-empty shared secret key.'); | ||
|
||
Hash\Context::hmac('sha1', ''); | ||
} | ||
|
||
public function testContextIsImmutable(): void | ||
{ | ||
$first = Hash\Context::forAlgorithm('md5'); | ||
$second = $first->update('The quick brown fox '); | ||
$third = $second->update('jumped over the lazy dog.'); | ||
|
||
static::assertNotSame($first, $second); | ||
static::assertNotSame($second, $third); | ||
static::assertNotSame($third, $first); | ||
|
||
static::assertSame('d41d8cd98f00b204e9800998ecf8427e', $first->finalize()); | ||
static::assertSame('c4314972a672ded8759cafdca9af3238', $second->finalize()); | ||
static::assertSame('5c6ffbdd40d9556b73a21e63c3e0e904', $third->finalize()); | ||
} | ||
|
||
public function testContextIsStillValidAfterFinalization(): void | ||
{ | ||
$context = Hash\Context::forAlgorithm('md5') | ||
->update('The quick brown fox ') | ||
->update('jumped over the lazy dog.'); | ||
|
||
static::assertSame('5c6ffbdd40d9556b73a21e63c3e0e904', $context->finalize()); | ||
static::assertSame('5983132dd3e26f51fa8611a94c8e05ac', $context->update(' cool!')->finalize()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Tests\Hash; | ||
|
||
use Generator; | ||
use PHPUnit\Framework\TestCase; | ||
use Psl\Hash; | ||
|
||
final class EqualsTest extends TestCase | ||
{ | ||
/** | ||
* @dataProvider provideEqualsData | ||
*/ | ||
public function testEquals(bool $expected, string $known_string, string $user_string): void | ||
{ | ||
static::assertSame($expected, Hash\equals($known_string, $user_string)); | ||
} | ||
|
||
/** | ||
* @psalm-return Generator<int, array{0: bool, 1: string, 2: string}, mixed, void> | ||
*/ | ||
public function provideEqualsData(): Generator | ||
{ | ||
yield [true, 'hello', 'hello']; | ||
yield [false, 'hey', 'hello']; | ||
yield [false, 'hello', 'hey']; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Tests\Hash; | ||
|
||
use Generator; | ||
use PHPUnit\Framework\TestCase; | ||
use Psl\Exception\InvariantViolationException; | ||
use Psl\Hash; | ||
|
||
final class HashTest extends TestCase | ||
{ | ||
/** | ||
* @dataProvider provideHashData | ||
*/ | ||
public function testHash(string $expected, string $data, string $algorithm): void | ||
{ | ||
static::assertSame($expected, Hash\hash($data, $algorithm)); | ||
} | ||
|
||
public function testHashThrowsForUnsupportedAlgorithm(): void | ||
{ | ||
$this->expectException(InvariantViolationException::class); | ||
|
||
Hash\hash('Hello', 'base64'); | ||
} | ||
|
||
/** | ||
* @psalm-return Generator<int, array{0: string, 1: string, 2: string}, mixed, void> | ||
*/ | ||
public function provideHashData(): Generator | ||
{ | ||
yield ['2aae6c35c94fcfb415dbe95f408b9ce91ee846ed', 'hello world', 'sha1']; | ||
yield ['5eb63bbbe01eeed093cb22bb8f5acdc3', 'hello world', 'md5']; | ||
} | ||
} |
Oops, something went wrong.