Skip to content

Instantly share code, notes, and snippets.

@jakubboucek
Created May 8, 2020 17:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jakubboucek/3938f12b43e10a372fb822b09d1b561c to your computer and use it in GitHub Desktop.
Save jakubboucek/3938f12b43e10a372fb822b09d1b561c to your computer and use it in GitHub Desktop.
AutoPassword
<?php
declare(strict_types=1);
class AutoPasswords
{
/** @var IPasswords[] */
private $passwords;
/** @var IPasswords */
private $defaultPasswords;
/**
* @param IPasswords[] $passwords
* @param IPasswords $defaultPasswords
*/
public function __construct(array $passwords, IPasswords $defaultPasswords)
{
$this->passwords = $passwords;
$this->defaultPasswords = $defaultPasswords;
}
public function matchHash(string $hash, bool $isTransientPassword): IPasswords
{
foreach ($this->passwords as $passwords) {
$matched = $passwords->matchHash($hash, $isTransientPassword);
if ($matched instanceof IPasswords) {
return $matched;
}
}
throw new NotMatchPasswordsException('Unable to match right password hast algoritm for hash');
}
/**
* Hash password with default passwords (most secure hash)
*
* @param string $password
* @return string
*/
public function hash(string $password): string
{
return $this->defaultPasswords->hash($password);
}
}
<?php
declare(strict_types=1);
use Nette\Security\Passwords;
class Bcrypt implements IPasswords
{
/** @var Passwords */
private $passwords;
public function __construct(Passwords $passwords)
{
$this->passwords = $passwords;
}
public function matchHash(string $hash, bool $isTransientPassword): ?IPasswords
{
// Not transient (bcrpyt + sha1) and not old version (sha1)
$isMatched = $isTransientPassword === false && strpos($hash, '$2y$') === 0;
return $isMatched ? $this : null;
}
public function hash(string $password): string
{
return $this->passwords->hash($password);
}
public function verify(string $password, string $hash): bool
{
return $this->passwords->verify($password, $hash);
}
public function needsRehash(string $hash): bool
{
return $this->passwords->needsRehash($hash);
}
}
<?php
/**
* Toto je example pro použití tříd. Vyžaduje composer balíček "nette/security".
*
* Je to systém použitelný pro přehashování hesel z SHA1 na BCRYPT. Vzhledem k tomu, že nelze získat původní
* podobu hesel, jsou hesla v SHA1 zahashována do tvaru `password_hash(sha1($heslo))` - tento stav
* se nazývá Transient (přechodný).
*
* Níže uvedený kód umí detekovat typ použitého hashe a vrátí knihovnu, která s danám hashem umí pracovat.
*
* Jsou 3 typy hashů:
* - Bcrypt => to je požadovaný hash. Kdykoliv je potřeba zahashovat heslo, MUSÍ se zvolit tento algoritmus
* - Sha1 => původníhash - nyní je použitelný pouze pro ověření hesla.
* - Transient => Přechodný algoritmus hashe získaný pomocí `password_hash(sha1($heslo))`
*
* Protože je
*/
$bcrypt = new Bcrypt(new Nette\Security\Passwords());
$sha1 = new Sha1('any-security-salt');
$transient = new Transient($bcrypt,$sha1);
$autoPassword = new AutoPasswords([$bcrypt, $transient, $sha1], $bcrypt);
// =========== zahashování hesla
$hash = $autoPassword->hash('tajne-heslo');
// =========== ověření hesla
// hash hesla z DB
$hash = '$2y$10$Mu3jFu5VtyzKKOKRju5QduGKEYXi11mvcSYx.L2f98tuuppk5uoHe';
$isTransient = true/false; // určuje, zda je heslo bcrypt(heslo) a nebo bcrypt(sha1)
$verifier = $autoPassword->matchHash($hash, $isTransient);
$isValidPassword = $verifier->verify('tajne-heslo', $hash);
// Chybné heslo - vykopnem další zpracování
if($isValidPassword !== true) {
throw new Exception("špatné heslo");
}
// zjistíme, zda bychom měli heslo přehashovat
if($verifier->needsRehash($hash)) {
// využijeme příležitosti, že nám uživatel zadal heslo a rovnou ho přehashujeme do správného tvaru
$hash = $autoPassword->hash('tajne-heslo');
$isTransient = false;
// uložíme do DB nový hash
save($hash, $isTransient);
}
<?php
declare(strict_types=1);
interface IPasswords
{
public function matchHash(string $hash, bool $isTransientPassword): ?IPasswords;
public function hash(string $password): string;
public function verify(string $password, string $hash): bool;
public function needsRehash(string $hash): bool;
}
<?php
declare(strict_types=1);
use RuntimeException;
class NotMatchPasswordsException extends RuntimeException
{
}
<?php
declare(strict_types=1);
class Sha1 implements IPasswords
{
/** @var string */
private $securitySalt;
public function __construct(string $securitySalt)
{
$this->securitySalt = $securitySalt;
}
public function matchHash(string $hash, bool $isTransientPassword): ?IPasswords
{
// Is sha1, isTransientPassword doesn't matter
$isMatched = (bool)preg_match('/^[a-f0-9]{40}$/D', $hash);
return $isMatched ? $this : null;
}
public function hash(string $password): string
{
return sha1($password . $this->securitySalt);
}
public function verify(string $password, string $hash): bool
{
return hash_equals($hash, $this->hash($password));
}
public function needsRehash(string $hash): bool
{
// Deprecated alg
return true;
}
}
<?php
declare(strict_types=1);
class Transient implements IPasswords
{
/*** @var Sha1 */
private $sha1;
/** @var Bcrypt */
private $passwords;
public function __construct(Bcrypt $passwords, Sha1 $sha1)
{
$this->sha1 = $sha1;
$this->passwords = $passwords;
}
public function matchHash(string $hash, bool $isTransientPassword): ?IPasswords
{
// Is transient (bcrpyt + sha1) and not old version (sha1)
$isMatched = $isTransientPassword === true && strpos($hash, '$2y$') === 0;
return $isMatched ? $this : null;
}
public function hash(string $password): string
{
return $this->passwords->hash($this->sha1->hash($password));
}
public function verify(string $password, string $hash): bool
{
return $this->passwords->verify($this->sha1->hash($password), $hash);
}
public function needsRehash(string $hash): bool
{
// Deprecated alg
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment