Skip to content

Instantly share code, notes, and snippets.

@tmacam
Created March 14, 2012 17:33
Show Gist options
  • Save tmacam/2038097 to your computer and use it in GitHub Desktop.
Save tmacam/2038097 to your computer and use it in GitHub Desktop.
Verifies a password's strength by measuring its entropy.
"""Verifies a password's strength by measuring its entropy.
Notice that this form of password strength testing does not distinguish easy to
guess passwords like "1111...." from supposedly good passwords like "%hF6Nz-0".
Some rule/points based passwords tests can and should be used together
with this to asset a password's strengh.
References:
- http://xkcd.com/936/
- http://en.wikipedia.org/wiki/Password_strength
- http://www.redkestrel.co.uk/Articles/RandomPasswordStrength.html
- http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
- http://stackoverflow.com/questions/75057/what-is-the-best-way-to-check-the-strength-of-a-password
"""
import re
import math
import getpass
SYMBOLS_SET_LIST = (
('empty password', '^$', 0.0),
('digits', '^\d+$', math.log(10,2)),
('hex', '^[a-f0-9]+$', math.log(16,2)),
('hex', '^[A-F0-9]+$', math.log(16,2)),
('hex2', '^[a-fA-F0-9]+$', math.log(24,2)),
('lowercase letters', '^[a-z]+$', math.log(26, 2)),
('uppercase letters', '^[A-Z]+$', math.log(26, 2)),
('mixedcase letters', '^[a-zA-Z]+$', math.log(26*2, 2)),
('mixedcase alphanum', '^[a-zA-Z0-9]+$', math.log(26*2 +10, 2)),
# assume ASCII-printable in best case
('printable ASCII', '^.+$', math.log(94, 2)),
)
#NIST reccomends at least 80 bits of entropy.
NIST_MIN_ENTROPY = 80.0
def TextStringIsPlainASCII(text):
if any(ord(c) >= 128 for c in text):
return False
return True
def GetPasswordSymbolSetDetails(pw):
"""Given a password, finds the symbol set with least entropy that contains
all characters in the password and, for this set, returns its (description,
entropy per charcter)."""
for desc, pattern, entropy_per_char in SYMBOLS_SET_LIST:
# our REs assume we are using ASCII. This may not be the case but,
# in the worst case, we will just understimate the password strength.
if re.match(pattern, pw):
return desc, entropy_per_char
return (None, None)
def PrintPwStrengthInfo(pw):
if len(pw) == 0:
print "Empty password. Dude, you are not even trying..."
return 1
if not TextStringIsPlainASCII(pw):
print "Your password is not plain ASCII. Are you sure you want this?"
print "We will understimate its strength -- but this is a good thing."
details = GetPasswordSymbolSetDetails(pw)
if details is not None:
desc, entropy_per_char = details
unique_chars = set(ord(i) for i in pw)
unique_chars_count = len(unique_chars)
char_range_len = max(unique_chars) - min(unique_chars) +1
total_entropy = len(pw) * entropy_per_char
# The following two metrics bellow are completely made made up:
# - use only the uniq characters in the password to calc. its entropy
unique_chars_entropy = unique_chars_count * entropy_per_char
# - get the tightest range or ASCII chars that contains all
# characters in the password and use this instead of the data
# from GetPasswordSymbolSetDetails to calc. per-char entropy.
char_range_entropy = len(pw) * math.log(char_range_len, 2)
min_entropy = min(total_entropy, unique_chars_entropy,
char_range_entropy)
print "Password Details:"
print "\tLength: %i\n\tUnique chars.: %i\n\tMax-Min char range: %i" % (
len(pw), unique_chars_count, char_range_len)
print "\tContaining symbol set:", desc
print "\tEntropy per digit in given symbol class:", entropy_per_char
print "Entropy result due to..."
print "\tclass os symbols used:", total_entropy
print "\tunique chars. used and their class:", unique_chars_entropy
print "\trange of characters used, ignoring their class:", \
char_range_entropy
if min_entropy < NIST_MIN_ENTROPY:
print "\nYou seem to have a weak password"
else:
print "\nYeah, your password's strength seems fine."
if __name__ == '__main__':
pw = getpass.getpass("Type password to test:")
PrintPwStrengthInfo(pw)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment