'use strict'; /** * Mellt * * Tests the strength of a password by calculating how long it would take to * brute force it. * * @version 0.1.0 * @link http://mel.lt/ The homepage for this script. * @link http://www.hammerofgod.com/passwordcheck.aspx Much of this is based * on the description of Thor's Godly Privacy password strength checker, * however the actual code below is all my own. * @link http://xato.net/passwords/more-top-worst-passwords/ The included * common passwords list is from Mark Burnett's password collection (which * is excellent). You can of course use your own password file instead. */ var Mellt = function() { /** * @var integer HashesPerSecond The number of attempts per second you expect * an attacker to be able to attempt. Set to 1 billion by default. */ this.HashesPerSecond = 1000000000; /** * @var string CommonPasswords A variable containing an array of common * passwords to check against. If you include common-passwords.js in your * HTML after including Mellt.js, the contents of that file will be used * if this isn't set. * Set this to null (and don't include common-passwords.js) to skip * checking common passwords. */ this.CommonPasswords = null; /** * @var array $CharacterSets An array of strings, each string containing a * character set. These should proceed in the order of simplest (0-9) to most * complex (all characters). More complex = more characters. */ this.CharacterSets = [ // We're making some guesses here about human nature (again much of this is // based on the TGP password strength checker, and Timothy "Thor" Mullen // deserves the credit for the thinking behind this). Basically we're combining // what we know about users (SHIFT+numbers are more common than other // punctuation for example) combined with how an attacker will attack a // password (most common letters first, expanding outwards). // // If you want to support passwords that use non-english characters, and // your attacker knows this (for example, a Russian site would be expected // to contain passwords in Russian characters) add your characters to one of // the sets below, or create new sets and insert them in the right places. "0123456789", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz0123456789", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=_+", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=_+[]\"{}|;':,./<>?`~" ]; }; Mellt.prototype = { CommonPasswords: null, /** * Tests password strength by simulating how long it would take a cracker to * brute force your password. * * Also optionally tests against a list of common passwords (contained in an * external file) to weed out things like "password", which from a pure brute * force perspective would be harder to break if it wasn't so common. * * The character sets being used in this checker assume English (ASCII) * characters (no umlauts for example). If you run a non-english site, and you * suspect the crackers will realize this, you may want to modify the * character set to include the characters in your language. * * @param string $password The password to test the strength of * @return integer Returns an integer specifying how many days it would take * to brute force the password (at 1 billion checks a second) or -1 to * indicate the password was found in the common passwords file. Obviously if * they don't have direct access to the hashed passwords this time would be * longer, and even then most computers (at the time of this writing) won't be * able to test 1 billion hashes a second, but this function measures worst * case scenario, so... I would recommend you require at least 30 days to brute * force a password, obviously more if you're a bank or other secure system. * @throws Exception If an error is encountered. */ CheckPassword: function(password) { // First check passwords in the common password file if available. // We do this because "password" takes 129 seconds, but is the first // thing an attacker will try. if (!this.CommonPasswords && Mellt.prototype.CommonPasswords) { this.CommonPasswords = Mellt.prototype.CommonPasswords; } if (this.CommonPasswords) { var text = password.toLowerCase(); for (var t=0; t-1) { baseKey = characterSetKey; base = characterSet; foundChar = true; break; } } // If the character we were looking for wasn't anywhere in any of the // character sets, assign the largest (last) character set as default. if (!foundChar) { base = this.CharacterSets[this.CharacterSets.length-1]; break; } } // Starting at the first character, figure out it's position in the character set // and how many attempts will take to get there. For example, say your password // was an integer (a bank card PIN number for example): // 0 (or 0000 if you prefer) would be the very first password they attempted by the attacker. // 9999 would be the last password they attempted (assuming 4 characters). // Thus a password/PIN of 6529 would take 6529 attempts until the attacker found // the proper combination. The same logic words for alphanumeric passwords, just // with a larger number of possibilities for each position in the password. The // key thing to note is the attacker doesn't need to test the entire range (every // possible combination of all characters) they just need to get to the point in // the list of possibilities that is your password. They can (in this example) // ignore anything between 6530 and 9999. Using this logic, 'aaa' would be a worse // password than 'zzz', because the attacker would encounter 'aaa' first. var attempts = 0; var charactersInBase = base.length; var charactersInPassword = password.length; for (var position=0; position1000000000) { return 1000000000; } return Math.round(days); } };