/* CrackLib.java This is a reformatted copy of Justin Chapweske's Artistic Licensed Java port of Alec Muffett's cracklib, circa version 0.5. All changes to this file relative to that located in the original archive from http://sourceforge.net/projects/solinger/ are for the purpose of keeping with the code formatting rules we use in the Ganymede project, and to implement Ganymede-compatible localization support for the message strings. */ package org.solinger.cracklib; import java.io.*; import arlut.csd.Util.TranslationService; public class CrackLib { /** * TranslationService object for handling string localization in * the Ganymede server. */ static final TranslationService ts = TranslationService.getTranslationService("org.solinger.cracklib.CrackLib"); public static final int MINDIFF = 5; public static final int MINLEN = 6; public static final int MAXSTEP = 4; /** * Array of rules to apply to attempt to strip elementary * conversions away when doing dictionary checks. */ public static final String[] destructors = new String[] { ":", // noop - must do this to test raw word. "[", // trimming leading/trailing junk "]", "[[", "]]", "[[[", "]]]", "/?p@?p", // purging out punctuation/symbols/junk "/?s@?s", "/?X@?X", // attempt reverse engineering of password strings "/$s$s", "/$s$s/0s0o", "/$s$s/0s0o/2s2a", "/$s$s/0s0o/2s2a/3s3e", "/$s$s/0s0o/2s2a/3s3e/5s5s", "/$s$s/0s0o/2s2a/3s3e/5s5s/1s1i", "/$s$s/0s0o/2s2a/3s3e/5s5s/1s1l", "/$s$s/0s0o/2s2a/3s3e/5s5s/1s1i/4s4a", "/$s$s/0s0o/2s2a/3s3e/5s5s/1s1i/4s4h", "/$s$s/0s0o/2s2a/3s3e/5s5s/1s1l/4s4a", "/$s$s/0s0o/2s2a/3s3e/5s5s/1s1l/4s4h", "/$s$s/0s0o/2s2a/3s3e/5s5s/4s4a", "/$s$s/0s0o/2s2a/3s3e/5s5s/4s4h", "/$s$s/0s0o/2s2a/3s3e/5s5s/4s4a", "/$s$s/0s0o/2s2a/3s3e/5s5s/4s4h", "/$s$s/0s0o/2s2a/3s3e/1s1i", "/$s$s/0s0o/2s2a/3s3e/1s1l", "/$s$s/0s0o/2s2a/3s3e/1s1i/4s4a", "/$s$s/0s0o/2s2a/3s3e/1s1i/4s4h", "/$s$s/0s0o/2s2a/3s3e/1s1l/4s4a", "/$s$s/0s0o/2s2a/3s3e/1s1l/4s4h", "/$s$s/0s0o/2s2a/3s3e/4s4a", "/$s$s/0s0o/2s2a/3s3e/4s4h", "/$s$s/0s0o/2s2a/3s3e/4s4a", "/$s$s/0s0o/2s2a/3s3e/4s4h", "/$s$s/0s0o/2s2a/5s5s", "/$s$s/0s0o/2s2a/5s5s/1s1i", "/$s$s/0s0o/2s2a/5s5s/1s1l", "/$s$s/0s0o/2s2a/5s5s/1s1i/4s4a", "/$s$s/0s0o/2s2a/5s5s/1s1i/4s4h", "/$s$s/0s0o/2s2a/5s5s/1s1l/4s4a", "/$s$s/0s0o/2s2a/5s5s/1s1l/4s4h", "/$s$s/0s0o/2s2a/5s5s/4s4a", "/$s$s/0s0o/2s2a/5s5s/4s4h", "/$s$s/0s0o/2s2a/5s5s/4s4a", "/$s$s/0s0o/2s2a/5s5s/4s4h", "/$s$s/0s0o/2s2a/1s1i", "/$s$s/0s0o/2s2a/1s1l", "/$s$s/0s0o/2s2a/1s1i/4s4a", "/$s$s/0s0o/2s2a/1s1i/4s4h", "/$s$s/0s0o/2s2a/1s1l/4s4a", "/$s$s/0s0o/2s2a/1s1l/4s4h", "/$s$s/0s0o/2s2a/4s4a", "/$s$s/0s0o/2s2a/4s4h", "/$s$s/0s0o/2s2a/4s4a", "/$s$s/0s0o/2s2a/4s4h", "/$s$s/0s0o/3s3e", "/$s$s/0s0o/3s3e/5s5s", "/$s$s/0s0o/3s3e/5s5s/1s1i", "/$s$s/0s0o/3s3e/5s5s/1s1l", "/$s$s/0s0o/3s3e/5s5s/1s1i/4s4a", "/$s$s/0s0o/3s3e/5s5s/1s1i/4s4h", "/$s$s/0s0o/3s3e/5s5s/1s1l/4s4a", "/$s$s/0s0o/3s3e/5s5s/1s1l/4s4h", "/$s$s/0s0o/3s3e/5s5s/4s4a", "/$s$s/0s0o/3s3e/5s5s/4s4h", "/$s$s/0s0o/3s3e/5s5s/4s4a", "/$s$s/0s0o/3s3e/5s5s/4s4h", "/$s$s/0s0o/3s3e/1s1i", "/$s$s/0s0o/3s3e/1s1l", "/$s$s/0s0o/3s3e/1s1i/4s4a", "/$s$s/0s0o/3s3e/1s1i/4s4h", "/$s$s/0s0o/3s3e/1s1l/4s4a", "/$s$s/0s0o/3s3e/1s1l/4s4h", "/$s$s/0s0o/3s3e/4s4a", "/$s$s/0s0o/3s3e/4s4h", "/$s$s/0s0o/3s3e/4s4a", "/$s$s/0s0o/3s3e/4s4h", "/$s$s/0s0o/5s5s", "/$s$s/0s0o/5s5s/1s1i", "/$s$s/0s0o/5s5s/1s1l", "/$s$s/0s0o/5s5s/1s1i/4s4a", "/$s$s/0s0o/5s5s/1s1i/4s4h", "/$s$s/0s0o/5s5s/1s1l/4s4a", "/$s$s/0s0o/5s5s/1s1l/4s4h", "/$s$s/0s0o/5s5s/4s4a", "/$s$s/0s0o/5s5s/4s4h", "/$s$s/0s0o/5s5s/4s4a", "/$s$s/0s0o/5s5s/4s4h", "/$s$s/0s0o/1s1i", "/$s$s/0s0o/1s1l", "/$s$s/0s0o/1s1i/4s4a", "/$s$s/0s0o/1s1i/4s4h", "/$s$s/0s0o/1s1l/4s4a", "/$s$s/0s0o/1s1l/4s4h", "/$s$s/0s0o/4s4a", "/$s$s/0s0o/4s4h", "/$s$s/0s0o/4s4a", "/$s$s/0s0o/4s4h", "/$s$s/2s2a", "/$s$s/2s2a/3s3e", "/$s$s/2s2a/3s3e/5s5s", "/$s$s/2s2a/3s3e/5s5s/1s1i", "/$s$s/2s2a/3s3e/5s5s/1s1l", "/$s$s/2s2a/3s3e/5s5s/1s1i/4s4a", "/$s$s/2s2a/3s3e/5s5s/1s1i/4s4h", "/$s$s/2s2a/3s3e/5s5s/1s1l/4s4a", "/$s$s/2s2a/3s3e/5s5s/1s1l/4s4h", "/$s$s/2s2a/3s3e/5s5s/4s4a", "/$s$s/2s2a/3s3e/5s5s/4s4h", "/$s$s/2s2a/3s3e/5s5s/4s4a", "/$s$s/2s2a/3s3e/5s5s/4s4h", "/$s$s/2s2a/3s3e/1s1i", "/$s$s/2s2a/3s3e/1s1l", "/$s$s/2s2a/3s3e/1s1i/4s4a", "/$s$s/2s2a/3s3e/1s1i/4s4h", "/$s$s/2s2a/3s3e/1s1l/4s4a", "/$s$s/2s2a/3s3e/1s1l/4s4h", "/$s$s/2s2a/3s3e/4s4a", "/$s$s/2s2a/3s3e/4s4h", "/$s$s/2s2a/3s3e/4s4a", "/$s$s/2s2a/3s3e/4s4h", "/$s$s/2s2a/5s5s", "/$s$s/2s2a/5s5s/1s1i", "/$s$s/2s2a/5s5s/1s1l", "/$s$s/2s2a/5s5s/1s1i/4s4a", "/$s$s/2s2a/5s5s/1s1i/4s4h", "/$s$s/2s2a/5s5s/1s1l/4s4a", "/$s$s/2s2a/5s5s/1s1l/4s4h", "/$s$s/2s2a/5s5s/4s4a", "/$s$s/2s2a/5s5s/4s4h", "/$s$s/2s2a/5s5s/4s4a", "/$s$s/2s2a/5s5s/4s4h", "/$s$s/2s2a/1s1i", "/$s$s/2s2a/1s1l", "/$s$s/2s2a/1s1i/4s4a", "/$s$s/2s2a/1s1i/4s4h", "/$s$s/2s2a/1s1l/4s4a", "/$s$s/2s2a/1s1l/4s4h", "/$s$s/2s2a/4s4a", "/$s$s/2s2a/4s4h", "/$s$s/2s2a/4s4a", "/$s$s/2s2a/4s4h", "/$s$s/3s3e", "/$s$s/3s3e/5s5s", "/$s$s/3s3e/5s5s/1s1i", "/$s$s/3s3e/5s5s/1s1l", "/$s$s/3s3e/5s5s/1s1i/4s4a", "/$s$s/3s3e/5s5s/1s1i/4s4h", "/$s$s/3s3e/5s5s/1s1l/4s4a", "/$s$s/3s3e/5s5s/1s1l/4s4h", "/$s$s/3s3e/5s5s/4s4a", "/$s$s/3s3e/5s5s/4s4h", "/$s$s/3s3e/5s5s/4s4a", "/$s$s/3s3e/5s5s/4s4h", "/$s$s/3s3e/1s1i", "/$s$s/3s3e/1s1l", "/$s$s/3s3e/1s1i/4s4a", "/$s$s/3s3e/1s1i/4s4h", "/$s$s/3s3e/1s1l/4s4a", "/$s$s/3s3e/1s1l/4s4h", "/$s$s/3s3e/4s4a", "/$s$s/3s3e/4s4h", "/$s$s/3s3e/4s4a", "/$s$s/3s3e/4s4h", "/$s$s/5s5s", "/$s$s/5s5s/1s1i", "/$s$s/5s5s/1s1l", "/$s$s/5s5s/1s1i/4s4a", "/$s$s/5s5s/1s1i/4s4h", "/$s$s/5s5s/1s1l/4s4a", "/$s$s/5s5s/1s1l/4s4h", "/$s$s/5s5s/4s4a", "/$s$s/5s5s/4s4h", "/$s$s/5s5s/4s4a", "/$s$s/5s5s/4s4h", "/$s$s/1s1i", "/$s$s/1s1l", "/$s$s/1s1i/4s4a", "/$s$s/1s1i/4s4h", "/$s$s/1s1l/4s4a", "/$s$s/1s1l/4s4h", "/$s$s/4s4a", "/$s$s/4s4h", "/$s$s/4s4a", "/$s$s/4s4h", "/0s0o", "/0s0o/2s2a", "/0s0o/2s2a/3s3e", "/0s0o/2s2a/3s3e/5s5s", "/0s0o/2s2a/3s3e/5s5s/1s1i", "/0s0o/2s2a/3s3e/5s5s/1s1l", "/0s0o/2s2a/3s3e/5s5s/1s1i/4s4a", "/0s0o/2s2a/3s3e/5s5s/1s1i/4s4h", "/0s0o/2s2a/3s3e/5s5s/1s1l/4s4a", "/0s0o/2s2a/3s3e/5s5s/1s1l/4s4h", "/0s0o/2s2a/3s3e/5s5s/4s4a", "/0s0o/2s2a/3s3e/5s5s/4s4h", "/0s0o/2s2a/3s3e/5s5s/4s4a", "/0s0o/2s2a/3s3e/5s5s/4s4h", "/0s0o/2s2a/3s3e/1s1i", "/0s0o/2s2a/3s3e/1s1l", "/0s0o/2s2a/3s3e/1s1i/4s4a", "/0s0o/2s2a/3s3e/1s1i/4s4h", "/0s0o/2s2a/3s3e/1s1l/4s4a", "/0s0o/2s2a/3s3e/1s1l/4s4h", "/0s0o/2s2a/3s3e/4s4a", "/0s0o/2s2a/3s3e/4s4h", "/0s0o/2s2a/3s3e/4s4a", "/0s0o/2s2a/3s3e/4s4h", "/0s0o/2s2a/5s5s", "/0s0o/2s2a/5s5s/1s1i", "/0s0o/2s2a/5s5s/1s1l", "/0s0o/2s2a/5s5s/1s1i/4s4a", "/0s0o/2s2a/5s5s/1s1i/4s4h", "/0s0o/2s2a/5s5s/1s1l/4s4a", "/0s0o/2s2a/5s5s/1s1l/4s4h", "/0s0o/2s2a/5s5s/4s4a", "/0s0o/2s2a/5s5s/4s4h", "/0s0o/2s2a/5s5s/4s4a", "/0s0o/2s2a/5s5s/4s4h", "/0s0o/2s2a/1s1i", "/0s0o/2s2a/1s1l", "/0s0o/2s2a/1s1i/4s4a", "/0s0o/2s2a/1s1i/4s4h", "/0s0o/2s2a/1s1l/4s4a", "/0s0o/2s2a/1s1l/4s4h", "/0s0o/2s2a/4s4a", "/0s0o/2s2a/4s4h", "/0s0o/2s2a/4s4a", "/0s0o/2s2a/4s4h", "/0s0o/3s3e", "/0s0o/3s3e/5s5s", "/0s0o/3s3e/5s5s/1s1i", "/0s0o/3s3e/5s5s/1s1l", "/0s0o/3s3e/5s5s/1s1i/4s4a", "/0s0o/3s3e/5s5s/1s1i/4s4h", "/0s0o/3s3e/5s5s/1s1l/4s4a", "/0s0o/3s3e/5s5s/1s1l/4s4h", "/0s0o/3s3e/5s5s/4s4a", "/0s0o/3s3e/5s5s/4s4h", "/0s0o/3s3e/5s5s/4s4a", "/0s0o/3s3e/5s5s/4s4h", "/0s0o/3s3e/1s1i", "/0s0o/3s3e/1s1l", "/0s0o/3s3e/1s1i/4s4a", "/0s0o/3s3e/1s1i/4s4h", "/0s0o/3s3e/1s1l/4s4a", "/0s0o/3s3e/1s1l/4s4h", "/0s0o/3s3e/4s4a", "/0s0o/3s3e/4s4h", "/0s0o/3s3e/4s4a", "/0s0o/3s3e/4s4h", "/0s0o/5s5s", "/0s0o/5s5s/1s1i", "/0s0o/5s5s/1s1l", "/0s0o/5s5s/1s1i/4s4a", "/0s0o/5s5s/1s1i/4s4h", "/0s0o/5s5s/1s1l/4s4a", "/0s0o/5s5s/1s1l/4s4h", "/0s0o/5s5s/4s4a", "/0s0o/5s5s/4s4h", "/0s0o/5s5s/4s4a", "/0s0o/5s5s/4s4h", "/0s0o/1s1i", "/0s0o/1s1l", "/0s0o/1s1i/4s4a", "/0s0o/1s1i/4s4h", "/0s0o/1s1l/4s4a", "/0s0o/1s1l/4s4h", "/0s0o/4s4a", "/0s0o/4s4h", "/0s0o/4s4a", "/0s0o/4s4h", "/2s2a", "/2s2a/3s3e", "/2s2a/3s3e/5s5s", "/2s2a/3s3e/5s5s/1s1i", "/2s2a/3s3e/5s5s/1s1l", "/2s2a/3s3e/5s5s/1s1i/4s4a", "/2s2a/3s3e/5s5s/1s1i/4s4h", "/2s2a/3s3e/5s5s/1s1l/4s4a", "/2s2a/3s3e/5s5s/1s1l/4s4h", "/2s2a/3s3e/5s5s/4s4a", "/2s2a/3s3e/5s5s/4s4h", "/2s2a/3s3e/5s5s/4s4a", "/2s2a/3s3e/5s5s/4s4h", "/2s2a/3s3e/1s1i", "/2s2a/3s3e/1s1l", "/2s2a/3s3e/1s1i/4s4a", "/2s2a/3s3e/1s1i/4s4h", "/2s2a/3s3e/1s1l/4s4a", "/2s2a/3s3e/1s1l/4s4h", "/2s2a/3s3e/4s4a", "/2s2a/3s3e/4s4h", "/2s2a/3s3e/4s4a", "/2s2a/3s3e/4s4h", "/2s2a/5s5s", "/2s2a/5s5s/1s1i", "/2s2a/5s5s/1s1l", "/2s2a/5s5s/1s1i/4s4a", "/2s2a/5s5s/1s1i/4s4h", "/2s2a/5s5s/1s1l/4s4a", "/2s2a/5s5s/1s1l/4s4h", "/2s2a/5s5s/4s4a", "/2s2a/5s5s/4s4h", "/2s2a/5s5s/4s4a", "/2s2a/5s5s/4s4h", "/2s2a/1s1i", "/2s2a/1s1l", "/2s2a/1s1i/4s4a", "/2s2a/1s1i/4s4h", "/2s2a/1s1l/4s4a", "/2s2a/1s1l/4s4h", "/2s2a/4s4a", "/2s2a/4s4h", "/2s2a/4s4a", "/2s2a/4s4h", "/3s3e", "/3s3e/5s5s", "/3s3e/5s5s/1s1i", "/3s3e/5s5s/1s1l", "/3s3e/5s5s/1s1i/4s4a", "/3s3e/5s5s/1s1i/4s4h", "/3s3e/5s5s/1s1l/4s4a", "/3s3e/5s5s/1s1l/4s4h", "/3s3e/5s5s/4s4a", "/3s3e/5s5s/4s4h", "/3s3e/5s5s/4s4a", "/3s3e/5s5s/4s4h", "/3s3e/1s1i", "/3s3e/1s1l", "/3s3e/1s1i/4s4a", "/3s3e/1s1i/4s4h", "/3s3e/1s1l/4s4a", "/3s3e/1s1l/4s4h", "/3s3e/4s4a", "/3s3e/4s4h", "/3s3e/4s4a", "/3s3e/4s4h", "/5s5s", "/5s5s/1s1i", "/5s5s/1s1l", "/5s5s/1s1i/4s4a", "/5s5s/1s1i/4s4h", "/5s5s/1s1l/4s4a", "/5s5s/1s1l/4s4h", "/5s5s/4s4a", "/5s5s/4s4h", "/5s5s/4s4a", "/5s5s/4s4h", "/1s1i", "/1s1l", "/1s1i/4s4a", "/1s1i/4s4h", "/1s1l/4s4a", "/1s1l/4s4h", "/4s4a", "/4s4h", "/4s4a", "/4s4h"}; public static final String[] constructors = { ":", "r", "d", "f", "dr", "fr", "rf"}; public static final boolean gTry(String rawtext, String password) { /* use destructors to turn password into rawtext */ /* note use of Reverse() to save duplicating all rules */ String mp; for (int i=0; i<destructors.length; i++) { if ((mp = Rules.mangle(password, destructors[i])) == null) { continue; } if (mp.equals(rawtext)) { return true; } if (Rules.reverse(mp).equals(rawtext)) { return true; } } for (int i=0;i<constructors.length; i++) { if ((mp = Rules.mangle(rawtext, constructors[i])) == null) { continue; } if (mp.equals(rawtext)) { return true; } } return false; } /** * Returns a message string if the password is considered to be * unacceptable, or null if the submitted password is okay. * * @param p A reference to a org.solinger.cracklib.Packer object associated * with a dictionary list * @param password The prospective password to be examined. Must not be null. * @param username The username that we're approving the password for. May be null. */ public static final String fascistLook(Packer p, String password, String username) throws IOException { if (password.length() < 4) { // "It''s WAY too short." return ts.l("fascistLook.wayshort"); } if (password.length() < MINLEN) { // "It''s too short." return ts.l("fascistLook.short"); } if ((password = password.trim()).length() == 0) { // "It is all whitespace." return ts.l("fascistLook.whitespace"); } StringBuilder junk = new StringBuilder(password.substring(0,1)); outer: for (int i=1; i<password.length(); i++) { for (int j=0; j < junk.length(); j++) { if (junk.charAt(j) == password.charAt(i)) { continue outer; } } junk.append(password.charAt(i)); } if (junk.length() < MINDIFF) { // "It does not contain enough DIFFERENT characters." return ts.l("fascistLook.mindiff"); } // make sure we don't have an all-numeric password StringBuilder numPat = new StringBuilder(); for (int i = 0; i < password.length(); i++) { numPat.append("d"); } if (Rules.pMatch(numPat.toString(), password)) { // "It is too simplistic / too predictable." return ts.l("fascistLook.complexity"); } // test for step up / step down patternings int complexity = 0; for (int i = 0; i < password.length() - 1; i++) { if (password.charAt(i) == password.charAt(i+1) + 1 || password.charAt(i) == password.charAt(i+1) - 1) { complexity++; } } if (complexity > MAXSTEP) { // "It is too simplistic / too predictable." return ts.l("fascistLook.complexity"); } if (password.toUpperCase().equals(password) || password.toLowerCase().equals(password)) { // "It needs to be mixed case." return ts.l("fascistLook.notmixedcase"); } // check for character classes.. we require at least one // punctuation, symbolic, numeric, or whitespace character if (!(Rules.indexOf(password, 'p') > -1 || Rules.indexOf(password, 's') > -1 || Rules.indexOf(password, 'd') > -1 || Rules.indexOf(password, 'w') > -1)) { // "It requires at least one punctuation, numeric, or whitespace character." return ts.l("fascistLook.needssymbol"); } // we want at least one uppercase and one lower case letter if (Rules.indexOf(password, 'l') == -1 || Rules.indexOf(password, 'u') == -1) { // "It is too simplistic / too predictable." return ts.l("fascistLook.complexity"); } // check for worrisome numbers and such if (Rules.pMatch("aadddddda", password)) { // smirk // "It looks like a National Insurance number." return ts.l("fascistLook.insurance"); } if (Rules.pMatch("ddd#dd#dddd", password)) { // "It looks like a Social Security number." return ts.l("fascistLook.socialsecurity"); } if (Rules.pMatch("ddd#dddd", password) || Rules.pMatch("ddd#ddd#dddd", password) || Rules.pMatch("#ddd#ddd#dddd", password)) { // "It looks like a phone number." (american, that is) return ts.l("fascistLook.phone"); } // check for dictionary/username matches password = password.toLowerCase(); if (p.size() == 0 && username == null) { // No dictionary found, abort return null; } for (int i=0; i<destructors.length; i++) { String mp; if ((mp = Rules.mangle(password, destructors[i])) == null) { continue; } if (mp.equals(username)) { // "It is based on your username." return ts.l("fascistLook.username"); } if (p.find(mp) != -1) { // "It is based on the dictionary word {0}." return ts.l("fascistLook.dictionary", mp); } } password = Rules.reverse(password); for (int i=0; i<destructors.length; i++) { String mp; if ((mp = Rules.mangle(password, destructors[i])) == null) { continue; } if (mp.equals(username)) { // "It is based on your username." return ts.l("fascistLook.username"); } if (p.find(mp) != -1) { // "It is based on the reversed dictionary word {0}." return ts.l("fascistLook.revdictionary", mp); } } return null; } public static final void usage() { System.err.println("CrackLib -check <password> | -check <password> <username> | -dump <dict> | -make <dict> <wordlist> | -find <dict> <word>"); } public static void main(String[] args) throws Exception { if ((args.length == 3 || args.length == 4) && args[0].equals("-check")) { Packer p = new Packer(args[1], "r"); try { String msg = null; if (args.length == 4) { msg = fascistLook(p, args[2], args[3]); } else { msg = fascistLook(p, args[2], null); } if (msg != null) { System.out.println(msg); } else { // "{0} looks good to me!" System.out.println(ts.l("main.ok", args[2])); } } finally { p.close(); } } else if ((args.length == 2 && args[0].equals("-dump")) || (args.length == 3 && args[0].equals("-find")) || (args.length == 3 && args[0].equals("-make"))) { Packer.main(args); } else { usage(); System.exit(1); } } }