Updated Policy Generator with better logic and a new --noncompliant flag to test

for passwords not following defined password policy.
This commit is contained in:
iphelix 2013-07-20 23:15:12 -07:00
parent 05013d199b
commit a55be30bc5
2 changed files with 164 additions and 92 deletions

View File

@ -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)

View File

@ -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)