pack/policygen.py

206 lines
9.4 KiB
Python
Executable File

#!/usr/bin/python
# PolicyGen - Analyze and Generate password masks according to a password policy
#
# This tool is part of PACK (Password Analysis and Cracking Kit)
#
# VERSION 0.0.2
#
# Copyright (C) 2013 Peter Kacherginsky
# All rights reserved.
#
# Please see the attached LICENSE file for additional licensing information.
import sys, string, random
import datetime
from optparse import OptionParser, OptionGroup
import itertools
VERSION = "0.0.2"
class PolicyGen:
def __init__(self):
self.output_file = None
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
# 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
elif char == "a": count *= 95
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
# 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 ((self.minlower == None or lowercount >= self.minlower) and \
(self.maxlower == None or lowercount <= self.maxlower) and \
(self.minupper == None or uppercount >= self.minupper) and \
(self.maxupper == None or uppercount <= self.maxupper) and \
(self.mindigit == None or digitcount >= self.mindigit) and \
(self.maxdigit == None or digitcount <= self.maxdigit) and \
(self.minspecial == None or specialcount >= self.minspecial) and \
(self.maxspecial == None 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 %s | |\n" % VERSION
header += " _ __ __ _ ___| | _\n"
header += " | '_ \ / _` |/ __| |/ /\n"
header += " | |_) | (_| | (__| < \n"
header += " | .__/ \__,_|\___|_|\_\\\n"
header += " | | \n"
header += " |_| iphelix@thesprawl.org\n"
header += "\n"
# parse command line arguments
parser = OptionParser("%prog [options]\n\nType --help for more options", version="%prog "+VERSION)
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("--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)
parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Don't show headers.")
(options, args) = parser.parse_args()
# Print program header
if not options.quiet:
print header
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 != None: policygen.minlength = options.minlength
if options.maxlength != None: policygen.maxlength = options.maxlength
if options.mindigit != None: policygen.mindigit = options.mindigit
if options.minlower != None: policygen.minlower = options.minlower
if options.minupper != None: policygen.minupper = options.minupper
if options.minspecial != None: policygen.minspecial = options.minspecial
if options.maxdigit != None: policygen.maxdigit = options.maxdigit
if options.maxlower != None: policygen.maxlower = options.maxlower
if options.maxupper != None: policygen.maxupper = options.maxupper
if options.maxspecial != None: policygen.maxspecial = options.maxspecial
# Misc
if options.pps: policygen.pps = options.pps
if options.showmasks: policygen.showmasks = options.showmasks
print "[*] Using {:,d} keys/sec for calculations.".format(policygen.pps)
# Print current password policy
print "[*] Password policy:"
print " Pass Lengths: min:%d max:%d" % (policygen.minlength, policygen.maxlength)
print " Min strength: l:%s u:%s d:%s s:%s" % (policygen.minlower, policygen.minupper, policygen.mindigit, policygen.minspecial)
print " Max strength: l:%s u:%s d:%s s:%s" % (policygen.maxlower, policygen.maxupper, policygen.maxdigit, policygen.maxspecial)
print "[*] Generating [%s] masks." % ("compliant" if not options.noncompliant else "non-compliant")
policygen.generate_masks(options.noncompliant)