passrock 0.0.5 → 0.0.8
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.
- checksums.yaml +4 -4
- data/.env.example +2 -1
- data/README.md +29 -23
- data/benchmark/binary_search_test.rb +1 -1
- data/lib/passrock.rb +1 -0
- data/lib/passrock/exceptions.rb +1 -1
- data/lib/passrock/password_db.rb +3 -61
- data/lib/passrock/password_db_finder.rb +87 -0
- data/lib/passrock/version.rb +1 -1
- data/spec/passrock/password_db_finder_spec.rb +119 -0
- data/spec/passrock/password_db_spec.rb +8 -45
- data/spec/spec_helper.rb +4 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed1e2aff3b96416160e88dde57f911e727895521
|
4
|
+
data.tar.gz: dc7046386fc7a537d6a1b73594079cae40e735f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1a113b26a7a606f42058d644ff1a5d005838dd3729d8ecd2fb759d779da24d8477218d5f5b75f2c9def4800b2c0d00a1d780b337858090d2587ab88e6f69efe
|
7
|
+
data.tar.gz: cda555c54b742e2cb80d4b22db94549b2b044c0c2fb5173da3eac6de7bee395105f7ef1e4516c6dfa5a32df755d7118f707adf689e1c061d43cb78e5385ca505
|
data/.env.example
CHANGED
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Ruby client library for programmatic access to the [Passrock Binary Database](https://www.passrock.com/demo.php).
|
4
4
|
|
5
|
+
This library adheres to [SemVer](http://semver.org). Pre v1.0.0 is considered alpha level software.
|
6
|
+
|
5
7
|
|
6
8
|
## Installation
|
7
9
|
|
@@ -18,34 +20,38 @@ And then execute:
|
|
18
20
|
|
19
21
|
### Plain Ol' Ruby (PORO)
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
passrock_db = Passrock::PasswordDb.new('/path/to/passrock/binary.dat', 'your private key')
|
24
|
-
passrock_db.secure?('password') # => false
|
25
|
-
passrock_db.insecure?('av3r^securePass') # => false
|
23
|
+
```ruby
|
24
|
+
require 'passrock'
|
26
25
|
|
26
|
+
passrock_db = Passrock::PasswordDb.new(:password_db => '/path/to/passrock_db_dir', :private_key => 'your private key')
|
27
|
+
passrock_db.secure?('password') # => false
|
28
|
+
passrock_db.insecure?('PASSWORD') # => true
|
29
|
+
```
|
27
30
|
|
28
31
|
### Ruby on Rails
|
29
32
|
|
30
33
|
This library provides a custom ActiveModel validation:
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
35
|
+
```ruby
|
36
|
+
# Configure: config/initializers/passrock.rb
|
37
|
+
Passrock.configure do |config|
|
38
|
+
config.password_db = '/path/to/passrock_db_dir'
|
39
|
+
config.private_key = 'your private key'
|
40
|
+
end
|
41
|
+
|
42
|
+
# Model
|
43
|
+
# e.g. app/models/user.rb
|
44
|
+
validates :password, :passrock_secure => true
|
45
|
+
```
|
46
|
+
|
47
|
+
```yaml
|
48
|
+
# Customize the error message (see: http://guides.rubyonrails.org/i18n.html#error-message-scopes)
|
49
|
+
# e.g. config/locales/en.yml
|
50
|
+
activerecord:
|
51
|
+
errors:
|
52
|
+
messages:
|
53
|
+
passrock_secure: "appears to be a commonly used password"
|
54
|
+
```
|
49
55
|
|
50
56
|
## Contributing
|
51
57
|
|
@@ -56,7 +62,7 @@ This library provides a custom ActiveModel validation:
|
|
56
62
|
5. Create new Pull Request
|
57
63
|
|
58
64
|
|
59
|
-
|
65
|
+
## Specs
|
60
66
|
|
61
67
|
To run the spec suite:
|
62
68
|
|
@@ -6,7 +6,7 @@ Dotenv.load
|
|
6
6
|
|
7
7
|
PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '../'))
|
8
8
|
|
9
|
-
passrock_db = Passrock::PasswordDb.new(ENV['PASSROCK_PASSWORD_DB'], ENV['PASSROCK_PRIVATE_KEY'])
|
9
|
+
passrock_db = Passrock::PasswordDb.new(:password_db => ENV['PASSROCK_PASSWORD_DB'], :private_key => ENV['PASSROCK_PRIVATE_KEY'])
|
10
10
|
|
11
11
|
Benchmark.bm do |x|
|
12
12
|
x.report("#find_by_binary_search\n") { puts "Password secure? #{passrock_db.secure?('password')}" }
|
data/lib/passrock.rb
CHANGED
data/lib/passrock/exceptions.rb
CHANGED
data/lib/passrock/password_db.rb
CHANGED
@@ -1,77 +1,19 @@
|
|
1
|
-
require 'base64'
|
2
|
-
require 'bcrypt'
|
3
|
-
|
4
1
|
module Passrock
|
5
2
|
class PasswordDb
|
6
3
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def self.bcrypt_hash(secret, salt)
|
11
|
-
BCrypt::Engine.hash_secret(secret, "$2a$07$#{salt}")
|
12
|
-
end
|
13
|
-
|
14
|
-
|
15
|
-
attr_reader :password_db, :private_key
|
4
|
+
attr_reader :password_db_finder
|
16
5
|
|
17
6
|
def initialize(opts = {})
|
18
|
-
@
|
19
|
-
@private_key = opts[:private_key]
|
20
|
-
|
21
|
-
raise PasswordDbNotFoundError, "Passrock Password DB not found at: #{@password_db}" unless File.file?(@password_db)
|
22
|
-
end
|
23
|
-
|
24
|
-
def password_in_searchable_form(password)
|
25
|
-
hashed_password = self.class.bcrypt_hash(password, private_key)
|
26
|
-
|
27
|
-
searchable = hashed_password[29..-1]
|
28
|
-
searchable.tr!('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/')
|
29
|
-
searchable += '=' * (3 - (searchable.size + 3) % 4)
|
7
|
+
@password_db_finder = PasswordDbFinder.new(opts)
|
30
8
|
end
|
31
9
|
|
32
10
|
def secure?(password)
|
33
|
-
!
|
11
|
+
!password_db_finder.find(password)
|
34
12
|
end
|
35
13
|
|
36
14
|
def insecure?(password)
|
37
15
|
!secure?(password)
|
38
16
|
end
|
39
17
|
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def total_records
|
44
|
-
# Minus 1 for length in file and 1 for 0-up counting
|
45
|
-
@total_records ||= (File.size(password_db) / RECORD_LENGTH) - 2
|
46
|
-
end
|
47
|
-
|
48
|
-
def find_by_binary_search(password)
|
49
|
-
file = File.new(password_db, 'rb')
|
50
|
-
target = password_in_searchable_form(password)
|
51
|
-
|
52
|
-
lo = 1 # start at 1 because the testKey is at 0
|
53
|
-
hi = total_records
|
54
|
-
while lo <= hi
|
55
|
-
mid = (lo + (hi - lo) / 2)
|
56
|
-
file.seek(RECORD_LENGTH * mid, IO::SEEK_SET)
|
57
|
-
midtest = file.read(RECORD_LENGTH)
|
58
|
-
raise 'Error reading binary file' if midtest.nil?
|
59
|
-
|
60
|
-
midtest = Base64.strict_encode64(midtest)
|
61
|
-
|
62
|
-
if ( (midtest <=> target) == 0 )
|
63
|
-
file.close
|
64
|
-
return true
|
65
|
-
elsif ( (midtest <=> target) < 0 )
|
66
|
-
lo = mid + 1
|
67
|
-
else
|
68
|
-
hi = mid - 1
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
file.close
|
73
|
-
return false
|
74
|
-
end
|
75
|
-
|
76
18
|
end
|
77
19
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'bcrypt'
|
3
|
+
|
4
|
+
module Passrock
|
5
|
+
class PasswordDbFinder
|
6
|
+
|
7
|
+
RECORD_LENGTH = 12
|
8
|
+
|
9
|
+
|
10
|
+
def self.bcrypt_hash(secret, salt)
|
11
|
+
BCrypt::Engine.hash_secret(secret, "$2a$07$#{salt}")
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
attr_reader :password_db, :private_key
|
16
|
+
|
17
|
+
def initialize(opts = {})
|
18
|
+
@password_db = opts[:password_db]
|
19
|
+
@private_key = opts[:private_key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid?
|
23
|
+
password_db_is_a_file? || password_db_is_a_directory?
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(password)
|
27
|
+
raise PasswordDbNotFoundError, "Passrock Password DB not found at: #{password_db}" unless valid?
|
28
|
+
find_by_binary_search(password)
|
29
|
+
end
|
30
|
+
|
31
|
+
def filename(hashed_password = nil)
|
32
|
+
return password_db if password_db_is_a_file?
|
33
|
+
File.join(password_db, "PRbinary#{hashed_password.ord}.dat")
|
34
|
+
end
|
35
|
+
|
36
|
+
def password_db_is_a_file?
|
37
|
+
File.file?(password_db)
|
38
|
+
end
|
39
|
+
|
40
|
+
def password_db_is_a_directory?
|
41
|
+
File.directory?(password_db)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def password_in_searchable_form(password)
|
48
|
+
password = password.to_s.downcase
|
49
|
+
hashed_password = self.class.bcrypt_hash(password, private_key)
|
50
|
+
|
51
|
+
searchable = hashed_password[29..-1]
|
52
|
+
searchable.tr!('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/')
|
53
|
+
searchable += '=' * (3 - (searchable.size + 3) % 4)
|
54
|
+
searchable[0, 16]
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_by_binary_search(password)
|
58
|
+
target = password_in_searchable_form(password)
|
59
|
+
file = File.new(filename(target), 'rb')
|
60
|
+
total_records = (File.size(file) / RECORD_LENGTH) - 1
|
61
|
+
|
62
|
+
lo = 0
|
63
|
+
hi = total_records
|
64
|
+
while lo <= hi
|
65
|
+
mid = (lo + (hi - lo) / 2)
|
66
|
+
file.seek(RECORD_LENGTH * mid, IO::SEEK_SET)
|
67
|
+
midtest = file.read(RECORD_LENGTH)
|
68
|
+
raise BinaryFileReadError if midtest.nil?
|
69
|
+
|
70
|
+
midtest = Base64.strict_encode64(midtest)
|
71
|
+
|
72
|
+
if ( (midtest <=> target) == 0 )
|
73
|
+
file.close
|
74
|
+
return target
|
75
|
+
elsif ( (midtest <=> target) < 0 )
|
76
|
+
lo = mid + 1
|
77
|
+
else
|
78
|
+
hi = mid - 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
file.close
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
data/lib/passrock/version.rb
CHANGED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Passrock::PasswordDbFinder do
|
4
|
+
|
5
|
+
let(:password_db) { passrock_password_db }
|
6
|
+
let(:private_key) { passrock_private_key }
|
7
|
+
let(:init_opts) { {:password_db => password_db, :private_key => private_key} }
|
8
|
+
|
9
|
+
|
10
|
+
describe '.bcrypt_hash' do
|
11
|
+
|
12
|
+
it 'calculates and returns the bcrypt password hash given a secret and salt' do
|
13
|
+
secret = 'password'
|
14
|
+
salt = private_key
|
15
|
+
expect(described_class.bcrypt_hash(secret, salt)).to eq('$2a$07$c16iYVArVz3hYEvtakjiXO8jPyn2MxhVHlrY92EErobY/OCDNObhG')
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
describe '#valid?' do
|
22
|
+
|
23
|
+
let(:subject) { described_class.new(init_opts) }
|
24
|
+
|
25
|
+
context 'password_db is a directory' do
|
26
|
+
let(:password_db) { passrock_password_db }
|
27
|
+
|
28
|
+
it 'returns true' do
|
29
|
+
expect(subject).to be_valid
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'password_db is a file' do
|
34
|
+
let(:password_db) { passrock_password_db_file }
|
35
|
+
|
36
|
+
it 'returns true' do
|
37
|
+
expect(subject).to be_valid
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'password_db is does not exist as a file or directory' do
|
42
|
+
let(:password_db) { '/invalid/path/to/password_db' }
|
43
|
+
|
44
|
+
it 'returns false' do
|
45
|
+
expect(subject).to_not be_valid
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#filename' do
|
52
|
+
|
53
|
+
let(:subject) { described_class.new(init_opts) }
|
54
|
+
|
55
|
+
context 'password_db is a directory' do
|
56
|
+
let(:password_db) { passrock_password_db }
|
57
|
+
|
58
|
+
it 'returns the filename based on the integer ordinal of the first character of the given hashed password' do
|
59
|
+
hashed_password = 'g+vOBRu/5hi40RA5'
|
60
|
+
expect(subject.filename(hashed_password)).to eq("#{password_db}/PRbinary#{hashed_password[0].ord}.dat")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'password_db is a file' do
|
65
|
+
let(:password_db) { passrock_password_db_file }
|
66
|
+
|
67
|
+
it 'returns the password_db as the filename verbatim' do
|
68
|
+
expect(subject.filename).to eq(passrock_password_db_file)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#find' do
|
75
|
+
|
76
|
+
let(:subject) { described_class.new(init_opts) }
|
77
|
+
let(:known_password) { 'password' }
|
78
|
+
let(:unknown_password) { 'BoatActKnowsDog' }
|
79
|
+
|
80
|
+
context 'when given password is present in the password database' do
|
81
|
+
it 'returns the Base64 representation of the hashed password' do
|
82
|
+
expect(subject.find(known_password)).to eq('+lR0p4OzjXJnta/4')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when given password is mixed cased' do
|
87
|
+
it 'returns the Base64 representation of the hashed password ignoring case' do
|
88
|
+
expect(subject.find(known_password.upcase)).to eq('+lR0p4OzjXJnta/4')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when given password does not appear in the password database' do
|
93
|
+
it 'returns true' do
|
94
|
+
expect(subject.find(unknown_password)).to be_nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when password_db does not exist' do
|
99
|
+
let(:password_db) { '/invalid/path/to/password_db' }
|
100
|
+
|
101
|
+
it 'raises PasswordDbNotFoundError' do
|
102
|
+
expect {
|
103
|
+
subject.find(known_password)
|
104
|
+
}.to raise_error(Passrock::PasswordDbNotFoundError)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'multiple sequential calls' do
|
109
|
+
it 'does not error out' do
|
110
|
+
expect {
|
111
|
+
subject.find(unknown_password)
|
112
|
+
subject.find(known_password)
|
113
|
+
}.to_not raise_error
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -4,50 +4,22 @@ describe Passrock::PasswordDb do
|
|
4
4
|
|
5
5
|
let(:password_db) { passrock_password_db }
|
6
6
|
let(:private_key) { passrock_private_key }
|
7
|
-
let(:
|
7
|
+
let(:init_opts) { {:password_db => password_db, :private_key => private_key} }
|
8
8
|
let(:insecure_password) { 'password' }
|
9
9
|
let(:secure_password) { 'BoatActKnowsDog' }
|
10
10
|
|
11
|
-
|
12
|
-
describe '.bcrypt_hash' do
|
13
|
-
|
14
|
-
it 'calculates and returns the bcrypt password hash given a secret and salt' do
|
15
|
-
secret = 'password'
|
16
|
-
salt = private_key
|
17
|
-
expect(described_class.bcrypt_hash(secret, salt)).to eq('$2a$07$c16iYVArVz3hYEvtakjiXO8jPyn2MxhVHlrY92EErobY/OCDNObhG')
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
|
23
|
-
describe '#initialize' do
|
24
|
-
|
25
|
-
context 'when password_db file does not exist' do
|
26
|
-
it 'raises PasswordDbNotFoundError' do
|
27
|
-
expect {
|
28
|
-
described_class.new(:password_db => '/invalid/path/to/password_db', :private_key => private_key)
|
29
|
-
}.to raise_error(Passrock::PasswordDbNotFoundError)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
describe '#password_in_searchable_form' do
|
36
|
-
|
37
|
-
it 'returns the given password in a searchable format' do
|
38
|
-
subject = described_class.new(valid_init_opts)
|
39
|
-
expect(subject.password_in_searchable_form(insecure_password)).to eq('+lR0p4OzjXJnta/4GGtqdaBQEFPQdjI=')
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
11
|
describe '#secure?' do
|
45
12
|
|
46
|
-
let(:subject) { described_class.new(
|
13
|
+
let(:subject) { described_class.new(init_opts) }
|
47
14
|
|
48
15
|
context 'when given password is present in the password database' do
|
49
16
|
it 'returns false' do
|
50
17
|
expect(subject.secure?(insecure_password)).to be_false
|
18
|
+
|
19
|
+
# sanity check other known insecure passwords
|
20
|
+
[ 'inIUfiWO13', 'PVGWpkf81', 'cSAuOcUW58', 'XxPRBGF11', 'WjNYUmGj72', 'P0RQU33SM3N3ST3r' ].each do |password|
|
21
|
+
expect(subject.secure?(password)).to be_false
|
22
|
+
end
|
51
23
|
end
|
52
24
|
end
|
53
25
|
|
@@ -57,20 +29,11 @@ describe Passrock::PasswordDb do
|
|
57
29
|
end
|
58
30
|
end
|
59
31
|
|
60
|
-
context 'multiple sequential calls' do
|
61
|
-
it 'does not error out' do
|
62
|
-
expect {
|
63
|
-
subject.secure?(secure_password)
|
64
|
-
subject.secure?(insecure_password)
|
65
|
-
}.to_not raise_error
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
32
|
end
|
70
33
|
|
71
34
|
describe '#insecure?' do
|
72
35
|
|
73
|
-
let(:subject) { described_class.new(
|
36
|
+
let(:subject) { described_class.new(init_opts) }
|
74
37
|
|
75
38
|
context 'when given password is present in the password database' do
|
76
39
|
it 'returns true' do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: passrock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bitium, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bcrypt-ruby
|
@@ -100,11 +100,13 @@ files:
|
|
100
100
|
- lib/passrock/configuration.rb
|
101
101
|
- lib/passrock/exceptions.rb
|
102
102
|
- lib/passrock/password_db.rb
|
103
|
+
- lib/passrock/password_db_finder.rb
|
103
104
|
- lib/passrock/railtie.rb
|
104
105
|
- lib/passrock/version.rb
|
105
106
|
- locales/en.yml
|
106
107
|
- passrock.gemspec
|
107
108
|
- spec/active_model/validations/passrock_secure_validator_spec.rb
|
109
|
+
- spec/passrock/password_db_finder_spec.rb
|
108
110
|
- spec/passrock/password_db_spec.rb
|
109
111
|
- spec/passrock_spec.rb
|
110
112
|
- spec/spec_helper.rb
|
@@ -134,6 +136,7 @@ specification_version: 4
|
|
134
136
|
summary: Ruby client library to access a Passrock (Passrock.com) binary database
|
135
137
|
test_files:
|
136
138
|
- spec/active_model/validations/passrock_secure_validator_spec.rb
|
139
|
+
- spec/passrock/password_db_finder_spec.rb
|
137
140
|
- spec/passrock/password_db_spec.rb
|
138
141
|
- spec/passrock_spec.rb
|
139
142
|
- spec/spec_helper.rb
|