Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/update hashing - Add password hasher #18

Merged
merged 36 commits into from
Oct 6, 2016

Conversation

t0mmie
Copy link
Member

@t0mmie t0mmie commented Oct 3, 2016

Jira https://angrybytes.atlassian.net/browse/ABC-564

Changelog

2.0.0

New Password Hasher

Version 2.0.0 comes with the new AngryBytes\Hash\Hasher\Password hasher. This hasher is intended to be used for
hashing passwords and other secure tokens. The hasher utilises PHP's native password hashing functions like
password_hash() and password_verify(), see Password Hashing.

The password hasher has been setup to use PHP's default cost and algorithm. The default cost can however be overwritten
via the setCost() method, like so:

$hasher = new \AngryBytes\Hash\Hasher\Password;
$hasher->setCost(15);

The password hasher offers a method to check if an existing hash needs to be rehashed. This can be the case if
the cost and/or algorithm of the hasher has changed. If this is the case you'll want to rehash and store your existing hashes.

Example

In this example we will check if an existing password hash needs to be rehashed, if so we'll hash the password value.
Note, in order to rehash the password you will need access to the plaintext value. It's advised to to this after
a user has successfully entered their password.

use AngryBytes\Hash\Hasher\Password;
use AngryBytes\Hash\Hash;

// Create a password hasher
$hasher = new Hash(
    new Password()
);

$password = 'plaintext password'
$passwordHash = 'password hash';

// Verify the password against the hash
if ($hasher->verify($password, $passwordHash)) {
    // Check if the hash needs to be rehashed
    if ($hasher->getHasher()->needsRehash($passwordHash)) {
        // Rehash the password
        $passwordHash = $hasher->hash($password);
    }
}

Refactored AngryBytes\Hash\HasherInterface

AngryBytes\Hash\HasherInterface::verify() is a new method for verify an existing hash. The method accepts the
following three arguments:

  • $data - The data that needs to be hashed.
  • $hash - The hash that needs to match the hashed value of $data.
  • $salt - The salt used for hashing.

Refactored AngryBytes\Hash\Hash

AngryBytes\Hash\Hash::construct() second argument ($salt) has become optional to accommodate for hashers that
handle their own salt, like AngryBytes\Hash\Hasher\Password.

AngryBytes\Hash\Hash::hash() and AngryBytes\Hash\Hash::shortHash() no longer accept any number of arguments but
only one. This single argument can however be of any type. All non-scalar types will be serialized before hashing.

AngryBytes\Hash\Hash::verify() is a new method that's available to validate a hash in a time-safe manner.
The method accepts the following two arguments:

  • $data - The data that needs to be hashed. This data can be of any type, all non-scalar types will be
    serialized before hashing.
  • $hash - The hash that needs to match the hashed value of $data.

AngryBytes\Hash\Hash::matchesShortHash() is replaced by AngryBytes\Hash\Hash::verifyShortHash() this methods
accepts two arguments ($data and $hash) just like AngryBytes\Hash\Hash::verify().

Minor Changes

  • Scalar values passed to hash() and shortHash() are no longer serialized.
  • compare() Now uses PHP native hash_equals() function.
  • Fixed namespace issues for AngryBytes\Hash\HMAC.

Upgrading

Please refer to the upgrade notes.

Deprecated & Removed Components

AngryBytes\Hash\RandomString has been removed.

@ivarb
Copy link
Contributor

ivarb commented Oct 3, 2016

Zie kapotte build. Zo te zien falen de unit tests.

@ivarb
Copy link
Contributor

ivarb commented Oct 3, 2016

Wil je ook even kijken naar de composer.json en test file zodat hij PHP 5.6+ compatible wordt en de laatste unit test versie gebruikt.

@t0mmie
Copy link
Member Author

t0mmie commented Oct 3, 2016

@ivarb Composer op PHP 5.6+ gezet maar hij blijft testen voor 5.3, 5.4 en 5.5. Kan dat uit? :)

**/
public function __construct(HasherInterface $hasher, $salt)
public function __construct(HasherInterface $hasher, $salt = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Liever null als default waarde voor optionele params.

'Provided salt "%s" is not long enough. A minimum length of 20 characters is required',
$salt
));
if ($salt) {
Copy link
Contributor

@ivarb ivarb Oct 4, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (is_string($salt) && strlen($salt) < 20))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

....doh 🙈

* @return string
**/
public function shortHash()
public function shortHash($data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waarom doe je niet ...$data ipv $data? Dan kan je namelijk gewoon blijven doen shortHash(foo, bar, baz)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bij hash en verify zou dat inderdaad kunnen, shortHash zet die data direct door naar hash.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...$data en dan foo(...$data)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solved

{
return serialize(func_get_args());
if (is_scalar($data)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boolean is ook scalar, weet niet of dat goed gaat aangezien het geen string is? Misschien moet je return (string) $data; doen voor de zekerheid. Dan wordt true -> 1 en blijft false ''

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hij geeft nu inderdaad een boolean terug, zal de typehint toevoegen

* @return string
**/
* {@inheritDoc}
*/
public function hash($data, $salt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kan je $data niet beter $string noemen? Meer een stijl dingetje.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Klinkt wel logischer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

laat maar staan hoor

@@ -31,4 +31,14 @@
* @return string
**/
public function hash($data, $salt);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moet $salt hier niet default null zijn oid?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik denk het niet, voor de md5 hasher is de salt verplicht maar voor de password hasher weer niet.

use AngryBytes\Hash\Hasher\Password as PasswordHasher;

/**
* PasswordTest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1e regel graag summary maken.

$options['salt'] = $salt;
}

$hash = password_hash($data, self::ALGORITHM, $options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waarom doe je niet PASSWORD_DEFAULT. Nu is het beetje dubbel?
En ik zou met die vergelijking gewoon positive check doen: if ($hash === false)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik had met het idee zitten spelen om dat ook instelbaar te maken maar ik denk dat de default beter is. Is nu inderdaad een beetje dubbel.

@ivarb
Copy link
Contributor

ivarb commented Oct 4, 2016

@angrytom ik start de review vast, anders vergeet ik het weer :P

@ivarb
Copy link
Contributor

ivarb commented Oct 4, 2016

Ik zou ook nog even de class docs nalopen, zie nu veel classes met classname als 1e class doc regel, waar dit beter summary kan zijn.

Qua gebruik moeten we misschien even kijken naar Hash vs Hasher. Was nooit zon fan van de naam Hash, maar dit is meer een helper om een Hasher heen. Je kan dus in de meeste gevallen ook een Hasher gebruiken zonder Hash denk ik.

@ivarb
Copy link
Contributor

ivarb commented Oct 4, 2016

Als je in https://github.com/angryTom/hash/blob/feat/update-hashing/src/AngryBytes/Hash/HMAC.php#L97 gebruikt maakt van variadic functions kan de bestaande logica wat simpler:

public function hmac($sharedSecret, ...$args) {

@ivarb
Copy link
Contributor

ivarb commented Oct 4, 2016

Ik zie ook nog veel (bestaande) public getter/setters. vraag me af of deze allemaal nog nodig zijn of /danwel public moeten zijn. Setter wordt vaak niet gebruikt omdat het via constructor gaat, en getter wordt vaak alleen intern gebruikt en hoeft niet echt voor public API beschikbaar te zijn.

));
if ($salt) {
// Make sure it's of sufficient length
if (strlen($salt) < 20) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik denk dat je salt max length ook moet checken tegen CRYPT_SALT_LENGTH, zie crypt() docs.

* @throws RuntimeException If the hashing fails
* @param string|bool $salt (optional) When omitted `password_hash()` will generate it's own salt
*/
public function hash($data, $salt = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dit lijkt niet compatible met de Interface?

* @param int|null $cost
* @return $this
*/
public function setCost($cost)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is het niet makkelijker dit ook als optionele contructor arg op te nemen zodat je dit tijdens aanmaken al kan zetten?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aan de andere kant, het is een optionele override, misschien ook maar laten staan zo, aangezien het niet per see aangemoedigd wordt.

*/
public function setCost($cost)
{
if (is_null($cost)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waarom is dit toegestaan? Als het toegestaan is, dan moet je denk ik wel $this->cost overschrijven. De vraag is of we cost overschrijfbaar moeten maken nadat een hasher gemaakt is.

* @param string $salt
* @return bool
*/
public function verify($data, $hash, $salt);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kunnen we hier niet, net zoals bij password_hash de 2e (en hier 3e) arg array $options = [] maken zodat daar meerdere opties (e.g. salt) in kunnen zonder dat bij uitbreidingen de API (interface) aangepast hoeft te worden?

* @return string
**/
private function getDataString()
private function getDataString($data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deze kan static zijn.

@@ -79,7 +74,7 @@ public function setHasher(HasherInterface $hasher)
/**
* Get the salt
*
* @return string
* @return string|bool
*/
public function getSalt()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik denk dat get/setSalt protected mogen zijn.

@ivarb
Copy link
Contributor

ivarb commented Oct 4, 2016

Heb nu review gedaan (moet nog testen). Kan je iig kijken om nog een paar dingen op te schonen en wat gelijk te trekken?

@t0mmie
Copy link
Member Author

t0mmie commented Oct 5, 2016

@ivarb bijgewerkt

@ivarb
Copy link
Contributor

ivarb commented Oct 6, 2016

@angrytom added some doc updates t0mmie#3

*/
protected function setSalt($salt)
{
if (is_string($salt) && strlen($salt) < 20 && strlen($salt) > CRYPT_SALT_LENGTH) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dit moet een || ipv && zijn.

@scrutinizer-notifier
Copy link

The inspection completed: 1 Issues, 1 Patches

@ivarb ivarb merged commit babfa56 into AngryBytes:master Oct 6, 2016
@t0mmie t0mmie deleted the feat/update-hashing branch October 10, 2016 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants