Intro
As of 2012-06-28, many sites (like LinkedIn and Last.fm) have recently reportedly had their user databases hacked and usernames and password exposed. Tiki has mostly recently used per-user one-time salted hashes, using PHP's crypt() CRYPT_MD5 option. Inspired by fear, petjal (Pete Jalajas) began to implement CRYPT_BLOWFISH to better protect user passwords. Work was completed on 2012-06-30 with svn commit r42170 for Tiki 9.x but reverted because it was done in the stable branch and something of this nature should first be done in trunk.
From: http://php.net/manual/en/function.crypt.php
CRYPT_BLOWFISH - Blowfish hashing with a salt as follows: "$2a$", a two digit cost parameter, "$", and 22 digits from the alphabet "./0-9A-Za-z". Using characters outside of this range in the salt will cause crypt() to return a zero-length string. The two digit cost parameter is the base-2 logarithm of the iteration count for the underlying Blowfish-based hashing algorithmeter and must be in range 04-31, values outside this range will cause crypt() to fail.
If you have any questions or suggestions about any of this, please ask!
Properties
This CRYPT_BLOWFISH implementation has the following properties.
- Uses a hashing algorithm that is designed to be slow (with variable key stretching). MD5, SHA1, etc, were designed to be fast, and thus are weak for password-protection purposes.
- Uses /dev/random (or Windows CAPICOM, "Crypto API COM") to generate cryptographically strong random numbers for salt generation. /dev/random has the potential for being "locked" by other processes, but is chosen for use here because of it is deemed to have the highest entropy. Experience during this development indicates that $systemSalt_tiki calculation using /dev/random hangs for a few seconds when rapidly calculated one right after one another a few times, likely because the random pool is drained and is waiting to refill.
- Does not fall back to a weaker source of random bytes. If a cryptographically very strong random source is not available, I'd rather fail and throw an error, than fall-back to a less secure source and give the user/admin a false sense of security. If they choose a weaker source to begin with, that's fine. I don't want a tiki admin to select crypt-blowfish only to click through some warnings about how it had to fall-back to using mt_rand(); they would think that they are using strong password salting, but they would not be.
- A new cryptographically very strong random salt is created unique for each user and a new one is created upon each password change. You might call it a "per-password salt".
- In addition, separately, not part of CRYPT_BLOWFISH, but implemented concurrently, a single permanent site-wide $systemSalt_tiki has been added to db/local.php during installation. This $systemSalt_tiki is also cryptographically very strong from /dev/random. Just another layer in the security onion. Currently set to 100 bytes; could easily be made to be much stronger, with little if any performance hit, I believe. From the web: the salt should be random string with at least as many variable bits, as there are bits in the hash result.
- Tiki (lib/userslib.php hash_pass()) smartly handles old password formats currently in the site database. Password hashes are (only) upgraded when users change their passwords. Site admins are advised to reset all passwords and have all users create new ones upon next login.
- Similarly, tiki smartly handles changes in CRYPT_BLOWFISH cost parameter.
- Is designed to better protect against database-only attacks (sqli, xss, misplaced db backups), using Rainbow tables, dictionary, and brute-force attacks.
- Perhaps the most important part of this is the selection of the best available random number generator. The PHP rand() and mt_rand() functions are universally considered weak and should not be used for this purpose.
- Not sure of the OS, PHP, MySQL or Tiki version requirements.
- Not sure of TIki upgrade path regressions this might cause.
- Presuming that we don't create, change or validate passwords very often, so a little slow performance is worth the massive security improvement.
- The hash field in the tiki database users_users table, contains something like:
$2a$15$6zZPPaJ90Cz43TVT3ODTqem3nhtxyA8tEgusysAddIFsQxBqwn4X6
Where the $2a$ indicates it's a blowfish hash. The $15$ indicates it is set to a key stretching cost factor of 2^15 = 32,768 rounds. After the $15$, the next 30 or so characters are the salt, and the remaining of the 60 total characters are what could be considered the hashed password. That takes about 4 seconds on my server. If set to 13, it's a fraction of a second, but only gives 8192 key-stretching rounds. 13,14, or 15 (or more) are probably good numbers to choose for your site. - Hasn't been tested at all on Windows; has only been tested on CentOS.
Changed files
The following files were changed for this commit:
- 20120630_patch_users-users-hash-varchar60_tiki.sql: MODIFY COLUMN `hash` varchar(60)
- See: Database Schema Upgrade. To be located in installer/schema/.
- lib/userslib.php: added CRYPTO_BLOWFISH to hash_pass().
- lib/prefs/feature.php: added crypto-blowfish option to feature_crypt_passwords and make it the default.
- installer/tiki-installer.php: added calculate $systemSalt_tiki and add it to db/local.php during installation only.
db/local.php: added $systemSalt_tikI (inserted by installer/tiki-installer.php during installation). - templates/admin/include_login.tpl: added Copy to clipboard{preference name=pass_blowfish_cost}
- lib/prefs/pass.php: added pass_blowfish_cost.
- db/tiki.sql: arildb added `hash` varchar(60) default NULL,
$systemSalt_tiki
- This variable value is automatically generated during installation, and the resultant value is inserted into db/local.php
- If you change this value in db/local.php, all your passwords will be no long work, including Admins. Do not change it after install; if you do, you will have to rehash a new admin password and paste it manually into your MySQL users_users table.
- Technically, this variable is not part of CRYPT_BLOWFISH. Use of this variable during tiki password hashing augments the security that is provided separately by the CRYPT_BLOWFISH password hashing which uses unique per-user per-password salts.
- This variable should be kept secret, much like $pass_tiki in db/local.php.
- db/local.php should be chmod 660 on shared hosting especially to prevent other users from seeing db password and $systemSalt_tiki.
- Should be as long a possible that doesn't interfere with performance. 60-100 bytes? Currently set to 100 bytes.
- Should be as complex as possible, including many of each kind of character (upper, lower, numbers, special characters).
- Tried several encodings (uuencode, quoted_printable), etc, but base64 [azAZ09+/] works best. Strong enough; simple characters.
- Looks like:
Copy to clipboard$systemSalt_tiki='KlbWqJRELAMADEPotrff/8I5LghTfCkhk2/h5dAqSEQXOpGiOzYFlqz5qLiJ';
- Is appended to the user password before passing into crypt() along with the one-time per-user salt, like:
crypt($pass . $systemSalt_tiki, $salt);
References
- http://www.php.net/manual/en/function.mt-rand.php#83655
- https://en.wikipedia.org/wiki//dev/random
- http://blog.mozilla.org/webappsec/2011/05/10/sha-512-w-per-user-salts-is-not-enough/
- http://fredericiana.com/2012/06/08/lets-talk-about-password-storage/
- http://stackoverflow.com/ was a great resource.
ToDo
- Any benefit, or reason not, in creating a completely separate database for some small amount of highly sensitive Tiki info? Not hard to do, doesn't take up space, might even help performance, might slow down attackers?
- Extract random number generation, salt generation, hashing into separate tiki libs?
- Use MySQL aes_encrypt to protect usernames, email addresses, etc.
- Is there a bug in the installer or elsewhere the fails to create, or deletes, the closing php tag in db/local.php?
- Other uses of MD5 in tiki should be reviewed. Emailing MD5 hashes of passwords should be considered risky if they exist.
- If using /dev/random (or /dev/urandom) and/or Windows CAPICOM are ever deemed unacceptable, we can use:
//http://php.net/manual/en/function.openssl-random-pseudo-bytes.php: openssl_random_pseudo_bytes($numRandomBytes, $cstrong); $hex = bin2hex($bytes); or https://www.random.org/clients/http/archive/
- CAPICOM is apparently deprecated.
- I haven't tested on Windows.
- Clean out code of old unsafe hash-creation options (keep old hash-reading options).
- Use MySQL aes_encrypt to protect usernames, email addresses, etc? Are they strong enough? Should we use MySQL hashing tools?
- Client-side hashing to protect username, password over non-SSL connections?
- Use Zend tools? My quick research indicated use of MD5 or just wrapper code.
Miscellaneous
- $ find . -iname "*hash*" -o -iname "*rand*" | grep -i sec | grep -v -e \.svn
- ./lib/phpseclib/Crypt/Random.php (uses /dev/urandom, not /dev/random)
- ./lib/phpseclib/Crypt/Hash.php
- ./lib/phpsec/phpsec/phpsec.hash.php
- ./lib/phpsec/phpsec/phpsec.rand.php (uses openssl_random_pseudo_bytes and mcrypt_create_iv; falls back to mt_rand if needed, which I find unacceptable; if I want or need to use mt_rand, I can easily just call it directly, perhaps by just selecting the crypt-md5 tiki Encryption method option instead of the stronger crypt-blowfish option.)
- From the web: "uniqid is not cryptographically secure, it is designed specifically to never return the same number twice"
- $ php -r 'print_r(hash_algos());'
Note: phpsec has been deprecated: https://sourceforge.net/p/tikiwiki/code/61780
Interesting links
- Telecom TV dot Com
- Intro video: Short History of ICT at ITU Telecom World 2012 (very informative)
- Why Antivirus Companies Like Bitcoin Failed to Catch Flame *+ (**) and Stuxnet
- Breadcrumb (commercial)
- Password safe by Yubico
- Hackers fin (Rony's Kay being commented in the article)