Hashing and encryption
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 |
Passwords
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 |
- After a successful login,
padHashNeedsRehashsays 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. - A password that is no text, or a hash that is none - the
NULLof a user that does not exist - simply does not match:padHashCheckanswersFALSE, never an error.
Sealed values
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 |
- The value is JSON-encoded and sealed with sodium's
secretboxand a random nonce, so the same value sealed twice gives two different payloads. - A payload that was changed in a single character, cut short, or sealed with another key opens to the default -
NULLunless one is given. An object comes back as an array.
Signed links
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 ] );
- The expiry is a number of seconds, a
DateIntervalor a moment such as'+2 days'; without one the link never expires. - A value changed, added or removed, another page, an expiry moved or passed - each makes the signature invalid. The order of the values does not count, and the link holds in the clean form too.
- The link is absolute, since it is mostly mailed; the values are visible in it - sign what must not change, seal what must not be read.
The key
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.