Your salt doesn't need to be perfectly random, it just needs to be unique for each of your users. See this question on Crypto.SE for more. That's why some websites use the username to derive the salt from (ie
In your case, when crypt says 'blowfish hash' it really means bcrypt so you also want to keep up with Moore's law when selecting the cost factor.
There are a few observations I made on your code though. First,
strlen() does not include the NULL character at the end so
strlen($blowfishCharacters) - 1; makes sure you'll never see a
/ in your salt. I'd also suggest using
range() when building the array, but that's my personal preference. You can also completely do away with the loop since we don't like re-inventing the wheel (right?). Therefore your code now looks like this:
$salt=array_merge(range('a', 'z'), range('A', 'Z'), ['.', '/']); //generate the dictionary
$salt=array_slice($salt, 0, 21); //cut to size
This will return a
$salt array with 21 elements. The 22nd element required by
crypt is actually the delimiter
To answer your original question: According to
mt_rand()'s man page
By default, PHP uses the libc random number generator with the rand() function. The mt_rand() function is a drop-in replacement for this. It uses a random number generator with known characteristics using the » Mersenne Twister, which will produce random numbers four times faster than what the average libc rand() provides.
so it's not more "secure" it just runs faster.
In conclusion, you don't need a crypto-grade PRF to derive the salt from. That's because a salt guards against pre-computed hash tables. If you don't have a salt, the adversary can pre-generate a table and compare it against your entire database to find a match. The salt makes sure the adversary has to do that process for each entry in the database, thus making the attack extremely expensive with a high enough cost factor.
Of course setting the cost too high will make your server struggle when lots of users try to log in at the same time but I'd trade some risk of inconvenience for security any time.
So the answer to your question is
mt_rand() is good enough
Edit: This article is a good guide on bcrypt hygiene. Quoting section #2,
A perfect source would be /dev/urandom. Other sources would be mcrypt_create_iv when paired with MCRYPT_DEV_URANDOM. If neither of them are available, you can fall back to openssl_random_pseudo_bytes or mt_rand (at an absolute last resort).