To mark #WorldPasswordDay, today we will look at how to generate a strong password inside your PHP application.
We’ll create a function called generatePassword() that will take four arguments, each optional: The length of the password, plus the number of capitals, numbers and symbols it should have.
If no parameters are given, the function will return 8 random lower-case letters. The function will also test its attributes to see if too many capitals, numbers or symbols are requested; if so, the function will return Boolean false and raise a PHP warning error.
Note that while no password based on mt_rand()
and shuffle()
is truly “random” these should suffice for all intents and purposes.
function generatePassword($l = 8, $c = 0, $n = 0, $s = 0) { // get count of all required minimum special chars $count = $c + $n + $s; // sanitize inputs; should be self-explanatory if(!is_int($l) || !is_int($c) || !is_int($n) || !is_int($s)) { trigger_error('Argument(s) not an integer', E_USER_WARNING); return false; } elseif($l < 0 || $l > 20 || $c < 0 || $n < 0 || $s < 0) { trigger_error('Argument(s) out of range', E_USER_WARNING); return false; } elseif($c > $l) { trigger_error('Number of password capitals required exceeds password length', E_USER_WARNING); return false; } elseif($n > $l) { trigger_error('Number of password numerals exceeds password length', E_USER_WARNING); return false; } elseif($s > $l) { trigger_error('Number of password capitals exceeds password length', E_USER_WARNING); return false; } elseif($count > $l) { trigger_error('Number of password special characters exceeds specified password length', E_USER_WARNING); return false; } // all inputs clean, proceed to build password // change these strings if you want to include or exclude possible password characters $chars = "abcdefghijklmnopqrstuvwxyz"; $caps = strtoupper($chars); $nums = "0123456789"; $syms = "!@#$%^&*()-+?"; // build the base password of all lower-case letters for($i = 0; $i < $l; $i++) { $out .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } // create arrays if special character(s) required if($count) { // split base password to array; create special chars array $tmp1 = str_split($out); $tmp2 = array(); // add required special character(s) to second array for($i = 0; $i < $c; $i++) { array_push($tmp2, substr($caps, mt_rand(0, strlen($caps) - 1), 1)); } for($i = 0; $i < $n; $i++) { array_push($tmp2, substr($nums, mt_rand(0, strlen($nums) - 1), 1)); } for($i = 0; $i < $s; $i++) { array_push($tmp2, substr($syms, mt_rand(0, strlen($syms) - 1), 1)); } // hack off a chunk of the base password array that's as big as the special chars array $tmp1 = array_slice($tmp1, 0, $l - $count); // merge special character(s) array with base password array $tmp1 = array_merge($tmp1, $tmp2); // mix the characters up shuffle($tmp1); // convert to string for output $out = implode('', $tmp1); } return $out; }
Line 3 is going to tell us if we need any caps, numerals or symbols in our password. It’s also going to tell us if the total number of non-lower-case letters is greater than the requested password length.
Lines 6-29 test the inputs to ensure we have values of the right type and in range; that no one non-lower-case-letter request is greater than the requested password length; and that the total of all requested “special” characters does not exceed the requested password length. If any of those conditions are not met, the function raises a PHP warning and returns false.
Lines 34-37 provide the seed characters that can appear in a returned password. You can edit these strings as you like, if you want to add or remove characters (e.g., some people will want to not pass the letter “o” if the numeral “0” can also be returned).
Lines 40-42 create a base password of all lower-case letters. It does so by using mt_rand() to pick a number between 0 and however long the base $chars string is; then, grabbing a single letter, at whatever index of the $chars string is chosen by mt_rand(); using substr() to grab that single character; then appending that character to the string we will output ($out).
If there’s no request for capitals, numbers or symbols in our password, we’re done. But if we do need “special” characters (which is determined at Line 45, by determining if $count is greater than 0), then:
Lines 47-48 prepare two arrays: One that converts our base password into a character array, and another that preps a second array to which we will add our “special” characters.
Lines 51-59 append the required special characters onto our second array.
Note that the benefit of the PHP for() control structure is that we can bypass its execution by conditioning its execution on an evaluation that is initially false. In other words, I can add a for() loop to my code, but it will not execute so long as its second argument — the one that evaluates whether the control should execute — is false.
for($i = 1; $i < 0; $i++) { echo "Hello World!"; }
Line 62 lops a chunk off the front of our original password array, using array_slice(), that’s as big as the number of “special” password characters we requested. This is so we can merge those special characters into that array, using the aptly named array_merge() function, which is done at Line 64.
You might be asking yourself, “Why not use array_splice() here? It’s supposed to do exactly what you did with those two functions.” The answer is, I tried that, but I couldn’t get it to work properly; so, a bit more verbose a solution to achieve the same end. It’s inelegant and I know it; if you have alternative code that works better, please comment and I will incorporate it here.
Line 66 calls the handy shuffle() function, which mixes up the characters in an array; Line 68 converts the password back into a string for output. Line 71 returns the password if there wasn’t an error.
Usage
To use this generator, you just need to add its code somewhere on the page where it will be used, and then call it as you would any built-in PHP function:
$password = generatePassword(); // $password is 8 lower-case letters $password = generatePassword(10); // $password is 10 lower-case letters $password = generatePassword(12, 1, 2, 3); // $password is 12 characters: 6 lower-case letters, 1 upper-case letter, 2 numbers and 3 symbols $password = generatePassword(10, 0, 3, 2); // $password is a 10 characters: 5 lower-case letters, 3 numbers and 2 symbols $password = generatePassword(7, 2, 1); // $password is 7 characters: 4 lower-case letters, 2 upper-case letters and 1 number $password = generatePassword(0); // $password is Boolean false; warning error that password length is less than 1 $password = generatePassword(21); // $password is Boolean false; warning error that arguments are out of range $password = generatePassword(10, -1, 3, 1); // $password is Boolean false; warning error that arguments are out of range $password = generatePassword('foo'); // $password is Boolean false; error returned that arguments are not an integer $password = generatePassword(8, 9); // $password is Boolean false; warning error that capital letters count exceeds password length $password = generatePassword(10, 5, 3, 3); // $password is Boolean false; warning error that total special characters count exceeds password length
It’s worth noting that some characters have special meanings in PHP and MySQL, notably the parentheses, dollar sign and arithmetic operators. For example, this password generator could produce double-minus signs, which is the comment character in MySQL; if you got double-dashed in a password and didn’t escape them before using the password in a SQL comment, it could cause SQL errors. The easiest thing to do is remove, from the symbols string at Line 49, any characters that might cause troubles for your code.
Also, if you are going to use this with a form, as in the demo code, it’s worth noting that all POST and GET variables are passed as strings. Therefore, you must explicitly cast your form variables as integers when passing them to your function.