note 121508 deleted from function.hash-equals by cmb
| From: | cmb@php.net | Date: | Thu, 29 Jul 2021 09:31:26 +0000 |
| Subject: | note 121508 deleted from function.hash-equals by cmb | ||
| References: | 1 | Groups: | php.notes |
| Request: | Send a blank email to php-notes+get-216679@lists.php.net to get a copy of this message | ||
Note Submitter: enclaved
----
This is a WARNING to everyone!
Using hash_equals() is fine in itself, but the NEED for such comparison isn't, and should be an
alarm to you that your PHP program design is severely flawed somewhere. The golden rule has it:
NEVER do any sort of crypto in PHP in the first place. Cryptography doesn't belong in PHP code
for a variety of reasons.
First of all, it is a very sloppy and insecure enterprise; I keep seeing script kiddos trying to
roll their own crypto again and again without a iota of understanding. Homemade crypto appliance is
worse than no crypto, it is pure vice. If you're not a professional, well-educated, experienced
cryptographer, then forget about writing your own crypto code forever, don't even dare to
consider it before you master the subject in every tiny aspect, because you WILL make mistakes.
Dangerous, fatal mistakes. To add insult to injury, in PHP security flaws may be exceptionally
non-obvious due to the complexity of Zend--the chances are there will always be a side channel
attack opportunity, and you'll never figure it out, because your code MAY appear bug-less, but
some internal aspect of the Zend framework still opens a side channel attack vector. Remember:
crypto does not belong in scripting languages because of their very misleading and unpredictable
nature (in terms of cryptographic environm
ents); crypto must be written in solid, compiled, purely deterministic languages like C, C++, and
Fortran.
Second, PHP is neither the right tool nor the right environment for cryptography. In a typical site
system, PHP is not in place to do crypto, because there are better places to do it: the HTTP server
and the RDBMS. Just memorize this single rule of thumb: cryptographic secrets must never cross
subsystem/layer boundaries:
Database <-- ! --> CGI program <-- ! --> HTTP server
Cryptographic tasks are performed either by the HTTP server (e.g. authentication of users with
client SSL certificates) or the RDBMS (e.g. password-based access to data), and these tasks must be
ENCAPSULATED inside the facility, self-contained. For example, if you store KDF-derived digests of
passwords in an SQL database, you must NOT compare digests in PHP, but only in SQL queries or stored
procedures. Once produced and put into the database, a password digest (or any other sensitive data)
must not exit it in any way as-is, be it a SELECT query or some other way, that is considered a leak
in the cryptosystem. Use ONLY database-provided means to perform any crypto operations.
As PostgreSQL is the usual database of choice for technically advanced and sound WWW or intranet
sites, my advice is to use its pgcrypto extension, it is mature, well-tested, and has all the right
tools. Here's a textbook password handling example to illustrate how secrets can be confined
within the database layer without extracting them into the PHP layer. Password digest derivation and
storing:
INSERT INTO account (digest) VALUES (crypt('password', gen_salt('bf')));
Verification:
SELECT digest = crypt('password', gen_salt(digest)) FROM account;
Exceptionally simple, elegant, clean, and secure (Blowfish is more than enough for user-set
passwords), isn't it? You can clearly see that after the initial INSERT (or any subsequent
UPDATEs) the password digest never leaves the RDBMS, that is, never gets trasmitted in any form over
the client-server link, the entire checking procedure is wholly executed by the RDBMS, i.e. it is
encapsulated and isolated. And the best part about it: no PHP involved in crypto! This IS the way to
go if application security is on your checklist.
The SELECT query above performs password checking without disclosing the digest it is comparing
against, which is exactly my point. It would be illogical, impractical, and just stupid to use
hash_equals() in this scenario when the RDBMS itself can do just fine.