Three jobs every application with users meets: keeping passwords so that nobody - the
database's reader included - can read them back; handing a value to the browser that it
cannot read or change; and sending a link that works for the one who got it, until it
expires. PHP has the building blocks - password_hash, sodium,
hash_hmac - and these helpers put them together the safe way.
| function | what it answers |
|---|---|
padHash ( $password ) | the password hashed with password_hash and PHP's default algorithm - the text to store |
padHashCheck ( $password, $hash ) | whether the password matches the hash |
padHashNeedsRehash ( $hash ) | whether the hash was made with weaker settings than PHP's default now |
padEncrypt ( $value ) | the value sealed: JSON-encoded, encrypted and signed, as one URL-safe string |
padDecrypt ( $payload, $default = NULL ) | the value back - or the default for a payload that was changed, cut short or sealed with another key |
padSignedUrl ( $page, $vars = [], $expires = NULL ) | a link to a page of this application with an expiry time and a signature |
padSignatureValid () | whether this request came by such a link, unchanged and not expired |
A password is never stored, only its hash. padHash makes one with a new
random salt each time, so two users with the same password get different hashes;
padHashCheck tells whether a password typed at login matches the stored hash.
|
PHP fragments/hashing_and_encryption_1.php |
HTML fragments/hashing_and_encryption_1.pad |
Result |
|---|---|---|
|
|
Stored: $2y$... 60 characters The right password matches: yes Another password matches: no Needs a new hash: no |
padHashNeedsRehash says whether the stored hash
was made with older settings - a lower cost, an older algorithm. The password is at hand
right then, so the hash can be made again and stored.NULL of a user
that does not exist - simply does not match: padHashCheck answers
FALSE, never an error.padEncrypt seals a value - text, a number, an array: whatever JSON holds -
so it can travel through the browser in a cookie, a hidden field or a link, and come back
unread and unchanged. Anyone can carry the payload; only the application can open it.
|
PHP fragments/hashing_and_encryption_2.php |
HTML fragments/hashing_and_encryption_2.pad |
Result |
|---|---|---|
|
|
Sealed: 95 URL-safe characters Opened: order 1042, step payment One character changed: refused |
secretbox and a random
nonce, so the same value sealed twice gives two different payloads.NULL unless one is given. An object comes back as
an array.A link in a mail - confirm your address, download your invoice - has to
work without a login, for the one who got it, and stop working after a while.
padSignedUrl adds the time it expires and a signature over the application, the
page and every value; padSignatureValid (), in the page the link leads to, says
whether the request came by such a link.
|
PHP fragments/hashing_and_encryption_3.php |
HTML fragments/hashing_and_encryption_3.pad |
Result |
|---|---|---|
|
|
This page came by a signed link: no |
The page the link leads to checks it before anything else:
// invoice.php
if ( ! padSignatureValid () )
padRedirect ( 'link_expired' );
$invoice = db ( "RECORD * FROM invoices WHERE id = {0}", [ $id ] );
DateInterval or a moment such as
'+2 days'; without one the link never expires.Sealing and signing use the application key. Without a setting it is the file
DATA/keys/<application>.key, made the first time it is needed - 32 random
bytes, readable by its owner only, under DATA/ where no URL reaches. Set
$padAppKey in _config/config.php - 32 bytes, or
'base64:' and 32 bytes in base64, the form the key file holds - when several
servers must read each other's values, and keep it out of version control. A new key makes
every value sealed and every link signed before it unreadable. The error reports never show
it.