The static class Randomness is a collection of helper methods for web apps that need random data for security purposes.
Applications may require Cryptographically Secure (CS) random data Wikipedia CSPRNG to be used in forming, for example, encryption keys, random passwords, session keys, stream initialization vectors, nonces, secure unique IDs, and some kinds of salts.
PHP's mt_rand()
is a simple pseudo-random number gnerator designed
for use in Monte Carlo simulations, not in security systems. It is not
cryptographically secure. You can determine the next random
number from previous ones or from knowing the internal state of the generator.
Most operating systems on which PHP typically runs provide a CSPRNG as a service to applications. On Windows it is called CryptGenRandom. On Linux, OS X, FreeBSD etc. applications may read the /dev/random pseudo-device. Each of these OSs also offers a way for the user to query the status of the CSPRNG. But in PHP, accessing the CSPRNG can be problematic.
Randomness::randomBytes
uses several different approaces to read from
the operating system's CSPRNG. It is possible that all of them may fail. In this
case it has an option to get data from the http://www.random.org service and another
option to fall back on its own non-crypto-secure generator.
There are many tutorials and examples that show storage of passwords in a table.
Often the methods used are substandard and very easy to crack. For example, the
"Agile Web Application Development with Yii1.1 and PHP5"
book's example stores md5($password)
in the DB and calls it
"encryption". It is not. "The Yii Blog Tutorial"
is a little better in
that it uses a salt but it still uses md5 and is easy to crack.
The yii-user
and yii-user-management extensions
are similarly insecure.
Examples of the same errors abound and are by no means limited to webapps implemented in Yii or PHP.
You cannot rely on a user to use a (practically) unguessable password or to not use that password in systems other than yours. And you should not assume that your server is so secure that an attacker cannot get hold of the password file/table or a backup of it.
A very common error I see in what I read and other people's code is fast hashes. MD5, for example, is very fast. As of Nov 2011 you can check 350 million keys per second on a commodity nVidia processor. So no matter what you do with salts, the combination of short passwords and fast brute force checking means your system is open to intruders if you rely on a non-iterated message digest such as MD5 or any of the SHA algos. Most hash fuctions are indeed designed to be fast to compute.
The Blowfish hash function is currently considered pretty good. It is designed to be slow. The
implementation in PHP's crypt()
is easy to use. Set a cost parameter high enough
to make a brute force attack really slow. I set it so that it takes about 250 ms
on the production server which is fast enough for users to tolerate but slow enough to
defeat a brute-force attack.
Each password should have its own random salt. The salt's purpose is to make the dictionary size in a rainbow table or dictionary attack so large that the attack is not feasible. Salts used with the Blowfish hash do not need to be cryptographically secure random strings so Randomness's salt generator by default uses the cass's own pseudo-random generator.
Some people advocate resalting every time a user logs in. I think this is only useful if you also limit the time interval between user logins, e.g. block an account if the user hasn't logged in in more than N weeks.
If your software will be in use for many years then you should increase the cost factor in line with increases in computer speed. You will need to rehash passwords when do.
If your PHP is older than 5.3, please read the section Availability of crypt()’s Blowfish option below.
People often get confused about how to use implement a password store using crypt()
.
It is actually very simple but it helps to know that:
-
It is safe to store the salt together with the password hash. An attacker cannot use it to make a dictionary attack easier.
-
The string
crypt()
returns is the concatenation of the salt you give it and the hash value. -
crypt()
ignores excess characters in the input salt string.
crypt()
has function signature string crypt (string $str, string $salt)
and the
salt string format determines the hash method. For Blowfish hashing, the format is:
"$2a$"
, a two digit cost parameter, "$"
, and 22 digits from the alphabet
"./0-9A-Za-z"
. The cost must be between 04
and 31
.
crypt('EgzamplPassword', '$2a$10$1qAz2wSx3eDc4rFv5tGb5t')
>> '$2a$10$1qAz2wSx3eDc4rFv5tGb5e4jVuld5/KF2Kpy.B8D2XoC031sReFGi'
The first 29 characters are the same as the salt string. Anthing appended to the salt string argument has no effect on the result:
crypt('EgzamplPassword', '$2a$10$1qAz2wSx3eDc4rFv5tGb5t12345678901234567890')
>> '$2a$10$1qAz2wSx3eDc4rFv5tGb5e4jVuld5/KF2Kpy.B8D2XoC031sReFGi'
crypt('EgzamplPassword', '$2a$10$1qAz2wSx3eDc4rFv5tGb5t$2a$10$1qAz2wSx3eDc4rFv5tGb5t')
>> '$2a$10$1qAz2wSx3eDc4rFv5tGb5e4jVuld5/KF2Kpy.B8D2XoC031sReFGi'
And in particular, pass the value returned from crypt()
back in as the salt argument:
crypt('EgzamplPassword', '$2a$10$1qAz2wSx3eDc4rFv5tGb5e4jVuld5/KF2Kpy.B8D2XoC031sReFGi')
>> '$2a$10$1qAz2wSx3eDc4rFv5tGb5e4jVuld5/KF2Kpy.B8D2XoC031sReFGi'
So we can use crypt()
to authenticate a user by passing the hash value it
gave us previously back in as a salt when checking a password input.
Say we have a user
table like this
create table user (
id int not null auto_increment primary key,
email varchar(255) not null,
password_hash char(64) not null,
unique key (email)
)
From a user account generation form assume that we have (already sanitized) user input in
$form->email
and $form->password
. We generate the hash:
$password_hash = crypt($form->password, Randomness::blowfishSalt());
And insert a row into user
containing $form->email
and $password_hash
.
At user logon assume we again have sanitized user input in $form->email
and $form->password
.
To authenticate these against the accounts in user
we select the password_hash
field from table user
where email
= $form->email
and, with that value in $password_hash
if ($password_hash === crypt($form->password, $password_hash))
// password is correct
else
// password is wrong
So there is no need to store the salt in a separate column from the hash value because
crypt()
conveniently keeps it in the same string as the hash.
Randomness::blowfishSalt()
generates a salt to use with crypt()
. Let's say you
have an AR model class User
for the user records in your DB and a form model
instance $form
holding the username and password etc. of the new user.
The controller action where you register new users might include:
$user = new User;
$user->email = $form->email;
$user->password = crypt($form->password, Randomness::blowfishSalt());
if ($user->save()) {
...
}
To authenticate (refer to the authenticate method in protected/components/UserIdentity.php
of a fresh yiic webapp
, see also the auth topic in the Yii Guide):
public function authenticate()
{
$record = User::model()->findByAttributes(array('username' => $this->username));
if ($record === null) {
$this->errorCode = self::ERROR_USERNAME_INVALID;
} else if ($record->password !== crypt($this->password, $record->password)) {
$this->errorCode = self::ERROR_PASSWORD_INVALID;
} else {
$this->_id = $record->id;
$this->setState('title', $record->title);
$this->errorCode = self::ERROR_NONE;
}
return !$this->errorCode;
}
The crypt()
function has ben part of PHP for a long time but not all PHP installations
have all its options.
I use the Blowfish hash option which is available in all PHP systems since 5.3.
It is also available in older PHPs if either the operating system has the option in
its standard library crypt(3)
function
(e.g. many Unix and Linux systems) or if
PHP has the Suhosin patch.
PHP's CRYPT_BLOWFISH
constant is true
if the system has Blowfish.
It can be tricky to implement good password hashing on systems that do not have it and I do not have any recommendations other than to upgrade your PHP or move to a host with an up-to-date PHP.