zxcvbn-ruby 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -7,7 +7,7 @@ Ruby port of [zxcvbn.js](https://github.com/dropbox/zxcvbn)
7
7
  Gemfile:
8
8
 
9
9
  ```ruby
10
- gem "zxcvbn-ruby", :require => 'zxcvbn'
10
+ gem 'zxcvbn-ruby', require: 'zxcvbn'
11
11
  ```
12
12
 
13
13
  Example usage:
@@ -20,4 +20,20 @@ $ irb
20
20
  => #<Zxcvbn::Score:0x007fd467803098 @entropy=7.895, @crack_time=0.012, @crack_time_display="instant", @score=0, @match_sequence=[#<Zxcvbn::Match matched_word="alfred", token="@lfred", i=0, j=5, rank=1, pattern="dictionary", dictionary_name="user_inputs", l33t=true, sub={"@"=>"a"}, sub_display"@ -> a", base_entropy0.0, uppercase_entropy0.0, l33t_entropy1, entropy1.0, #<Zxcvbn::Match i=6, j=9, token="2004", pattern="year", entropy=6.894817763307944], @password="@lfred2004", @calc_time=0.003436>
21
21
  >> Zxcvbn.test('asdfghju7654rewq', ['alfred'])
22
22
  => #<Zxcvbn::Score:0x007fd4689c1168 @entropy=29.782, @crack_time=46159.451, @crack_time_display="14 hours", @score=2, @match_sequence=[#<Zxcvbn::Match pattern="spatial", i=0, j=15, token="asdfghju7654rewq", graph="qwerty", turns=5, shifted_count=0, entropy=29.7820508329166>], password"asdfghju7654rewq", calc_time0.00526
23
+ ```
24
+
25
+ ## Testing Multiple Passwords
26
+
27
+ The dictionaries used for password strength testing are loaded each request to `Zxcvbn.test`. If you you'd prefer to persist the dictionaries in memory (approx 20MB RSS) to perform lots of password tests in succession then you can use the `Zxcvbn::Tester` API:
28
+
29
+ ```ruby
30
+ $ irb
31
+ >> require 'zxcvbn'
32
+ => true
33
+ >> tester = Zxcvbn::Tester.new
34
+ => #<Zxcvbn::Tester:0x3fe99d869aa4>
35
+ >> tester.test('@lfred2004', ['alfred'])
36
+ => #<Zxcvbn::Score:0x007fd4689c1168 @entropy=29.782, @crack_time=46159.451, @crack_time_display="14 hours", @score=2, @match_sequence=[#<Zxcvbn::Match pattern="spatial", i=0, j=15, token="asdfghju7654rewq", graph="qwerty", turns=5, shifted_count=0, entropy=29.7820508329166>], password"asdfghju7654rewq", calc_time0.00526
37
+ >> tester.test('@lfred2004', ['alfred'])
38
+ => #<Zxcvbn::Score:0x007fd4689c1168 @entropy=29.782, @crack_time=46159.451, @crack_time_display="14 hours", @score=2, @match_sequence=[#<Zxcvbn::Match pattern="spatial", i=0, j=15, token="asdfghju7654rewq", graph="qwerty", turns=5, shifted_count=0, entropy=29.7820508329166>], password"asdfghju7654rewq", calc_time0.00526
23
39
  ```
data/lib/zxcvbn.rb CHANGED
@@ -1,46 +1,20 @@
1
- require 'json'
2
1
  require 'pathname'
3
-
4
2
  require 'zxcvbn/version'
5
- require 'zxcvbn/match'
6
- require 'zxcvbn/matchers/regex_helpers'
7
- require 'zxcvbn/matchers/dictionary'
8
- require 'zxcvbn/matchers/l33t'
9
- require 'zxcvbn/matchers/spatial'
10
- require 'zxcvbn/matchers/sequences'
11
- require 'zxcvbn/matchers/repeat'
12
- require 'zxcvbn/matchers/digits'
13
- require 'zxcvbn/matchers/year'
14
- require 'zxcvbn/matchers/date'
15
- require 'zxcvbn/dictionary_ranker'
16
- require 'zxcvbn/omnimatch'
17
- require 'zxcvbn/math'
18
- require 'zxcvbn/entropy'
19
- require 'zxcvbn/crack_time'
20
- require 'zxcvbn/score'
21
- require 'zxcvbn/scorer'
22
- require 'zxcvbn/password_strength'
3
+ require 'zxcvbn/tester'
23
4
 
24
5
  module Zxcvbn
25
6
  extend self
26
7
 
27
8
  DATA_PATH = Pathname(File.expand_path('../../data', __FILE__))
28
- ADJACENCY_GRAPHS = JSON.load(DATA_PATH.join('adjacency_graphs.json').read)
29
- FREQUENCY_LISTS_PATH = DATA_PATH.join("frequency_lists")
30
- RANKED_DICTIONARIES = DictionaryRanker.rank_dictionaries(
31
- "english" => FREQUENCY_LISTS_PATH.join("english.txt").read.split,
32
- "female_names" => FREQUENCY_LISTS_PATH.join("female_names.txt").read.split,
33
- "male_names" => FREQUENCY_LISTS_PATH.join("male_names.txt").read.split,
34
- "passwords" => FREQUENCY_LISTS_PATH.join("passwords.txt").read.split,
35
- "surnames" => FREQUENCY_LISTS_PATH.join("surnames.txt").read.split
36
- )
37
-
38
- def test(password, user_inputs = [])
39
- zxcvbn = PasswordStrength.new
40
- zxcvbn.test(password, user_inputs)
41
- end
42
9
 
43
- def add_word_list(name, list)
44
- RANKED_DICTIONARIES[name] = DictionaryRanker.rank_dictionary(list)
10
+ # Returns a Zxcvbn::Score for the given password
11
+ #
12
+ # Example:
13
+ #
14
+ # Zxcvbn.test("password").score #=> 0
15
+ def test(password, user_inputs = [], word_lists = {})
16
+ tester = Tester.new
17
+ tester.add_word_lists(word_lists)
18
+ tester.test(password, user_inputs)
45
19
  end
46
20
  end
@@ -1,51 +1,53 @@
1
- module Zxcvbn::CrackTime
2
- SINGLE_GUESS = 0.010
3
- NUM_ATTACKERS = 100
1
+ module Zxcvbn
2
+ module CrackTime
3
+ SINGLE_GUESS = 0.010
4
+ NUM_ATTACKERS = 100
4
5
 
5
- SECONDS_PER_GUESS = SINGLE_GUESS / NUM_ATTACKERS
6
+ SECONDS_PER_GUESS = SINGLE_GUESS / NUM_ATTACKERS
6
7
 
7
- def entropy_to_crack_time(entropy)
8
- 0.5 * (2 ** entropy) * SECONDS_PER_GUESS
9
- end
8
+ def entropy_to_crack_time(entropy)
9
+ 0.5 * (2 ** entropy) * SECONDS_PER_GUESS
10
+ end
10
11
 
11
- def crack_time_to_score(seconds)
12
- case
13
- when seconds < 10**2
14
- 0
15
- when seconds < 10**4
16
- 1
17
- when seconds < 10**6
18
- 2
19
- when seconds < 10**8
20
- 3
21
- else
22
- 4
12
+ def crack_time_to_score(seconds)
13
+ case
14
+ when seconds < 10**2
15
+ 0
16
+ when seconds < 10**4
17
+ 1
18
+ when seconds < 10**6
19
+ 2
20
+ when seconds < 10**8
21
+ 3
22
+ else
23
+ 4
24
+ end
23
25
  end
24
- end
25
26
 
26
- def display_time(seconds)
27
- minute = 60
28
- hour = minute * 60
29
- day = hour * 24
30
- month = day * 31
31
- year = month * 12
32
- century = year * 100
27
+ def display_time(seconds)
28
+ minute = 60
29
+ hour = minute * 60
30
+ day = hour * 24
31
+ month = day * 31
32
+ year = month * 12
33
+ century = year * 100
33
34
 
34
- case
35
- when seconds < minute
36
- 'instant'
37
- when seconds < hour
38
- "#{1 + (seconds / minute).ceil} minutes"
39
- when seconds < day
40
- "#{1 + (seconds / hour).ceil} hours"
41
- when seconds < month
42
- "#{1 + (seconds / day).ceil} days"
43
- when seconds < year
44
- "#{1 + (seconds / month).ceil} months"
45
- when seconds < century
46
- "#{1 + (seconds / year).ceil} years"
47
- else
48
- 'centuries'
35
+ case
36
+ when seconds < minute
37
+ 'instant'
38
+ when seconds < hour
39
+ "#{1 + (seconds / minute).ceil} minutes"
40
+ when seconds < day
41
+ "#{1 + (seconds / hour).ceil} hours"
42
+ when seconds < month
43
+ "#{1 + (seconds / day).ceil} days"
44
+ when seconds < year
45
+ "#{1 + (seconds / month).ceil} months"
46
+ when seconds < century
47
+ "#{1 + (seconds / year).ceil} years"
48
+ else
49
+ 'centuries'
50
+ end
49
51
  end
50
52
  end
51
53
  end
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+ require 'zxcvbn/dictionary_ranker'
3
+
4
+ module Zxcvbn
5
+ class Data
6
+ def initialize
7
+ @ranked_dictionaries = DictionaryRanker.rank_dictionaries(
8
+ "english" => read_word_list("english.txt"),
9
+ "female_names" => read_word_list("female_names.txt"),
10
+ "male_names" => read_word_list("male_names.txt"),
11
+ "passwords" => read_word_list("passwords.txt"),
12
+ "surnames" => read_word_list("surnames.txt")
13
+ )
14
+ @adjacency_graphs = JSON.load(DATA_PATH.join('adjacency_graphs.json').read)
15
+ end
16
+
17
+ attr_reader :ranked_dictionaries, :adjacency_graphs
18
+
19
+ def add_word_list(name, list)
20
+ @ranked_dictionaries[name] = DictionaryRanker.rank_dictionary(list)
21
+ end
22
+
23
+ private
24
+
25
+ def read_word_list(file)
26
+ DATA_PATH.join("frequency_lists", file).read.split
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Zxcvbn
4
2
  class DictionaryRanker
5
3
  def self.rank_dictionaries(lists)
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/math'
2
+
1
3
  module Zxcvbn::Entropy
2
4
  include Zxcvbn::Math
3
5
 
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/matchers/regex_helpers'
2
+
1
3
  module Zxcvbn
2
4
  module Matchers
3
5
  class Date
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/match'
2
+
1
3
  module Zxcvbn
2
4
  module Matchers
3
5
  # Given a password and a dictionary, match on any sequential segment of
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/matchers/regex_helpers'
2
+
1
3
  module Zxcvbn
2
4
  module Matchers
3
5
  class Digits
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/match'
2
+
1
3
  module Zxcvbn
2
4
  module Matchers
3
5
  module RegexHelpers
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/match'
2
+
1
3
  module Zxcvbn
2
4
  module Matchers
3
5
  class Repeat
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/match'
2
+
1
3
  module Zxcvbn
2
4
  module Matchers
3
5
  class Sequences
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/match'
2
+
1
3
  module Zxcvbn
2
4
  module Matchers
3
5
  class Spatial
@@ -1,3 +1,5 @@
1
+ require 'zxcvbn/matchers/regex_helpers'
2
+
1
3
  module Zxcvbn
2
4
  module Matchers
3
5
  class Year
data/lib/zxcvbn/math.rb CHANGED
@@ -41,14 +41,14 @@ module Zxcvbn
41
41
  end
42
42
 
43
43
  def average_degree_for_graph(graph_name)
44
- graph = Zxcvbn::ADJACENCY_GRAPHS[graph_name]
44
+ graph = data.adjacency_graphs[graph_name]
45
45
  degrees = graph.map { |_, neighbors| neighbors.compact.size }
46
46
  sum = degrees.inject(0, :+)
47
47
  sum.to_f / graph.size
48
48
  end
49
49
 
50
50
  def starting_positions_for_graph(graph_name)
51
- Zxcvbn::ADJACENCY_GRAPHS[graph_name].length
51
+ data.adjacency_graphs[graph_name].length
52
52
  end
53
53
  end
54
54
  end
@@ -1,6 +1,17 @@
1
+ require 'zxcvbn/dictionary_ranker'
2
+ require 'zxcvbn/matchers/dictionary'
3
+ require 'zxcvbn/matchers/l33t'
4
+ require 'zxcvbn/matchers/spatial'
5
+ require 'zxcvbn/matchers/digits'
6
+ require 'zxcvbn/matchers/repeat'
7
+ require 'zxcvbn/matchers/sequences'
8
+ require 'zxcvbn/matchers/year'
9
+ require 'zxcvbn/matchers/date'
10
+
1
11
  module Zxcvbn
2
12
  class Omnimatch
3
- def initialize
13
+ def initialize(data)
14
+ @data = data
4
15
  @matchers = build_matchers
5
16
  end
6
17
 
@@ -24,14 +35,14 @@ module Zxcvbn
24
35
 
25
36
  def build_matchers
26
37
  matchers = []
27
- dictionary_matchers = RANKED_DICTIONARIES.map do |name, dictionary|
38
+ dictionary_matchers = @data.ranked_dictionaries.map do |name, dictionary|
28
39
  Matchers::Dictionary.new(name, dictionary)
29
40
  end
30
41
  l33t_matcher = Matchers::L33t.new(dictionary_matchers)
31
42
  matchers += dictionary_matchers
32
43
  matchers += [
33
44
  l33t_matcher,
34
- Matchers::Spatial.new(ADJACENCY_GRAPHS),
45
+ Matchers::Spatial.new(@data.adjacency_graphs),
35
46
  Matchers::Digits.new,
36
47
  Matchers::Repeat.new,
37
48
  Matchers::Sequences.new,
@@ -1,10 +1,12 @@
1
1
  require 'benchmark'
2
+ require 'zxcvbn/omnimatch'
3
+ require 'zxcvbn/scorer'
2
4
 
3
5
  module Zxcvbn
4
6
  class PasswordStrength
5
- def initialize
6
- @omnimatch = Omnimatch.new
7
- @scorer = Scorer.new
7
+ def initialize(data)
8
+ @omnimatch = Omnimatch.new(data)
9
+ @scorer = Scorer.new(data)
8
10
  end
9
11
 
10
12
  def test(password, user_inputs = [])
data/lib/zxcvbn/scorer.rb CHANGED
@@ -1,5 +1,16 @@
1
+ require 'zxcvbn/entropy'
2
+ require 'zxcvbn/crack_time'
3
+ require 'zxcvbn/score'
4
+ require 'zxcvbn/match'
5
+
1
6
  module Zxcvbn
2
7
  class Scorer
8
+ def initialize(data)
9
+ @data = data
10
+ end
11
+
12
+ attr_reader :data
13
+
3
14
  include Entropy
4
15
  include CrackTime
5
16
 
@@ -0,0 +1,35 @@
1
+ require 'zxcvbn/data'
2
+ require 'zxcvbn/password_strength'
3
+
4
+ module Zxcvbn
5
+ # Allows you to test the strength of multiple passwords without reading and
6
+ # parsing the dictionary data from disk each test. Dictionary data is read
7
+ # once from disk and stored in memory for the life of the Tester object.
8
+ #
9
+ # Example:
10
+ #
11
+ # tester = Zxcvbn::Tester.new
12
+ #
13
+ # tester.add_word_lists("custom" => ["words"])
14
+ #
15
+ # tester.test("password 1")
16
+ # tester.test("password 2")
17
+ # tester.test("password 3")
18
+ class Tester
19
+ def initialize
20
+ @data = Data.new
21
+ end
22
+
23
+ def test(password, user_inputs = [])
24
+ PasswordStrength.new(@data).test(password, user_inputs)
25
+ end
26
+
27
+ def add_word_lists(lists)
28
+ lists.each_pair {|name, words| @data.add_word_list(name, words)}
29
+ end
30
+
31
+ def inspect
32
+ "#<#{self.class}:0x#{self.__id__.to_s(16)}>"
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  module Zxcvbn
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Zxcvbn::Matchers::Dictionary do
4
4
  let(:matcher) { described_class.new('english', dictionary) }
5
- let(:dictionary) { Zxcvbn::RANKED_DICTIONARIES['english'] }
5
+ let(:dictionary) { Zxcvbn::Data.new.ranked_dictionaries['english'] }
6
6
 
7
7
  it 'finds all the matches' do
8
8
  matches = matcher.matches('whatisinit')
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Zxcvbn::Matchers::L33t do
4
4
  let(:matcher) { described_class.new([dictionary_matcher]) }
5
- let(:dictionary) { Zxcvbn::RANKED_DICTIONARIES['english'] }
5
+ let(:dictionary) { Zxcvbn::Data.new.ranked_dictionaries['english'] }
6
6
  let(:dictionary_matcher) { Zxcvbn::Matchers::Dictionary.new('english', dictionary) }
7
7
 
8
8
  describe '#relevant_l33t_substitutions' do
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Zxcvbn::Matchers::Spatial do
4
4
  let(:matcher) { Zxcvbn::Matchers::Spatial.new(graphs) }
5
- let(:graphs) { Zxcvbn::ADJACENCY_GRAPHS }
5
+ let(:graphs) { Zxcvbn::Data.new.adjacency_graphs }
6
6
 
7
7
  describe '#matches' do
8
8
  let(:matches) { matcher.matches('rtyikm') }
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Zxcvbn::Omnimatch do
4
4
  before(:all) do
5
- @omnimatch = described_class.new
5
+ @omnimatch = described_class.new(Zxcvbn::Data.new)
6
6
  end
7
7
 
8
8
  def omnimatch(password)
@@ -6,6 +6,9 @@ describe Zxcvbn::Entropy do
6
6
  let(:entropy) {
7
7
  Class.new do
8
8
  include Zxcvbn::Entropy
9
+ def data
10
+ Zxcvbn::Data.new
11
+ end
9
12
  end.new
10
13
  }
11
14
 
@@ -163,7 +166,7 @@ describe Zxcvbn::Entropy do
163
166
  context 'when keyboard is qwerty' do
164
167
  it 'should return the correct entropy' do
165
168
  match.graph = 'qwerty'
166
-
169
+
167
170
  entropy.spatial_entropy(match).should eql 11.562242424221074
168
171
  end
169
172
  end
@@ -3,6 +3,10 @@ require 'spec_helper'
3
3
  describe Zxcvbn::Math do
4
4
  include Zxcvbn::Math
5
5
 
6
+ def data
7
+ Zxcvbn::Data.new
8
+ end
9
+
6
10
  describe '#bruteforce_cardinality' do
7
11
  context 'when empty password' do
8
12
  it 'should return 0 if empty password' do
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zxcvbn::Tester do
4
+ let(:tester) { Zxcvbn::Tester.new }
5
+
6
+ TEST_PASSWORDS.each do |password|
7
+ it "gives back the same score for #{password}" do
8
+ ruby_result = tester.test(password)
9
+ js_result = js_zxcvbn(password)
10
+
11
+ ruby_result.calc_time.should_not be_nil
12
+ ruby_result.password.should eq js_result['password']
13
+ ruby_result.entropy.should eq js_result['entropy']
14
+ ruby_result.crack_time.should eq js_result['crack_time']
15
+ ruby_result.crack_time_display.should eq js_result['crack_time_display']
16
+ ruby_result.score.should eq js_result['score']
17
+ ruby_result.pattern.should eq js_result['pattern']
18
+ ruby_result.match_sequence.count.should eq js_result['match_sequence'].count
19
+ end
20
+ end
21
+
22
+ context 'with a custom user dictionary' do
23
+ it 'scores them against the user dictionary' do
24
+ result = tester.test('themeforest', ['themeforest'])
25
+ result.entropy.should eq 0
26
+ result.score.should eq 0
27
+ end
28
+
29
+ it 'matches l33t substitutions on this dictionary' do
30
+ result = tester.test('th3m3for3st', ['themeforest'])
31
+ result.entropy.should eq 1
32
+ result.score.should eq 0
33
+ end
34
+ end
35
+
36
+ context 'with a custom global dictionary' do
37
+ before { tester.add_word_lists('envato' => ['envato']) }
38
+
39
+ it 'scores them against the dictionary' do
40
+ result = tester.test('envato')
41
+ result.entropy.should eq 0
42
+ result.score.should eq 0
43
+ end
44
+ end
45
+
46
+ context 'nil password' do
47
+ specify do
48
+ expect { tester.test(nil) }.to_not raise_error
49
+ end
50
+ end
51
+ end
data/spec/zxcvbn_spec.rb CHANGED
@@ -1,49 +1,24 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'zxcvbn over a set of example passwords' do
4
- TEST_PASSWORDS.each do |password|
5
- it "gives back the same score for #{password}" do
6
- ruby_result = Zxcvbn.test(password)
7
- js_result = js_zxcvbn(password)
8
-
9
- ruby_result.calc_time.should_not be_nil
10
- ruby_result.password.should eq js_result['password']
11
- ruby_result.entropy.should eq js_result['entropy']
12
- ruby_result.crack_time.should eq js_result['crack_time']
13
- ruby_result.crack_time_display.should eq js_result['crack_time_display']
14
- ruby_result.score.should eq js_result['score']
15
- ruby_result.pattern.should eq js_result['pattern']
16
- ruby_result.match_sequence.count.should eq js_result['match_sequence'].count
3
+ describe 'Zxcvbn.test' do
4
+ context 'with a password' do
5
+ it 'returns a result' do
6
+ result = Zxcvbn.test('password')
7
+ expect(result.entropy).to_not be_nil
17
8
  end
18
9
  end
19
10
 
20
- context 'with a custom user dictionary' do
21
- it 'scores them against the user dictionary' do
22
- result = Zxcvbn.test('themeforest', ['themeforest'])
23
- result.entropy.should eq 0
24
- result.score.should eq 0
25
- end
26
-
27
- it 'matches l33t substitutions on this dictionary' do
28
- result = Zxcvbn.test('th3m3for3st', ['themeforest'])
29
- result.entropy.should eq 1
30
- result.score.should eq 0
31
- end
32
- end
33
-
34
- context 'with a custom global dictionary' do
35
- before { Zxcvbn.add_word_list('envato', ['envato']) }
36
-
37
- it 'scores them against the dictionary' do
38
- result = Zxcvbn.test('envato')
39
- result.entropy.should eq 0
40
- result.score.should eq 0
11
+ context 'with a password and user input' do
12
+ it 'returns a result' do
13
+ result = Zxcvbn.test('password', ['inputs'])
14
+ expect(result.entropy).to_not be_nil
41
15
  end
42
16
  end
43
17
 
44
- context 'nil password' do
45
- specify do
46
- expect { Zxcvbn.test(nil) }.to_not raise_error
18
+ context 'with a password, user input and custom word lists' do
19
+ it 'returns a result' do
20
+ result = Zxcvbn.test('password', ['inputs'], {'list' => ['words']})
21
+ expect(result.entropy).to_not be_nil
47
22
  end
48
23
  end
49
- end
24
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zxcvbn-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-02-19 00:00:00.000000000 Z
13
+ date: 2015-04-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: therubyracer
@@ -66,6 +66,7 @@ files:
66
66
  - data/frequency_lists/surnames.txt
67
67
  - lib/zxcvbn.rb
68
68
  - lib/zxcvbn/crack_time.rb
69
+ - lib/zxcvbn/data.rb
69
70
  - lib/zxcvbn/dictionary_ranker.rb
70
71
  - lib/zxcvbn/entropy.rb
71
72
  - lib/zxcvbn/match.rb
@@ -84,6 +85,7 @@ files:
84
85
  - lib/zxcvbn/password_strength.rb
85
86
  - lib/zxcvbn/score.rb
86
87
  - lib/zxcvbn/scorer.rb
88
+ - lib/zxcvbn/tester.rb
87
89
  - lib/zxcvbn/version.rb
88
90
  - spec/dictionary_ranker_spec.rb
89
91
  - spec/matchers/date_spec.rb
@@ -111,6 +113,7 @@ files:
111
113
  - spec/support/js_source/scoring.coffee
112
114
  - spec/support/js_source/scoring.js
113
115
  - spec/support/matcher.rb
116
+ - spec/tester_spec.rb
114
117
  - spec/zxcvbn_spec.rb
115
118
  - zxcvbn-ruby.gemspec
116
119
  homepage: http://github.com/envato/zxcvbn-ruby
@@ -127,7 +130,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
130
  version: '0'
128
131
  segments:
129
132
  - 0
130
- hash: -1798543645320205772
133
+ hash: 3120558835973257605
131
134
  required_rubygems_version: !ruby/object:Gem::Requirement
132
135
  none: false
133
136
  requirements:
@@ -136,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
139
  version: '0'
137
140
  segments:
138
141
  - 0
139
- hash: -1798543645320205772
142
+ hash: 3120558835973257605
140
143
  requirements: []
141
144
  rubyforge_project:
142
145
  rubygems_version: 1.8.23.2
@@ -170,4 +173,5 @@ test_files:
170
173
  - spec/support/js_source/scoring.coffee
171
174
  - spec/support/js_source/scoring.js
172
175
  - spec/support/matcher.rb
176
+ - spec/tester_spec.rb
173
177
  - spec/zxcvbn_spec.rb