A common misconception is that the only way attackers crack hashed passwords is by using brute force attacks and rainbow tables. Although this is often the first pass in an attack sequence, attackers will use much more sophisticated attacks on a second, third, or fourth pass. Other attacks include combination, dictionary, mask, and rules-based. Dictionary attacks use a database of words literally from the dictionary to guess passwords. Combination is where dictionary words are combined. Mask attacks are similar to brute force, but more selective, thus cutting down the time to crack. Rules-based attacks will detect things such as substituting the number 0 for the letter o.
The good news is that by simply increasing the length of the password beyond the magic length of six characters exponentially increases the time to crack the hashed password. Other factors, such as interspersing uppercase with lowercase letters randomly, random digits, and special characters, will also have an exponential impact on the time to crack. At the end of the day, we need to bear in mind that a human being will eventually need to enter the passwords created, which means that need to be at least marginally memorable.
Best practice
Passwords should be stored as a hash, and never as plain text. MD5 and SHA* are no longer considered secure (although SHA* is much better than MD5). Using a utility such as oclHashcat, an attacker can generate an average of 55 billion attempts per second on a password hashed using MD5 that has been made available through an exploit (that is, a successful SQL injection attack).
Application\Security\PassGen class that will hold the methods needed for password generation. We also define certain class constants and properties that will be used as part of the process:namespace Application\Security;
class PassGen
{
const SOURCE_SUFFIX = 'src';
const SPECIAL_CHARS =
'\`¬|!"£$%^&*()_-+={}[]:@~;\'#<>?,./|\\';
protected $algorithm;
protected $sourceList;
protected $word;
protected $list;digits() produces random digits, and special() produces a single character from the SPECIAL_CHARS class constant:public function digits($max = 999)
{
return random_int(1, $max);
}
public function special()
{
$maxSpecial = strlen(self::SPECIAL_CHARS) - 1;
return self::SPECIAL_CHARS[random_int(0, $maxSpecial)];
}$wordSource constructor parameter comes into play. It is an array of websites from which our word base will be derived. Accordingly, we need a method that will pull a unique list of words from the sources indicated, and store the results in a file. We accept the $wordSource array as an argument, and loop through each URL. We use md5() to produce a hash of the website name, which is then built into a filename. The newly produced filename is then stored in $sourceList:public function processSource(
$wordSource, $minWordLength, $cacheDir)
{
foreach ($wordSource as $html) {
$hashKey = md5($html);
$sourceFile = $cacheDir . '/' . $hashKey . '.'
. self::SOURCE_SUFFIX;
$this->sourceList[] = $sourceFile;<body> tag. We then use str_word_count() to pull a list of words out of the string, also employing strip_tags() to remove any markup:if (!file_exists($sourceFile) || filesize($sourceFile) == 0) {
echo 'Processing: ' . $html . PHP_EOL;
$contents = file_get_contents($html);
if (preg_match('/<body>(.*)<\/body>/i',
$contents, $matches)) {
$contents = $matches[1];
}
$list = str_word_count(strip_tags($contents), 1);array_unique() to get rid of duplicates. The final result is stored in a file: foreach ($list as $key => $value) {
if (strlen($value) < $minWordLength) {
$list[$key] = 'xxxxxx';
} else {
$list[$key] = trim($value);
}
}
$list = array_unique($list);
file_put_contents($sourceFile, implode("\n",$list));
}
}
return TRUE;
}public function flipUpper($word)
{
$maxLen = strlen($word);
$numFlips = random_int(1, $maxLen - 1);
$flipped = strtolower($word);
for ($x = 0; $x < $numFlips; $x++) {
$pos = random_int(0, $maxLen - 1);
$word[$pos] = strtoupper($word[$pos]);
}
return $word;
}file() function to read from the appropriate cached file:public function word()
{
$wsKey = random_int(0, count($this->sourceList) - 1);
$list = file($this->sourceList[$wsKey]);
$maxList = count($list) - 1;
$key = random_int(0, $maxList);
$word = $list[$key];
return $this->flipUpper($word);
}['word', 'digits', 'word', 'special'] might end up looking like hElLo123aUTo!:public function initAlgorithm()
{
$this->algorithm = [
['word', 'digits', 'word', 'special'],
['digits', 'word', 'special', 'word'],
['word', 'word', 'special', 'digits'],
['special', 'word', 'special', 'digits'],
['word', 'special', 'digits', 'word', 'special'],
['special', 'word', 'special', 'digits',
'special', 'word', 'special'],
];
}public function __construct(
array $wordSource, $minWordLength, $cacheDir)
{
$this->processSource($wordSource, $minWordLength, $cacheDir);
$this->initAlgorithm();
}public function generate()
{
$pwd = '';
$key = random_int(0, count($this->algorithm) - 1);
foreach ($this->algorithm[$key] as $method) {
$pwd .= $this->$method();
}
return str_replace("\n", '', $pwd);
}
}First, you will need to place the code described in the previous recipe into a file called PassGen.php in the Application\Security folder. Now you can create a calling program called chap_12_password_generate.php that sets up autoloading, uses PassGen, and defines the location of the cache directory:
<?php
define('CACHE_DIR', __DIR__ . '/cache');
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\Security\PassGen;Next, you will need to define an array of websites that will be used as a source for the word-base to be used in password generation. In this illustration, we will choose from the Project Gutenberg texts Ulysses (J. Joyce), War and Peace (L. Tolstoy), and Pride and Prejudice (J. Austen):
$source = [ 'https://www.gutenberg.org/files/4300/4300-0.txt', 'https://www.gutenberg.org/files/2600/2600-h/2600-h.htm', 'https://www.gutenberg.org/files/1342/1342-h/1342-h.htm', ];
Next, we create the PassGen instance, and run generate():
$passGen = new PassGen($source, 4, CACHE_DIR); echo $passGen->generate();
Here are a few example passwords produced by PassGen:

An excellent article on how an attacker would approach cracking a password can be viewed at http://arstechnica.com/security/2013/05/how-crackers-make-minced-meat-out-of-your-passwords/. To find out more about brute force attacks you can refer to https://www.owasp.org/index.php/Brute_force_attack. For information on oclHashcat, see this page: http://hashcat.net/oclhashcat/.