/relevation/branches/1.1/devtools/checkpw.py |
---|
0,0 → 1,105 |
#!/usr/bin/env python |
""" |
Simplistic Password Strength Checker. |
""" |
# Based on |
# <http://www.geekwisdom.com/dyn/passwdmeter> |
# |-> <http://www.geekwisdom.com/js/passwordmeter.js> |
# (this is mostly based on the scoring system explained there, |
# and not on the actual implementation) |
import re |
import string |
import sys |
WEAK_THRESHOLD = 16 |
MEDIOCRE_THRESHOLD = 25 |
STRONG_THRESHOLD = 35 |
VERY_STRONG_THRESHOLD = 45 |
def check(pw): |
''' |
check(str) -> ( int score, str strength category, str description) |
''' |
score = 0 |
verdict = 'weak' |
log = '' |
# Password length |
length = len(pw) |
if length == 0: |
return ( 0, 'weak', 'empty password' ) |
if length < 5: |
score += 3 |
elif length < 8: |
score += 6 |
elif length < 16: |
score += 12 |
else: |
score += 18 |
log += '%d points for length (%d)\n' % (score, length) |
# Letters |
locase = re.search('[a-z]', pw) |
upcase = re.search('[A-Z]', pw) |
if (locase and upcase): |
score += 7 |
log += '7 points for mixed case\n' |
elif locase: |
score += 5 |
log += '5 points for all-lowercase letters\n' |
elif upcase: |
score += 5 |
log += '5 points for all-uppercase letters\n' |
else: # No letters at all |
pass |
# Numbers |
hasnums = re.search('\d', pw) |
if hasnums and re.search('\d.*\d.*\d', pw): |
score += 7 |
log += '7 points for at least three numbers\n' |
elif hasnums: |
score += 5 |
log += '5 points for at least one number\n' |
# Special Characters |
sch = string.punctuation |
hasspecial = re.search('[%s]' % sch, pw) |
if hasspecial and re.search('[%s].*[%s]' % ( sch, sch), pw): |
score += 10 |
log += '10 points for at least two special characters\n' |
elif hasspecial: |
score += 5 |
log += '5 points for at least one special character\n' |
# Combos |
hasletters = re.search('([a-z]|[A-Z])', pw) |
if hasnums and hasletters: |
score += 1 |
log += '1 combo point for mixed letters and numbers\n' |
if hasspecial: |
score += 2 |
log += '2 combo points for mixed letters, numbers and special characters\n' |
if upcase and locase: |
score += 2 |
log += '2 combo point for mixed case letters, numbers and special characters' |
# Verdict |
if score < WEAK_THRESHOLD: |
verdict = 'very weak' |
elif score < MEDIOCRE_THRESHOLD: |
verdict = 'weak' |
elif score < STRONG_THRESHOLD: |
verdict = 'mediocre' |
elif score < VERY_STRONG_THRESHOLD: |
verdict = 'strong' |
else: |
verdict = 'stronger' |
return ( score, verdict, log ) |
if __name__ == '__main__': |
for candidate in sys.argv[1:]: |
( score, verdict, descr ) = check(candidate) |
print '%s: %s\t%s' % ( candidate, score, verdict ) |
sys.stderr.write(descr) |
# vim:set ts=4 et ai: # |
Property changes: |
Added: svn:executable |
+* |
\ No newline at end of property |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/relevation/branches/1.1/devtools/genpw.py |
---|
14,6 → 14,12 |
import locale |
import sys |
try: |
import checkpw |
DO_CHECK=True |
except ImportError: |
DO_CHECK=False |
DEFAULT_RESULTS = 20 |
DEFAULT_LENGTH = 8 |
#Letters and digits are repeated to favour them |
22,17 → 28,51 |
DEFAULT_CSET = FAVOURED_SET * 8 + STDSET |
REJECTS_SET = [ 'l', '1', 'I', '0', 'O' ] # FIXME: What else? |
DEFAULT_FORCE = '*' # Symbolic |
def pwgen(length=DEFAULT_LENGTH, possible=DEFAULT_CSET, reject_ambiguous=False): |
def pwgen(length=DEFAULT_LENGTH, possible=DEFAULT_CSET, |
reject_ambiguous=False, force=DEFAULT_FORCE): |
''' |
pwgen(int, str, bool, [ str, str, ...]) -> str |
Generate a password. |
length - Password length |
possible - List of characters from where to pick |
reject_ambiguous - Reject characters that can be hard to tell from each other (e.g. 1 vs l vs I) |
force - List of forcible sets. Force at least one character of each set. |
''' |
pw = '' |
rejects = [] |
LENGTH = length |
if reject_ambiguous: |
rejects = REJECTS_SET |
if force == DEFAULT_FORCE: |
force = [ string.lowercase, string.uppercase, string.digits ] |
if type(force) != list: |
raise ValueError('force must be a list of strings') |
def pick_one(cset): |
''' |
Return a random character from cset |
''' |
c = random.choice(cset) |
while c in rejects: |
c = random.choice(cset) |
return c |
# Forcible includes |
for cset in force: |
pw += pick_one(cset) |
length -= 1 |
for i in range(length): |
pick = random.choice(possible) |
while pick in rejects: |
pick = random.choice(possible) |
pw += pick |
# Re-mix order (randomize forced-characters' position) |
if len(pw) > LENGTH: |
pw = pw[0:LENGTH] |
l = list(pw) |
random.shuffle(l) |
pw = ''.join(l) |
return pw |
def main(argv): |
40,6 → 80,7 |
length = DEFAULT_LENGTH |
cset = DEFAULT_CSET |
reject_ambiguous = False |
secure = False # When True, reject mediocre passwords and below |
# No need to use getopt or anything like it |
positional = [] |
46,6 → 87,10 |
for arg in argv: |
if arg in ( '-B', '--ambiguous' ): |
reject_ambiguous = True |
elif arg in ( '-s', '--secure' ): |
if not DO_CHECK: |
raise EnvironmentError('Can\'t generate secure-only password without checkpw.py') |
secure = True |
else: |
positional.append(arg) |
try: |
57,10 → 102,23 |
sys.stderr.write('Usage: pwgen [-B] [length] [num pw]\n'); |
sys.exit(2); |
def newpw(): |
return pwgen(length=length, reject_ambiguous=reject_ambiguous) |
for i in range(rounds): |
print pwgen(length=length, reject_ambiguous=reject_ambiguous) |
pw = newpw() |
if DO_CHECK: |
( score, verdict, _ ) = checkpw.check(pw) |
while score < checkpw.STRONG_THRESHOLD: |
pw = newpw() |
( score, verdict, _ ) = checkpw.check(pw) |
print '%s\t%d\t%s' % ( pw, score, verdict) |
#print _ |
else: |
print pw |
if __name__ == '__main__': |
locale.setlocale(locale.LC_ALL, 'C') |
main(sys.argv[1:]) |
# vim:set ts=4 et ai: # |
Property changes: |
Added: svn:executable |
+* |
\ No newline at end of property |