From a55be30bc5cee97b90e44989757fe780190a386f Mon Sep 17 00:00:00 2001 From: iphelix Date: Sat, 20 Jul 2013 23:15:12 -0700 Subject: [PATCH] Updated Policy Generator with better logic and a new --noncompliant flag to test for passwords not following defined password policy. --- maskgen.py | 5 +- policygen.py | 251 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 164 insertions(+), 92 deletions(-) diff --git a/maskgen.py b/maskgen.py index 95ae10f..52955aa 100755 --- a/maskgen.py +++ b/maskgen.py @@ -96,7 +96,8 @@ class MaskGen: self.masks[mask]['time'] = mask_complexity/self.pps self.masks[mask]['optindex'] = 1 - mask_complexity/mask_occurrence - def print_masks(self,sorting_mode): + def generate_masks(self,sorting_mode): + """ Generate optimal password masks sorted by occurrence, complexity or optindex """ sample_count = 0 sample_time = 0 sample_occurrence = 0 @@ -263,7 +264,7 @@ if __name__ == "__main__": sorting_mode = "optindex" print "[*] Sorting masks by their [%s]." % sorting_mode - maskgen.print_masks(sorting_mode) + maskgen.generate_masks(sorting_mode) diff --git a/policygen.py b/policygen.py index b9925fc..17b38a6 100755 --- a/policygen.py +++ b/policygen.py @@ -28,11 +28,12 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import string, random +import sys, string, random +import datetime from optparse import OptionParser, OptionGroup import itertools -VERSION = "0.0.1" +VERSION = "0.0.2" # PPS (Passwords per Second) Cracking Speed pps = 1000000000 @@ -41,58 +42,124 @@ pps = 1000000000 sample_time = 0 total_time = 0 -################################################################## -# Calculate complexity of a single mask -################################################################## -def complexity(mask): - count = 1 - for char in mask[1:].split("?"): - if char == "l": count *= 26 - elif char == "u": count *= 26 - elif char == "d": count *= 10 - elif char == "s": count *= 33 - else: "[!] Error, unknown mask ?%s" % char +class PolicyGen: + def __init__(self): + self.output_file = None - return count - -################################################################### -# Check whether a sample password mask matches defined policy -################################################################### -def filtermask(maskstring,options): - global total_time, sample_time - - # define character counters - lowercount = uppercount = digitcount = specialcount = 0 - - # calculate password complexity and cracking time - mask_time = complexity(maskstring)/options.pps - total_time += mask_time - - for char in maskstring[1:].split("?"): - if char == "l": lowercount += 1 - elif char == "u": uppercount += 1 - elif char == "d": digitcount += 1 - elif char == "s": specialcount += 1 - - # Filter according to password policy - if lowercount >= options.minlower and lowercount <= options.maxlower and \ - uppercount >= options.minupper and uppercount <= options.maxupper and \ - digitcount >= options.mindigits and digitcount <= options.maxdigits and \ - specialcount >= options.minspecial and specialcount <= options.maxspecial: - sample_time += mask_time - if options.verbose: - print "[*] [%dd|%dh|%dm|%ds] %s [l:%d u:%d d:%d s:%d]" % (mask_time/60/60/24, mask_time/60/60, mask_time/60, mask_time,maskstring,lowercount,uppercount,digitcount,specialcount) - return True - else: - return False + self.minlength = 8 + self.maxlength = 8 + self.mindigit = None + self.minlower = None + self.minupper = None + self.minspecial = None + self.maxdigit = None + self.maxlower = None + self.maxupper = None + self.maxspecial = None -def main(): - # define mask counters - total_count = sample_count = 0 + # PPS (Passwords per Second) Cracking Speed + self.pps = 1000000000 + self.showmasks = False + + def getcomplexity(self, mask): + """ Return mask complexity. """ + count = 1 + for char in mask[1:].split("?"): + if char == "l": count *= 26 + elif char == "u": count *= 26 + elif char == "d": count *= 10 + elif char == "s": count *= 33 + else: print "[!] Error, unknown mask ?%s in a mask %s" % (char,mask) + + return count + + def generate_masks(self, noncompliant): + """ Generate all possible password masks matching the policy """ + + total_count = 0 + sample_count = 0 + + # NOTE: It is better to collect total complexity + # in order not to lose precision when dividing by pps + total_complexity = 0 + sample_complexity = 0 + + # TODO: Randomize or even statistically arrange matching masks + for length in xrange(self.minlength, self.maxlength+1): + print "[*] Generating %d character password masks." % length + total_length_count = 0 + sample_length_count = 0 + + total_length_complexity = 0 + sample_length_complexity = 0 + + for masklist in itertools.product(['?d','?l','?u','?s'], repeat=length): + + mask = ''.join(masklist) + + lowercount = 0 + uppercount = 0 + digitcount = 0 + specialcount = 0 + + mask_complexity = self.getcomplexity(mask) + + total_length_count += 1 + total_length_complexity += mask_complexity + + # Count charachter types in a mask + for char in mask[1:].split("?"): + if char == "l": lowercount += 1 + elif char == "u": uppercount += 1 + elif char == "d": digitcount += 1 + elif char == "s": specialcount += 1 + + # Filter according to password policy + # NOTE: Perform exact opposite (XOR) operation if noncompliant + # flag was set when calling the function. + if ((not self.minlower or lowercount >= self.minlower) and \ + (not self.maxlower or lowercount <= self.maxlower) and \ + (not self.minupper or uppercount >= self.minupper) and \ + (not self.maxupper or uppercount <= self.maxupper) and \ + (not self.mindigit or digitcount >= self.mindigit) and \ + (not self.maxdigit or digitcount <= self.maxdigit) and \ + (not self.minspecial or specialcount >= self.minspecial) and \ + (not self.maxspecial or specialcount <= self.maxspecial)) ^ noncompliant : + + sample_length_count += 1 + sample_length_complexity += mask_complexity + + if self.showmasks: + mask_time = mask_complexity/self.pps + time_human = ">1 year" if mask_time > 60*60*24*365 else str(datetime.timedelta(seconds=mask_time)) + print "[{:>2}] {:<30} [l:{:>2} u:{:>2} d:{:>2} s:{:>2}] [{:>8}] ".format(length, mask, lowercount,uppercount,digitcount,specialcount, time_human) + + if self.output_file: + self.output_file.write("%s\n" % mask) + + + + total_count += total_length_count + sample_count += sample_length_count + + total_complexity += total_length_complexity + sample_complexity += sample_length_complexity + + + total_time = total_complexity/self.pps + total_time_human = ">1 year" if total_time > 60*60*24*365 else str(datetime.timedelta(seconds=total_time)) + print "[*] Total Masks: %d Time: %s" % (total_count, total_time_human) + + sample_time = sample_complexity/self.pps + sample_time_human = ">1 year" if sample_time > 60*60*24*365 else str(datetime.timedelta(seconds=sample_time)) + print "[*] Policy Masks: %d Time: %s" % (sample_count, sample_time_human) + + +if __name__ == "__main__": header = " _ \n" - header += " PolicyGen 0.0.1 | |\n" + header += " PolicyGen %s | |\n" % VERSION header += " _ __ __ _ ___| | _\n" header += " | '_ \ / _` |/ __| |/ /\n" header += " | |_) | (_| | (__| < \n" @@ -103,56 +170,60 @@ def main(): # parse command line arguments parser = OptionParser("%prog [options]\n\nType --help for more options", version="%prog "+VERSION) - parser.add_option("--length", dest="length", help="Password length", type="int", default=8, metavar="8") - parser.add_option("-o", "--output", dest="output",help="Save masks to a file", metavar="masks.txt") - parser.add_option("--pps", dest="pps", help="Passwords per Second", type="int", default=pps, metavar="1000000000") - parser.add_option("-v", "--verbose", action="store_true", dest="verbose") + parser.add_option("-o", "--outputmasks", dest="output_masks",help="Save masks to a file", metavar="masks.hcmask") + parser.add_option("--pps", dest="pps", help="Passwords per Second", type="int", metavar="1000000000") + parser.add_option("--showmasks", dest="showmasks", help="Show matching masks", action="store_true", default=False) + parser.add_option("--noncompliant", dest="noncompliant", help="Generate masks for noncompliant passwords", action="store_true", default=False) group = OptionGroup(parser, "Password Policy", "Define the minimum (or maximum) password strength policy that you would like to test") - group.add_option("--mindigits", dest="mindigits", help="Minimum number of digits", default=0, type="int", metavar="1") - group.add_option("--minlower", dest="minlower", help="Minimum number of lower-case characters", default=0, type="int", metavar="1") - group.add_option("--minupper", dest="minupper", help="Minimum number of upper-case characters", default=0, type="int", metavar="1") - group.add_option("--minspecial", dest="minspecial", help="Minimum number of special characters", default=0, type="int", metavar="1") - group.add_option("--maxdigits", dest="maxdigits", help="Maximum number of digits", default=9999, type="int", metavar="3") - group.add_option("--maxlower", dest="maxlower", help="Maximum number of lower-case characters", default=9999, type="int", metavar="3") - group.add_option("--maxupper", dest="maxupper", help="Maximum number of upper-case characters", default=9999, type="int", metavar="3") - group.add_option("--maxspecial", dest="maxspecial", help="Maximum number of special characters", default=9999, type="int", metavar="3") - parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Don't show headers.") + group.add_option("--minlength", dest="minlength", type="int", metavar="8", default=8, help="Minimum password length") + group.add_option("--maxlength", dest="maxlength", type="int", metavar="8", default=8, help="Maximum password length") + group.add_option("--mindigit", dest="mindigit", type="int", metavar="1", help="Minimum number of digits") + group.add_option("--minlower", dest="minlower", type="int", metavar="1", help="Minimum number of lower-case characters") + group.add_option("--minupper", dest="minupper", type="int", metavar="1", help="Minimum number of upper-case characters") + group.add_option("--minspecial",dest="minspecial",type="int", metavar="1", help="Minimum number of special characters") + group.add_option("--maxdigit", dest="maxdigit", type="int", metavar="3", help="Maximum number of digits") + group.add_option("--maxlower", dest="maxlower", type="int", metavar="3", help="Maximum number of lower-case characters") + group.add_option("--maxupper", dest="maxupper", type="int", metavar="3", help="Maximum number of upper-case characters") + group.add_option("--maxspecial",dest="maxspecial",type="int", metavar="3", help="Maximum number of special characters") parser.add_option_group(group) - (options, args) = parser.parse_args() + parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Don't show headers.") - # cleanup maximum occurence options - if options.maxlower > options.length: options.maxlower = options.length - if options.maxdigits > options.length: options.maxdigits = options.length - if options.mindigits > options.length: options.mindigits = options.length - if options.maxupper > options.length: options.maxupper = options.length - if options.maxspecial > options.length: options.maxspecial = options.length + (options, args) = parser.parse_args() # Print program header if not options.quiet: print header - # print current password policy + policygen = PolicyGen() + + # Settings + if options.output_masks: + print "[*] Saving generated masks to [%s]" % options.output_masks + policygen.output_file = open(options.output_masks, 'w') + + # Password policy + if options.minlength: policygen.minlength = options.minlength + if options.maxlength: policygen.maxlength = options.maxlength + if options.mindigit: policygen.mindigit = options.mindigit + if options.minlower: policygen.minlower = options.minlower + if options.minupper: policygen.minupper = options.minupper + if options.minspecial: policygen.minspecial = options.minspecial + if options.maxdigit: policygen.maxdigits = options.maxdigit + if options.maxlower: policygen.maxlower = options.maxlower + if options.maxupper: policygen.maxupper = options.maxupper + if options.maxspecial: policygen.maxspecial = options.maxspecial + + # Misc + if options.pps: policygen.pps = options.pps + if options.showmasks: policygen.showmasks = options.showmasks + + # Print current password policy print "[*] Password policy:" - print "[+] Password length: %d" % options.length - print "[+] Minimum strength: lower: %d, upper: %d, digits: %d, special: %d" % (options.minlower, options.minupper, options.mindigits, options.minspecial) - print "[+] Maximum strength: lower: %d, upper: %d, digits: %d, special: %d" % (options.maxlower, options.maxupper, options.maxdigits, options.maxspecial) + print " Pass Lengths: min:%d max:%d" % (options.minlength,options.maxlength) + print " Min strength: l:%s u:%s d:%s s:%s" % (options.minlower, options.minupper, options.mindigit, options.minspecial) + print " Max strength: l:%s u:%s d:%s s:%s" % (options.maxlower, options.maxupper, options.maxdigit, options.maxspecial) - if options.output: f = open(options.output, 'w') - - # generate all possible password masks and compare them to policy - # TODO: Randomize or even statistically arrange matching masks - for password in itertools.product(['?l','?u','?d','?s'],repeat=options.length): - if filtermask(''.join(password), options): - if options.output: f.write("%s\n" % ''.join(password)) - sample_count +=1 - total_count += 1 - - if options.output: f.close() - - print "[*] Total Masks: %d Runtime: [%dd|%dh|%dm|%ds]" % (total_count, total_time/60/60/24, total_time/60/60, total_time/60, total_time) - print "[*] Policy Masks: %d Runtime: [%dd|%dh|%dm|%ds]" % (sample_count, sample_time/60/60/24, sample_time/60/60, sample_time/60, sample_time) - -if __name__ == "__main__": - main() + print "[*] Generating [%s] masks." % ("compliant" if not options.noncompliant else "non-compliant") + policygen.generate_masks(options.noncompliant) \ No newline at end of file