Updated documentation.
Implemented policy detection in statsgen. Statsgen is now free of regex to speed things up.
This commit is contained in:
parent
32e92d10d3
commit
7ef9748553
@ -3,7 +3,7 @@
|
||||
#
|
||||
# This tool is part of PACK (Password Analysis and Cracking Kit)
|
||||
#
|
||||
# VERSION 0.0.2
|
||||
# VERSION 0.0.3
|
||||
#
|
||||
# Copyright (C) 2013 Peter Kacherginsky
|
||||
# All rights reserved.
|
||||
|
32
rulegen.py
32
rulegen.py
@ -811,7 +811,7 @@ class RuleGen:
|
||||
|
||||
|
||||
def password_worker(self,i, passwords_queue, rules_queue, words_queue):
|
||||
print "[*] Password analysis worker [%d] started." % i
|
||||
if self.debug: print "[*] Password analysis worker [%d] started." % i
|
||||
try:
|
||||
while True:
|
||||
password = passwords_queue.get()
|
||||
@ -821,15 +821,16 @@ class RuleGen:
|
||||
|
||||
self.analyze_password(password, rules_queue, words_queue)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
print "[*] Password analysis worker [%d] terminated." % i
|
||||
if self.debug: print "[*] Password analysis worker [%d] terminated." % i
|
||||
|
||||
print "[*] Password analysis worker [%d] stopped." % i
|
||||
if self.debug: print "[*] Password analysis worker [%d] stopped." % i
|
||||
|
||||
def rule_worker(self, rules_queue, output_rules_filename):
|
||||
""" Worker to store generated rules. """
|
||||
print "[*] Saving rules to %s" % output_rules_filename
|
||||
|
||||
f = open(output_rules_filename, 'w')
|
||||
print "[*] Rule worker started."
|
||||
if self.debug: print "[*] Rule worker started."
|
||||
try:
|
||||
while True:
|
||||
rule = rules_queue.get()
|
||||
@ -841,16 +842,17 @@ class RuleGen:
|
||||
f.flush()
|
||||
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
print "[*] Rule worker terminated."
|
||||
if self.debug: print "[*] Rule worker terminated."
|
||||
|
||||
f.close()
|
||||
print "[*] Rule worker stopped."
|
||||
if self.debug: print "[*] Rule worker stopped."
|
||||
|
||||
def word_worker(self, words_queue, output_words_filename):
|
||||
""" Worker to store generated rules. """
|
||||
print "[*] Saving words to %s" % output_words_filename
|
||||
|
||||
f = open(output_words_filename, 'w')
|
||||
print "[*] Word worker started."
|
||||
if self.debug: print "[*] Word worker started."
|
||||
try:
|
||||
while True:
|
||||
word = words_queue.get()
|
||||
@ -862,16 +864,17 @@ class RuleGen:
|
||||
f.flush()
|
||||
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
print "[*] Word worker terminated."
|
||||
if self.debug: print "[*] Word worker terminated."
|
||||
|
||||
f.close()
|
||||
print "[*] Word worker stopped."
|
||||
if self.debug: print "[*] Word worker stopped."
|
||||
|
||||
# Analyze passwords file
|
||||
def analyze_passwords_file(self,passwords_file):
|
||||
""" Analyze provided passwords file. """
|
||||
|
||||
print "[*] Analyzing passwords file: %s:" % passwords_file
|
||||
print "[*] Press Ctrl-C to end execution and generate statistical analysis."
|
||||
|
||||
# Setup queues
|
||||
passwords_queue = multiprocessing.Queue(multiprocessing.cpu_count() * 100)
|
||||
@ -948,7 +951,7 @@ class RuleGen:
|
||||
rules_counter = Counter(rules_file)
|
||||
rule_counter_total = sum(rules_counter.values())
|
||||
|
||||
print "\n[*] Top 10 rule statistics"
|
||||
print "\n[*] Top 10 rules"
|
||||
rules_i = 0
|
||||
for (rule, count) in rules_counter.most_common():
|
||||
rules_sorted_file.write(rule)
|
||||
@ -964,7 +967,7 @@ class RuleGen:
|
||||
words_counter = Counter(words_file)
|
||||
word_counter_total = sum(rules_counter.values())
|
||||
|
||||
print "\n[*] Top 10 word statistics"
|
||||
print "\n[*] Top 10 words"
|
||||
words_i = 0
|
||||
for (word, count) in words_counter.most_common():
|
||||
words_sorted_file.write(word)
|
||||
@ -1082,13 +1085,10 @@ if __name__ == "__main__":
|
||||
print "[*] Using Enchant '%s' module. For best results please install" % rulegen.enchant.provider.name
|
||||
print " '%s' module language dictionaries." % rulegen.enchant.provider.name
|
||||
|
||||
if not options.quiet:
|
||||
print "[*] Saving rules to %s.rule" % options.basename
|
||||
print "[*] Saving words to %s.word" % options.basename
|
||||
print "[*] Press Ctrl-C to end execution and generate statistical analysis."
|
||||
|
||||
|
||||
# Analyze a single password or several passwords in a file
|
||||
if options.password:
|
||||
rulegen.analyze_password(args[0])
|
||||
else:
|
||||
else:
|
||||
rulegen.analyze_passwords_file(args[0])
|
||||
|
174
statsgen.py
174
statsgen.py
@ -3,18 +3,19 @@
|
||||
#
|
||||
# This tool is part of PACK (Password Analysis and Cracking Kit)
|
||||
#
|
||||
# VERSION 0.0.2
|
||||
# VERSION 0.0.3
|
||||
#
|
||||
# Copyright (C) 2013 Peter Kacherginsky
|
||||
# All rights reserved.
|
||||
#
|
||||
# Please see the attached LICENSE file for additiona licensing information.
|
||||
# Please see the attached LICENSE file for additional licensing information.
|
||||
|
||||
import sys
|
||||
import re, operator, string
|
||||
from optparse import OptionParser, OptionGroup
|
||||
import time
|
||||
|
||||
VERSION = "0.0.2"
|
||||
VERSION = "0.0.3"
|
||||
|
||||
class StatsGen:
|
||||
def __init__(self):
|
||||
@ -25,37 +26,8 @@ class StatsGen:
|
||||
self.maxlength = None
|
||||
self.simplemasks = None
|
||||
self.charsets = None
|
||||
|
||||
# Constants
|
||||
self.chars_regex = list()
|
||||
self.chars_regex.append(('numeric',re.compile('^[0-9]+$')))
|
||||
self.chars_regex.append(('loweralpha',re.compile('^[a-z]+$')))
|
||||
self.chars_regex.append(('upperalpha',re.compile('^[A-Z]+$')))
|
||||
self.chars_regex.append(('mixedalpha',re.compile('^[a-zA-Z]+$')))
|
||||
self.chars_regex.append(('loweralphanum',re.compile('^[a-z0-9]+$')))
|
||||
self.chars_regex.append(('upperalphanum',re.compile('^[A-Z0-9]+$')))
|
||||
self.chars_regex.append(('mixedalphanum',re.compile('^[a-zA-Z0-9]+$')))
|
||||
self.chars_regex.append(('special',re.compile('^[^a-zA-Z0-9]+$')))
|
||||
self.chars_regex.append(('loweralphaspecial',re.compile('^[^A-Z0-9]+$')))
|
||||
self.chars_regex.append(('upperalphaspecial',re.compile('^[^a-z0-9]+$')))
|
||||
self.chars_regex.append(('mixedalphaspecial',re.compile('^[^0-9]+$')))
|
||||
self.chars_regex.append(('loweralphaspecialnum',re.compile('^[^A-Z]+$')))
|
||||
self.chars_regex.append(('upperalphaspecialnum',re.compile('^[^a-z]+$')))
|
||||
self.chars_regex.append(('mixedalphaspecialnum',re.compile('.*')))
|
||||
|
||||
self.masks_regex = list()
|
||||
self.masks_regex.append(('alldigit',re.compile('^\d+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('allstring',re.compile('^[a-z]+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('stringdigit',re.compile('^[a-z]+\d+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('digitstring',re.compile('^\d+[a-z]+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('digitstringdigit',re.compile('^\d+[a-z]+\d+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('stringdigitstring',re.compile('^[a-z]+\d+[a-z]+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('allspecial',re.compile('^[^a-z0-9]+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('stringspecial',re.compile('^[a-z]+[^a-z0-9]+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('specialstring',re.compile('^[^a-z0-9]+[a-z]+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('stringspecialstring',re.compile('^[a-z]+[^a-z0-9]+[a-z]+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('stringspecialdigit',re.compile('^[a-z]+[^a-z0-9]+\d+$', re.IGNORECASE)))
|
||||
self.masks_regex.append(('specialstringspecial',re.compile('^[^a-z0-9]+[a-z]+[^a-z0-9]+$', re.IGNORECASE)))
|
||||
self.quiet = False
|
||||
self.debug = True
|
||||
|
||||
# Stats dictionaries
|
||||
self.stats_length = dict()
|
||||
@ -63,33 +35,88 @@ class StatsGen:
|
||||
self.stats_advancedmasks = dict()
|
||||
self.stats_charactersets = dict()
|
||||
|
||||
# Ignore stats with less than 1% coverage
|
||||
self.hiderare = False
|
||||
|
||||
self.filter_counter = 0
|
||||
self.total_counter = 0
|
||||
|
||||
def simplemasks_check(self, password):
|
||||
for (name,regex) in self.masks_regex:
|
||||
if regex.match(password):
|
||||
return name
|
||||
else:
|
||||
return "othermask"
|
||||
# Minimum password complexity counters
|
||||
self.mindigit = None
|
||||
self.minupper = None
|
||||
self.minlower = None
|
||||
self.minspecial = None
|
||||
|
||||
def characterset_check(self, password):
|
||||
for (name,regex) in self.chars_regex:
|
||||
if regex.match(password):
|
||||
return name
|
||||
else:
|
||||
return "otherchar"
|
||||
self.maxdigit = None
|
||||
self.maxupper = None
|
||||
self.maxlower = None
|
||||
self.maxspecial = None
|
||||
|
||||
def advancedmask_check(self, password):
|
||||
mask = list()
|
||||
def analyze_password(self, password):
|
||||
|
||||
# Password length
|
||||
pass_length = len(password)
|
||||
|
||||
# Character-set and policy counters
|
||||
digit = 0
|
||||
lower = 0
|
||||
upper = 0
|
||||
special = 0
|
||||
|
||||
simplemask = list()
|
||||
advancedmask_string = ""
|
||||
|
||||
# Detect simple and advanced masks
|
||||
for letter in password:
|
||||
if letter in string.digits: mask.append("?d")
|
||||
elif letter in string.lowercase: mask.append("?l")
|
||||
elif letter in string.uppercase: mask.append("?u")
|
||||
else: mask.append("?s")
|
||||
return "".join(mask)
|
||||
|
||||
if letter in string.digits:
|
||||
digit += 1
|
||||
advancedmask_string += "?d"
|
||||
if not simplemask or not simplemask[-1] == 'digit': simplemask.append('digit')
|
||||
|
||||
elif letter in string.lowercase:
|
||||
lower += 1
|
||||
advancedmask_string += "?l"
|
||||
if not simplemask or not simplemask[-1] == 'string': simplemask.append('string')
|
||||
|
||||
|
||||
elif letter in string.uppercase:
|
||||
upper += 1
|
||||
advancedmask_string += "?u"
|
||||
if not simplemask or not simplemask[-1] == 'string': simplemask.append('string')
|
||||
|
||||
else:
|
||||
special += 1
|
||||
advancedmask_string += "?s"
|
||||
if not simplemask or not simplemask[-1] == 'special': simplemask.append('special')
|
||||
|
||||
|
||||
# String representation of masks
|
||||
simplemask_string = ''.join(simplemask) if len(simplemask) <= 3 else 'othermask'
|
||||
|
||||
# Policy
|
||||
policy = (digit,lower,upper,special)
|
||||
|
||||
# Determine character-set
|
||||
if digit and not lower and not upper and not special: charset = 'numeric'
|
||||
elif not digit and lower and not upper and not special: charset = 'loweralpha'
|
||||
elif not digit and not lower and upper and not special: charset = 'upperalpha'
|
||||
elif not digit and not lower and not upper and special: charset = 'special'
|
||||
|
||||
elif not digit and lower and upper and not special: charset = 'mixedalpha'
|
||||
elif digit and lower and not upper and not special: charset = 'loweralphanum'
|
||||
elif digit and not lower and upper and not special: charset = 'upperalphanum'
|
||||
elif not digit and lower and not upper and special: charset = 'loweralphaspecial'
|
||||
elif not digit and not lower and upper and special: charset = 'upperalphaspecial'
|
||||
elif digit and not lower and not upper and special: charset = 'specialnum'
|
||||
|
||||
elif not digit and lower and upper and special: charset = 'mixedalphaspecial'
|
||||
elif digit and not lower and upper and special: charset = 'upperalphaspecialnum'
|
||||
elif digit and lower and not upper and special: charset = 'loweralphaspecialnum'
|
||||
elif digit and lower and upper and not special: charset = 'mixedalphanum'
|
||||
else: charset = 'all'
|
||||
|
||||
return (pass_length, charset, simplemask_string, advancedmask_string, policy)
|
||||
|
||||
def generate_stats(self, filename):
|
||||
""" Generate password statistics. """
|
||||
@ -98,19 +125,32 @@ class StatsGen:
|
||||
|
||||
for password in f:
|
||||
password = password.rstrip('\r\n')
|
||||
|
||||
if len(password) == 0: continue
|
||||
|
||||
self.total_counter += 1
|
||||
|
||||
pass_length = len(password)
|
||||
characterset = self.characterset_check(password)
|
||||
simplemask = self.simplemasks_check(password)
|
||||
advancedmask = self.advancedmask_check(password)
|
||||
|
||||
(pass_length,characterset,simplemask,advancedmask, policy) = self.analyze_password(password)
|
||||
(digit,lower,upper,special) = policy
|
||||
|
||||
if (self.charsets == None or characterset in self.charsets) and \
|
||||
(self.simplemasks == None or simplemask in self.simplemasks) and \
|
||||
(self.maxlength == None or pass_length <= self.maxlength) and \
|
||||
(self.minlength == None or pass_length >= self.minlength):
|
||||
|
||||
self.filter_counter += 1
|
||||
self.filter_counter += 1
|
||||
|
||||
if self.mindigit == None or digit < self.mindigit: self.mindigit = digit
|
||||
if self.maxdigit == None or digit > self.maxdigit: self.maxdigit = digit
|
||||
|
||||
if self.minupper == None or upper < self.minupper: self.minupper = upper
|
||||
if self.maxupper == None or upper > self.maxupper: self.maxupper = upper
|
||||
|
||||
if self.minlower == None or lower < self.minlower: self.minlower = lower
|
||||
if self.maxlower == None or lower > self.maxlower: self.maxlower = lower
|
||||
|
||||
if self.minspecial == None or special < self.minspecial: self.minspecial = special
|
||||
if self.maxspecial == None or special > self.maxspecial: self.maxspecial = special
|
||||
|
||||
if pass_length in self.stats_length:
|
||||
self.stats_length[pass_length] += 1
|
||||
@ -139,22 +179,28 @@ class StatsGen:
|
||||
|
||||
print "[+] Analyzing %d%% (%d/%d) of passwords" % (self.filter_counter*100/self.total_counter, self.filter_counter, self.total_counter)
|
||||
print " NOTE: Statistics below is relative to the number of analyzed passwords, not total number of passwords"
|
||||
print "\n[*] Line Count Statistics..."
|
||||
print "\n[*] Length:"
|
||||
for (length,count) in sorted(self.stats_length.iteritems(), key=operator.itemgetter(1), reverse=True):
|
||||
if self.hiderare and not count*100/self.filter_counter > 0: continue
|
||||
print "[+] %25d: %02d%% (%d)" % (length, count*100/self.filter_counter, count)
|
||||
|
||||
print "\n[*] Charset statistics..."
|
||||
print "\n[*] Character-set:"
|
||||
for (char,count) in sorted(self.stats_charactersets.iteritems(), key=operator.itemgetter(1), reverse=True):
|
||||
if self.hiderare and not count*100/self.filter_counter > 0: continue
|
||||
print "[+] %25s: %02d%% (%d)" % (char, count*100/self.filter_counter, count)
|
||||
|
||||
print "\n[*] Simple Mask statistics..."
|
||||
print "\n[*] Password complexity:"
|
||||
print "[+] digit: min(%s) max(%s)" % (self.mindigit, self.maxdigit)
|
||||
print "[+] lower: min(%s) max(%s)" % (self.minlower, self.maxlower)
|
||||
print "[+] upper: min(%s) max(%s)" % (self.minupper, self.maxupper)
|
||||
print "[+] special: min(%s) max(%s)" % (self.minspecial, self.maxspecial)
|
||||
|
||||
print "\n[*] Simple Masks:"
|
||||
for (simplemask,count) in sorted(self.stats_simplemasks.iteritems(), key=operator.itemgetter(1), reverse=True):
|
||||
if self.hiderare and not count*100/self.filter_counter > 0: continue
|
||||
print "[+] %25s: %02d%% (%d)" % (simplemask, count*100/self.filter_counter, count)
|
||||
|
||||
print "\n[*] Advanced Mask statistics..."
|
||||
print "\n[*] Advanced Masks:"
|
||||
for (advancedmask,count) in sorted(self.stats_advancedmasks.iteritems(), key=operator.itemgetter(1), reverse=True):
|
||||
if count*100/self.filter_counter > 0:
|
||||
print "[+] %25s: %02d%% (%d)" % (advancedmask, count*100/self.filter_counter, count)
|
||||
@ -213,4 +259,4 @@ if __name__ == "__main__":
|
||||
statsgen.output_file = open(options.output_file, 'w')
|
||||
|
||||
statsgen.generate_stats(args[0])
|
||||
statsgen.print_stats()
|
||||
statsgen.print_stats()
|
||||
|
Loading…
x
Reference in New Issue
Block a user