Created
March 14, 2012 17:33
-
-
Save tmacam/2038097 to your computer and use it in GitHub Desktop.
Verifies a password's strength by measuring its entropy.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""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