1/** 2 * This is an object oriented approach of rumkin.com/passchk.js 3 * Originally written to be self contained, this makes it as an object 4 * that can be instantiated. 5 */ 6"use strict"; 7 8// fid-umd {"name":"PasswordStrength"} 9(function (name, root, factory) { 10 /** 11 * Determines if something is an object. Use for minification. 12 * 13 * @param {*} x 14 * @return {boolean} 15 */ 16 function isObject(x) { 17 return typeof x === "object"; 18 } 19 20 if ((typeof module)[0] === "o" && isObject(module.exports)) { 21 module.exports = factory(); 22 } else if ((typeof exports)[0] === "o") { 23 exports[name] = factory(); 24 } else if (isObject(root.define) && root.define.amd) { 25 root.define(name, [], factory); 26 } else if (isObject(root.modulejs)) { 27 root.modulejs.define(name, factory); 28 } else if (isObject(root.YUI)) { 29 root.YUI.add(name, function (Y) { 30 Y[name] = factory(); 31 }); 32 } else { 33 root[name] = factory(); 34 } 35 36 // eslint-disable-next-line 37}("PasswordStrength", this, function () { 38 // fid-umd end 39 40 /** 41 * Shim the Math object to support log2 if it does not already. 42 */ 43 if (!Math.log2) { 44 /** 45 * You can emulate any log in any base with log. 46 * 47 * @param {number} n 48 * @return {number} 49 */ 50 Math.log2 = function (n) { 51 return Math.log(n) / Math.log(2); 52 }; 53 } 54 55 56 /** 57 * Iterates over an object 58 * 59 * @param {Object} obj 60 * @param {Function} callback(key,value) 61 */ 62 function objectForEach(obj, callback) { 63 Object.keys(obj).forEach(function (key) { 64 callback(key, obj[key]); 65 }); 66 } 67 68 69 /** 70 * Turns a list of characters into a regular expression that will match 71 * whatever you pass in. There may be trouble generating the pattern, 72 * so here we are escaping every character that isn't alphanumeric. 73 * 74 * @param {string} letters 75 * @param {string} flags (defaults to "") 76 * @return {RegExp} pattern 77 */ 78 function safeCharMatcher(letters, flags) { 79 var pattern; 80 81 pattern = letters.replace(/[\W_]/g, function (x) { 82 return "\\" + x; 83 }); 84 pattern = "[" + pattern + "]"; 85 86 return new RegExp(pattern, flags || ""); 87 } 88 89 90 /** 91 * Determine the strength of a password. 92 * 93 * Usage: 94 * 95 * ps = new PasswordStrength(); 96 * 97 * // Improve checks by adding additional data files - optional 98 * ps.addCommonPasswords(pass); // data/common-passwords.json 99 * ps.addTrigraphMap(tri); // data/trigraphs.json 100 * 101 * // Calculate password strength 102 * stats = ps.check("my password"); 103 * 104 * @return {PasswordStrength} instance 105 */ 106 function PasswordStrength() { 107 if (!(this instanceof PasswordStrength)) { 108 return new PasswordStrength(); 109 } 110 111 this.commonPasswords = null; 112 this.trigraph = null; 113 this.charsets = { 114 number: "0123456789", 115 lower: "abcdefghijklmnopqrstuvwxyz", 116 upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 117 118 // Things found in sentences 119 punctuation: "!'.,:;?&-\" ", 120 121 // Typically math-related 122 symbol: "@#$%^*(){}[]><~`_+=|/" 123 }; 124 125 126 /** 127 * Sets commonPassword property with what a user passes in. 128 * Uses it if it's an array, processes it into an array if it's a 129 * string and throws an error if it's not a format we can work with. 130 * 131 * Returns the PasswordCheck object so it can be chained 132 * 133 * @param {(Array|string)} passwords 134 * @return {PasswordStrength} PasswordStrength 135 */ 136 this.addCommonPasswords = function (passwords) { 137 if (passwords) { 138 if (Array.isArray(passwords)) { 139 this.commonPasswords = passwords; 140 } else if (typeof passwords === "string") { 141 this.commonPasswords = passwords.split(/\r\n|\r|\n/); 142 } else { 143 throw new Error("Format does not match any expected format."); 144 } 145 } else { 146 this.commonPasswords = []; 147 } 148 149 return this; 150 }; 151 152 153 /** 154 * TrigraphMap is an ojbect representing entries of 3 letter 155 * combinations and how often they appear. 156 * 157 * { 158 * "_ty": 123, 159 * "tyl": 22, 160 * "yle": 19,
161 * "ler": 5, 162 * "er_": 543 163 * } 164 * 165 * For further explanation on the numbers, check out the documentation 166 * in the data folder. 167 * 168 * @typedef {Object} PasswordStrength~trigraphMap 169 * @property {number} * Frequencies of that letter combo. 170 */ 171 172 /** 173 * Returns the PasswordStrength object so it can be chained 174 * 175 * @param {PasswordStrength~trigraphMap} trigraphMap 176 * @return {PasswordStrength} PasswordStrength 177 */ 178 this.addTrigraphMap = function (trigraphMap) { 179 if (trigraphMap) { 180 if (trigraphMap && typeof trigraphMap === "object" && !Array.isArray(trigraphMap)) { 181 this.trigraph = trigraphMap; 182 } else { 183 throw new Error("Format does not match any expected format."); 184 } 185 } else { 186 this.trigraph = null; 187 } 188 189 return this; 190 }; 191 192 193 /** 194 * charsetGroups is an object of different group of defined characters 195 * used in figuring out how many characters per group as well as 196 * what characters fall into each group. It is based on a 197 * PasswordStrength instance's `this.charsets` object. When a password 198 * has a character that matches one of the character sets in the 199 * PasswordStrength instance's sets, this sets that key to `true`. 200 * 201 * The "other" key is all letters that did not match another set. 202 * 203 * Example for the password "abcd£1234": 204 * 205 * { 206 * lower: true, 207 * upper: false, 208 * number: true, 209 * punctuation: false, 210 * symbol: false, 211 * other: "£" 212 * } 213 * 214 * @typedef {Object} PasswordStrength~charsetGroups 215 * @property {string} other Characters that did not match another set 216 * @property {boolean} * All of the rest of the sets just set flags 217 */ 218 219 /** 220 * Gets the character set size of the current password. 221 * This breaks up the each character set and tests the password 222 * to see which set they belong in. 223 * 224 * @param {string} passToCheck 225 * @return {PasswordStrength~charsetGroups} groups 226 */ 227 this.charsetGroups = function (passToCheck) { 228 var groups; 229 230 groups = {}; 231 objectForEach(this.charsets, function (key, value) { 232 groups[key] = safeCharMatcher(value).test(passToCheck); 233 }); 234 groups.other = this.otherChars(passToCheck); 235 236 return groups; 237 }; 238 239 240 /** 241 * Calculates the size of each flag found for a password, 242 * and will get the flags if a password was passed in. 243 * 244 * @param {PasswordStrength~charsetGroups} groups 245 * @return {number} result 246 */ 247 this.charsetSize = function (groups) { 248 var size; 249 250 size = 0; 251 252 objectForEach(this.charsets, function (key, value) { 253 if (groups[key]) { 254 size += value.length; 255 } 256 }); 257 258 if (typeof groups.other === "string") { 259 size += groups.other.length; 260 } 261 262 return size; 263 }; 264 265 /** 266 * Statistics is an object with information about the current state of 267 * the password. 268 * 269 * @typedef {Object} PasswordStrength~statistics 270 * @property {number} charsetSize size of the characters from each group the characters fall into. 271 * @property {boolean} commonPassword 272 * @property {number} passwordLength the length of the current password 273 * @property {number} shannonEntropyBits 274 * @property {string} strength estimates strength score for the password 275 * @property {(null|number)} trigraphEntropyBits 276 */ 277 278 /** 279 * Calculates a bunch of statistics about a given password. 280 * 281 * @param {string} currentPassword 282 * @return {PasswordStrength~statistics} 283 */ 284 this.check = function (currentPassword) { 285 var result; 286 287 result = { 288 charsetSize: 0, 289 commonPassword: false, 290 nistEntropyBits: 0, 291 passwordLength: 0, 292 shannonEntropyBits: 0, 293 strengthCode: null, 294 trigraphEntropyBits: null, 295 charsets: null 296 }; 297 298 // For undefined and if we get an empty value 299 if (!currentPassword || !currentPassword.length) { 300 if (this.trigraph) { 301 result.trigraphEntropyBits = 0; 302 } 303 304 return result; 305 } 306 307 result.commonPassword = this.checkCommonPasswords(currentPassword); 308 result.charsets = this.charsetGroups(currentPassword); 309 result.charsetSize = this.charsetSize(result.charsets); 310 result.nistEntropyBits = this.nistScore(currentPassword); 311 result.shannonEntropyBits = this.shannonScore(currentPassword); 312 result.passwordLength = currentPassword.length; 313 result.trigraphEntropyBits = this.checkTrigraph(currentPassword, result.charsetSize); 314 result.strengthCode = this.determineStrength(result); 315 316 return result; 317 }; 318 319 /** 320 * This checks to see if the password is in the common list of
321 * passwords. If in the common passwords lists, sets the warn flag. 322 * 323 * @param {string} passToCheck 324 * @return {boolean} 325 */ 326 this.checkCommonPasswords = function (passToCheck) { 327 var commonPasswordsLength, commonPasswordsList, i; 328 329 passToCheck = passToCheck.toLowerCase(); 330 331 if (this.commonPasswords && this.commonPasswords.length) { 332 commonPasswordsList = this.commonPasswords; 333 commonPasswordsLength = this.commonPasswords.length; 334 335 for (i = 0; i < commonPasswordsLength; i += 1) { 336 if (commonPasswordsList[i] === passToCheck) { 337 return true; 338 } 339 } 340 341 return false; 342 } 343 344 return null; 345 }; 346 347 348 /** 349 * Compares the password to the trigraph to get the rating of how 350 * guessable the password is. 351 * 352 * @param {string} passToCheck 353 * @param {number} charsetSize 354 * @return {number} entropy in bits 355 */ 356 this.checkTrigraph = function (passToCheck, charsetSize) { 357 var i, score, str; 358 359 if (!this.trigraph) { 360 return null; 361 } 362 363 score = 1; 364 passToCheck = passToCheck.toLowerCase().replace(/[\W_]/gi, " ").trim(); 365 passToCheck = "_" + passToCheck + "_"; 366 367 for (i = 0; i < passToCheck.length - 2; i += 1) { 368 str = passToCheck.substr(i, 3); 369 370 if (this.trigraph[str]) { 371 // Less than fully random 372 score *= (1.0 - this.trigraph[str] / 10000) * charsetSize; 373 } else { 374 // Fully random selection 375 score *= charsetSize; 376 } 377 } 378 379 return Math.log2(score); 380 }; 381 382 383 /** 384 * Takes parameters and calculates the strength of a given password 385 * from a list of codes set. This will use the trigraph entropy bits if 386 * available and fallback to the shannon entropy bits which we should 387 * always have. 388 * 389 * @param {Object} status 390 * @return {string} strengthCode 391 */ 392 this.determineStrength = function (status) { 393 var entropyBits, strengthCode; 394 395 strengthCode = ""; 396 397 if (status.trigraphEntropyBits) { 398 entropyBits = status.trigraphEntropyBits; 399 } else { 400 entropyBits = status.shannonEntropyBits; 401 } 402 403 if (entropyBits <= 32) { 404 // Keep out a typical attacker for minutes 405 strengthCode = "VERY_WEAK"; 406 } else if (entropyBits <= 48) { 407 // Crackable by a typical home computer in a week. 408 strengthCode = "WEAK"; 409 } else if (entropyBits <= 64) { 410 // A specialized computer could get this in one year. 411 strengthCode = "REASONABLE"; 412 } else if (entropyBits <= 80) { 413 // Resistant to a large, coordinated attack (botnet) for over a year. 414 strengthCode = "STRONG"; 415 } else { 416 // Nearly impossible to brute force, given more than all of the 417 // computing power in the world, optimized algorithms, 418 // specialized hardware and a thousand years. 419 strengthCode = "VERY_STRONG"; 420 } 421 422 return strengthCode; 423 }; 424 425 426 /** 427 * Calculates the NIST score based on the following rules from NIST 428 * Special Publication 800-63 as of June 2004. 429 * 430 * * The entropy of the first character is four bits; 431 * * The entropy of the next seven characters are two bits per 432 * character; 433 * * The ninth through the twentieth character has 1.5 bits of 434 * entropy per character; 435 * * Characters 21 and above have one bit of entropy per character. 436 * * A "bonus" of six bits is added if both upper case letters and 437 * non-alphabetic characters are used. 438 * * A "bonus" of six bits is added for passwords of length 1 through 439 * 19 characters following an extensive dictionary check to ensure 440 * the password is not contained within a large dictionary. 441 * Passwords of 20 characters or more do not receive this bonus 442 * because it is assumed they are pass-phrases consisting of 443 * multiple dictionary words. 444 * 445 * According to Wikipedia, this score might not be overly useful. 446 * 447 * > Later research into human-selected password entropy using newly 448 * > available real world data has demonstrated that the NIST scheme 449 * > does not provide a valid metric for entropy estimation of 450 * > human-selected passwords. 451 * 452 * @param {string} password 453 * @return {number} 454 */ 455 this.nistScore = function (password) { 456 var len, score; 457 458 len = password.length; 459 score = 0; 460 461 if (len > 20) { 462 score += len - 20; 463 len = 20; 464 } 465 466 if (len > 8) { 467 score += 1.5 * (len - 8); 468 len = 8; 469 } 470 471 if (len > 1) { 472 score += 2 * (len - 1); 473 len = 1; 474 } 475 476 if (len) { 477 score += 4; 478 } 479 480 if (password.match(/[A-Z]/) && password.match(/[^A-Za-z]/)) { 481 score += 6; 482 } 483 484 // Don't have a large dictionary, thus nobody gets the 6 bit 485 // bonus. 486 return score; 487 }; 488 489 490 /** 491 * Calls function to see if there are other characters used 492 * which we don't explicitly check for. 493 * 494 * @param {string} passToCheck 495 * @return {string} result 496 */ 497 this.otherChars = function (passToCheck) { 498 var chars, hash, pattern; 499 500 chars = ""; 501 objectForEach(this.charsets, function (key, value) { 502 chars += value; 503 }); 504 pattern = safeCharMatcher(chars, "g"); 505 hash = {}; 506 passToCheck.replace(pattern, "").split("").forEach(function (letter) { 507 hash[letter] = true; 508 }); 509 510 return Object.keys(hash).join(""); 511 }; 512 513 514 /** 515 * Calculate the Shannon Entropy value of a password. This is really 516 * the number of bits necessary to represent the possible characters 517 * times the length of the password. 518 * 519 * Reworked from code under a MIT license. 520 * entropy.js MIT License © 2014 James Abney http://github.com/jabney 521 * 522 * @param {string} pass Password to evaluate 523 * @return {number} Bits of entropy 524 */ 525 this.shannonScore = function (pass) { 526 var freq, passLength, sum; 527 528 /** 529 * Counts how often each character appears in the passphrase. 530 * 531 * @return {Object} Treated like a hash, indexed by the character. 532 */ 533 function countFrequencies() { 534 var c, h, i; 535 536 // Hash for collecting frequencies 537 h = {}; 538 539 for (i = 0; i < passLength; i += 1) { 540 c = pass.charAt(i); 541 542 if (h[c]) { 543 h[c] += 1; 544 } else { 545 h[c] = 1; 546 } 547 } 548 549 return h; 550 } 551 552 sum = 0;
553 passLength = pass.length; 554 freq = countFrequencies(); 555 objectForEach(freq, function (key, value) { 556 var score; 557 558 score = value / passLength; 559 sum -= score * Math.log2(score); 560 }); 561 562 return sum * passLength; 563 }; 564 } 565 566 return PasswordStrength; 567 // fid-umd post 568})); 569// fid-umd post-end
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.