Skip to content

Add Python 3.2-3.4 compatibility and enable distribution as a wheel #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,61 @@
*~
.*.swp
*.pyc

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include LICENSE.txt
include tests.txt
include zxcvbn/generated/frequency_lists.json
include zxcvbn/generated/adjacency_graphs.json
5 changes: 5 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[bdist_wheel]
# This flag says that the code is written to work on both Python 2 and Python
# 3. If at all possible, it is good practice to do this. If you cannot, you
# will need to generate wheels for each Python version that you support.
universal=1
18 changes: 16 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from distutils.core import setup
from setuptools import setup

setup(name='zxcvbn',
version='1.0',
Expand All @@ -7,5 +7,19 @@
author_email='[email protected]',
url='https://www.github.com/rpearl/python-zxcvbn',
packages=['zxcvbn'],
package_data={'zxcvbn': ['generated/frequency_lists.json', 'generated/adjacency_graphs.json']}
package_data={
'zxcvbn': ['generated/frequency_lists.json',
'generated/adjacency_graphs.json',
],
},
classifiers=[
'Intended Audience :: Developers',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
],
)
19 changes: 14 additions & 5 deletions zxcvbn/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)

from zxcvbn import main

__all__ = ['password_strength']
Expand All @@ -7,12 +10,18 @@

if __name__ == '__main__':
import fileinput
ignored = ('match_sequence', 'password')
# Ensure fields are displayed in a deterministic order
display_fields = [
'crack_time_display',
'crack_time',
'score',
'entropy',
'calc_time',
]

for line in fileinput.input():
pw = line.strip()
print "Password: " + pw
print("Password: " + pw)
out = password_strength(pw)
for key, value in out.iteritems():
if key not in ignored:
print "\t%s: %s" % (key, value)
for key in display_fields:
print("\t%s: %s" % (key, out[key]))
3 changes: 3 additions & 0 deletions zxcvbn/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)

import time

from zxcvbn.matching import omnimatch
Expand Down
28 changes: 17 additions & 11 deletions zxcvbn/matching.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)

from itertools import groupby
import pkg_resources
import re
import sys

try:
import simplejson as json
Expand Down Expand Up @@ -29,8 +33,8 @@ def dictionary_match(password, ranked_dict):

pw_lower = password.lower()

for i in xrange(0, length):
for j in xrange(i, length):
for i in range(0, length):
for j in range(i, length):
word = pw_lower[i:j+1]
if word in ranked_dict:
rank = ranked_dict[word]
Expand Down Expand Up @@ -64,22 +68,24 @@ def _build_ranked_dict(unranked_list):

def _load_frequency_lists():
data = pkg_resources.resource_string(__name__, 'generated/frequency_lists.json')
data = data.decode('utf-8')
dicts = json.loads(data)
for name, wordlist in dicts.items():
for name, wordlist in list(dicts.items()):
DICTIONARY_MATCHERS.append(_build_dict_matcher(name, _build_ranked_dict(wordlist)))


def _load_adjacency_graphs():
global GRAPHS
data = pkg_resources.resource_string(__name__, 'generated/adjacency_graphs.json')
data = data.decode('utf-8')
GRAPHS = json.loads(data)


# on qwerty, 'g' has degree 6, being adjacent to 'ftyhbv'. '\' has degree 1.
# this calculates the average over all keys.
def _calc_average_degree(graph):
average = 0.0
for neighbors in graph.values():
for neighbors in list(graph.values()):
average += len([n for n in neighbors if n is not None])

average /= len(graph)
Expand Down Expand Up @@ -122,7 +128,7 @@ def relevant_l33t_subtable(password):
password_chars = set(password)

filtered = {}
for letter, subs in L33T_TABLE.items():
for letter, subs in list(L33T_TABLE.items()):
relevent_subs = [sub for sub in subs if sub in password_chars]
if len(relevent_subs) > 0:
filtered[letter] = relevent_subs
Expand All @@ -142,7 +148,7 @@ def dedup(subs):
deduped.append(sub)
return deduped

keys = table.keys()
keys = list(table.keys())
while len(keys) > 0:
first_key = keys[0]
rest_keys = keys[1:]
Expand All @@ -166,7 +172,7 @@ def dedup(subs):
next_subs.append(sub_alternative)
subs = dedup(next_subs)
keys = rest_keys
return map(dict, subs)
return [dict(sub) for sub in subs]


def l33t_match(password):
Expand All @@ -182,13 +188,13 @@ def l33t_match(password):
if token.lower() == match['matched_word']:
continue
match_sub = {}
for subbed_chr, char in sub.items():
for subbed_chr, char in list(sub.items()):
if token.find(subbed_chr) != -1:
match_sub[subbed_chr] = char
match['l33t'] = True
match['token'] = token
match['sub'] = match_sub
match['sub_display'] = ', '.join([("%s -> %s" % (k, v)) for k, v in match_sub.items()])
match['sub_display'] = ', '.join([("%s -> %s" % (k, v)) for k, v in list(match_sub.items())])
matches.append(match)
return matches

Expand All @@ -198,7 +204,7 @@ def l33t_match(password):

def spatial_match(password):
matches = []
for graph_name, graph in GRAPHS.items():
for graph_name, graph in list(GRAPHS.items()):
matches.extend(spatial_match_helper(password, graph, graph_name))
return matches

Expand Down Expand Up @@ -293,7 +299,7 @@ def sequence_match(password):
seq = None # either lower, upper, or digits
seq_name = None
seq_direction = None # 1 for ascending seq abcd, -1 for dcba
for seq_candidate_name, seq_candidate in SEQUENCES.items():
for seq_candidate_name, seq_candidate in list(SEQUENCES.items()):
i_n = seq_candidate.find(password[i])
j_n = seq_candidate.find(password[j]) if j < len(password) else -1

Expand Down
17 changes: 10 additions & 7 deletions zxcvbn/scoring.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)

import math
import re

Expand Down Expand Up @@ -253,7 +256,7 @@ def spatial_entropy(match):
if 'shifted_count' in match:
S = match['shifted_count']
U = L - S # unshifted count
possibilities = sum(binom(S + U, i) for i in xrange(0, min(S, U) + 1))
possibilities = sum(binom(S + U, i) for i in range(0, min(S, U) + 1))
entropy += lg(possibilities)
return entropy

Expand Down Expand Up @@ -294,7 +297,7 @@ def extra_l33t_entropy(match):
if 'l33t' not in match or not match['l33t']:
return 0
possibilities = 0
for subbed, unsubbed in match['sub'].items():
for subbed, unsubbed in list(match['sub'].items()):
sub_len = len([x for x in match['token'] if x == subbed])
unsub_len = len([x for x in match['token'] if x == unsubbed])
possibilities += sum(binom(unsub_len + sub_len, i) for i in range(0, min(unsub_len, sub_len) + 1))
Expand Down Expand Up @@ -330,14 +333,14 @@ def display_time(seconds):
if seconds < minute:
return 'instant'
elif seconds < hour:
return str(1 + math.ceil(seconds / minute)) + " minutes"
return '%s minutes' % (1 + math.ceil(seconds / minute),)
elif seconds < day:
return str(1 + math.ceil(seconds / hour)) + " hours"
return '%s hours' % (1 + math.ceil(seconds / hour),)
elif seconds < month:
return str(1 + math.ceil(seconds / day)) + " days"
return '%s days' % (1 + math.ceil(seconds / day),)
elif seconds < year:
return str(1 + math.ceil(seconds / month)) + " months"
return '%s months' % (1 + math.ceil(seconds / month),)
elif seconds < century:
return str(1 + math.ceil(seconds / year)) + " years"
return '%s years' % (1 + math.ceil(seconds / year),)
else:
return 'centuries'
37 changes: 21 additions & 16 deletions zxcvbn/scripts/build_frequency_lists.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from __future__ import with_statement
from __future__ import (absolute_import, division,
print_function, unicode_literals)

import os
import time
import codecs
Expand All @@ -8,7 +10,10 @@
except ImportError:
import json

import urllib2
try:
import urllib.request as urllib_request
except ImportError:
import urllib2 as urllib_request

SLEEP_TIME = 20 # seconds

Expand All @@ -24,11 +29,11 @@ def get_ranked_english():
'''
URL_TMPL = 'http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/TV/2006/%s'
urls = []
for i in xrange(10):
for i in range(10):
freq_range = "%d-%d" % (i * 1000 + 1, (i+1) * 1000)
urls.append(URL_TMPL % freq_range)

for i in xrange(0,15):
for i in range(0,15):
freq_range = "%d-%d" % (10000 + 2 * i * 1000 + 1, 10000 + (2 * i + 2) * 1000)
urls.append(URL_TMPL % freq_range)

Expand All @@ -53,15 +58,15 @@ def wiki_download(url):

tmp_path = DOWNLOAD_TMPL % freq_range
if os.path.exists(tmp_path):
print 'cached.......', url
print('cached.......', url)
with codecs.open(tmp_path, 'r', 'utf8') as f:
return f.read(), True
with codecs.open(tmp_path, 'w', 'utf8') as f:
print 'downloading...', url
req = urllib2.Request(url, headers={
print('downloading...', url)
req = urllib_request.Request(url, headers={
'User-Agent': 'zxcvbn'
})
response = urllib2.urlopen(req)
response = urllib_request.urlopen(req)
result = response.read().decode('utf8')
f.write(result)
return result, False
Expand Down Expand Up @@ -173,16 +178,16 @@ def main():
out[lst_name] = lst
json.dump(out, f)

print '\nall done! totals:\n'
print 'passwords....', len(passwords)
print 'male.........', len(male_names)
print 'female.......', len(female_names)
print 'surnames.....', len(surnames)
print 'english......', len(english)
print
print('\nall done! totals:\n')
print('passwords....', len(passwords))
print('male.........', len(male_names))
print('female.......', len(female_names))
print('surnames.....', len(surnames))
print('english......', len(english))
print()

if __name__ == '__main__':
if os.path.basename(os.getcwd()) != 'scripts':
print 'run this from the scripts directory'
print('run this from the scripts directory')
exit(1)
main()
6 changes: 4 additions & 2 deletions zxcvbn/scripts/build_keyboard_adjacency_graph.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from __future__ import with_statement
from __future__ import (absolute_import, division,
print_function, unicode_literals)

try:
import simplejson as json
json # silence pyflakes
Expand Down Expand Up @@ -71,7 +73,7 @@ def build_graph(layout_str, slanted):
position_table[(x,y)] = token

adjacency_graph = {}
for (x,y), chars in position_table.iteritems():
for (x,y), chars in position_table.items():
for char in chars:
adjacency_graph[char] = []
for coord in adjacency_func(x, y):
Expand Down