scrypt 3.0.6 → 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Rakefile +5 -4
- data/lib/scrypt.rb +7 -266
- data/lib/scrypt/engine.rb +175 -0
- data/lib/scrypt/errors.rb +14 -0
- data/lib/scrypt/password.rb +97 -0
- data/lib/scrypt/scrypt_ext.rb +3 -0
- data/lib/scrypt/security_utils.rb +4 -3
- data/lib/scrypt/version.rb +3 -1
- data/scrypt.gemspec +38 -20
- data/spec/scrypt/engine_spec.rb +34 -37
- data/spec/scrypt/password_spec.rb +56 -58
- data/spec/scrypt/utils_spec.rb +5 -3
- data/spec/spec_helper.rb +6 -3
- metadata +101 -36
- metadata.gz.sig +0 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SCrypt
|
4
|
+
module Errors
|
5
|
+
# The salt parameter provided is invalid.
|
6
|
+
class InvalidSalt < StandardError; end
|
7
|
+
|
8
|
+
# The hash parameter provided is invalid.
|
9
|
+
class InvalidHash < StandardError; end
|
10
|
+
|
11
|
+
# The secret parameter provided is invalid.
|
12
|
+
class InvalidSecret < StandardError; end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SCrypt
|
4
|
+
# A password management class which allows you to safely store users' passwords and compare them.
|
5
|
+
#
|
6
|
+
# Example usage:
|
7
|
+
#
|
8
|
+
# include "scrypt"
|
9
|
+
#
|
10
|
+
# # hash a user's password
|
11
|
+
# @password = Password.create("my grand secret")
|
12
|
+
# @password #=> "2000$8$1$f5f2fa5fe5484a7091f1299768fbe92b5a7fbc77$6a385f22c54d92c314b71a4fd5ef33967c93d679"
|
13
|
+
#
|
14
|
+
# # store it safely
|
15
|
+
# @user.update_attribute(:password, @password)
|
16
|
+
#
|
17
|
+
# # read it back
|
18
|
+
# @user.reload!
|
19
|
+
# @db_password = Password.new(@user.password)
|
20
|
+
#
|
21
|
+
# # compare it after retrieval
|
22
|
+
# @db_password == "my grand secret" #=> true
|
23
|
+
# @db_password == "a paltry guess" #=> false
|
24
|
+
#
|
25
|
+
class Password < String
|
26
|
+
# The hash portion of the stored password hash.
|
27
|
+
attr_reader :digest
|
28
|
+
# The salt of the store password hash
|
29
|
+
attr_reader :salt
|
30
|
+
# The cost factor used to create the hash.
|
31
|
+
attr_reader :cost
|
32
|
+
|
33
|
+
class << self
|
34
|
+
# Hashes a secret, returning a SCrypt::Password instance.
|
35
|
+
# Takes five options (optional), which will determine the salt/key's length and the cost limits of the computation.
|
36
|
+
# <tt>:key_len</tt> specifies the length in bytes of the key you want to generate. The default is 32 bytes (256 bits). Minimum is 16 bytes (128 bits). Maximum is 512 bytes (4096 bits).
|
37
|
+
# <tt>:salt_size</tt> specifies the size in bytes of the random salt you want to generate. The default and minimum is 8 bytes (64 bits). Maximum is 32 bytes (256 bits).
|
38
|
+
# <tt>:max_time</tt> specifies the maximum number of seconds the computation should take.
|
39
|
+
# <tt>:max_mem</tt> specifies the maximum number of bytes the computation should take. A value of 0 specifies no upper limit. The minimum is always 1 MB.
|
40
|
+
# <tt>:max_memfrac</tt> specifies the maximum memory in a fraction of available resources to use. Any value equal to 0 or greater than 0.5 will result in 0.5 being used.
|
41
|
+
# The scrypt key derivation function is designed to be far more secure against hardware brute-force attacks than alternative functions such as PBKDF2 or bcrypt.
|
42
|
+
# The designers of scrypt estimate that on modern (2009) hardware, if 5 seconds are spent computing a derived key, the cost of a hardware brute-force attack against scrypt is roughly 4000 times greater than the cost of a similar attack against bcrypt (to find the same password), and 20000 times greater than a similar attack against PBKDF2.
|
43
|
+
# Default options will result in calculation time of approx. 200 ms with 1 MB memory use.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
# @password = SCrypt::Password.create("my secret", :max_time => 0.25)
|
47
|
+
#
|
48
|
+
def create(secret, options = {})
|
49
|
+
options = SCrypt::Engine::DEFAULTS.merge(options)
|
50
|
+
|
51
|
+
# Clamp minimum/maximum keylen
|
52
|
+
options[:key_len] = 16 if options[:key_len] < 16
|
53
|
+
options[:key_len] = 512 if options[:key_len] > 512
|
54
|
+
|
55
|
+
# Clamp minimum/maximum salt_size
|
56
|
+
options[:salt_size] = 8 if options[:salt_size] < 8
|
57
|
+
options[:salt_size] = 32 if options[:salt_size] > 32
|
58
|
+
|
59
|
+
salt = SCrypt::Engine.generate_salt(options)
|
60
|
+
hash = SCrypt::Engine.hash_secret(secret, salt, options[:key_len])
|
61
|
+
|
62
|
+
Password.new(hash)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Initializes a SCrypt::Password instance with the data from a stored hash.
|
67
|
+
def initialize(raw_hash)
|
68
|
+
raise Errors::InvalidHash, 'invalid hash' unless valid_hash?(raw_hash)
|
69
|
+
|
70
|
+
replace(raw_hash)
|
71
|
+
|
72
|
+
@cost, @salt, @digest = split_hash(to_s)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
|
76
|
+
def ==(other)
|
77
|
+
SecurityUtils.secure_compare(self, SCrypt::Engine.hash_secret(other, @cost + @salt, digest.length / 2))
|
78
|
+
end
|
79
|
+
alias is_password? ==
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# Returns true if +h+ is a valid hash.
|
84
|
+
def valid_hash?(h)
|
85
|
+
h.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{16,64}\$[A-Za-z0-9]{32,1024}$/) != nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# call-seq:
|
89
|
+
# split_hash(raw_hash) -> cost, salt, hash
|
90
|
+
#
|
91
|
+
# Splits +h+ into cost, salt, and hash and returns them in that order.
|
92
|
+
def split_hash(h)
|
93
|
+
n, v, r, salt, hash = h.split('$')
|
94
|
+
[[n, v, r].join('$') + '$', salt, hash]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/scrypt/scrypt_ext.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# NOTE:: a verbatim copy of https://github.com/rails/rails/blob/c8c660002f4b0e9606de96325f20b95248b6ff2d/activesupport/lib/active_support/security_utils.rb
|
2
4
|
# Please see the Rails license: https://github.com/rails/rails/blob/master/activesupport/MIT-LICENSE
|
3
5
|
|
@@ -9,15 +11,14 @@ module SCrypt
|
|
9
11
|
# that have already been processed by HMAC. This should not be used
|
10
12
|
# on variable length plaintext strings because it could leak length info
|
11
13
|
# via timing attacks.
|
12
|
-
def secure_compare(a, b)
|
14
|
+
def self.secure_compare(a, b)
|
13
15
|
return false unless a.bytesize == b.bytesize
|
14
16
|
|
15
17
|
l = a.unpack "C#{a.bytesize}"
|
16
18
|
|
17
19
|
res = 0
|
18
20
|
b.each_byte { |byte| res |= byte ^ l.shift }
|
19
|
-
res
|
21
|
+
res.zero?
|
20
22
|
end
|
21
|
-
module_function :secure_compare
|
22
23
|
end
|
23
24
|
end
|
data/lib/scrypt/version.rb
CHANGED
data/scrypt.gemspec
CHANGED
@@ -1,37 +1,55 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Performance/EndWith, Style/SpecialGlobalVars
|
4
|
+
|
2
5
|
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require
|
6
|
+
require 'scrypt/version'
|
4
7
|
|
5
8
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
9
|
+
s.name = 'scrypt'
|
7
10
|
s.version = SCrypt::VERSION
|
8
|
-
s.authors = [
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
s.authors = ['Patrick Hogan',
|
12
|
+
'Stephen von Takach',
|
13
|
+
'Rene van Paassen',
|
14
|
+
'Johanns Gregorian']
|
15
|
+
s.email = ['pbhogan@gmail.com',
|
16
|
+
'steve@advancedcontrol.com.au',
|
17
|
+
'rene.vanpaassen@gmail.com',
|
18
|
+
'io+scrypt@jsg.io']
|
19
|
+
s.cert_chain = ['certs/stakach.pem']
|
12
20
|
s.license = 'BSD-3-Clause'
|
13
|
-
|
14
|
-
s.
|
15
|
-
|
16
|
-
s.
|
21
|
+
|
22
|
+
s.signing_key = File.expand_path('~/.ssh/gem-private_key.pem') if $0 =~ /gem\z/
|
23
|
+
|
24
|
+
s.homepage = 'https://github.com/pbhogan/scrypt'
|
25
|
+
s.summary = 'scrypt password hashing algorithm.'
|
26
|
+
|
27
|
+
s.description = <<-DESC
|
17
28
|
The scrypt key derivation function is designed to be far
|
18
29
|
more secure against hardware brute-force attacks than
|
19
30
|
alternative functions such as PBKDF2 or bcrypt.
|
20
|
-
|
31
|
+
DESC
|
21
32
|
|
22
33
|
s.add_dependency 'ffi-compiler', '>= 1.0', '< 2.0'
|
34
|
+
s.add_development_dependency 'awesome_print', '>= 1', '< 2'
|
23
35
|
s.add_development_dependency 'rake', '>= 9', '< 13'
|
24
|
-
s.add_development_dependency 'rspec', '>= 3', '< 4'
|
25
36
|
s.add_development_dependency 'rdoc', '>= 4', '< 5'
|
26
|
-
s.add_development_dependency '
|
37
|
+
s.add_development_dependency 'rspec', '>= 3', '< 4'
|
38
|
+
|
39
|
+
if RUBY_VERSION >= '2.5'
|
40
|
+
s.add_development_dependency 'rubocop', '>= 0.76.0', '< 1.0.0'
|
41
|
+
s.add_development_dependency 'rubocop-gitlab-security', '>= 0.1.1', '< 0.2'
|
42
|
+
s.add_development_dependency 'rubocop-performance', '>= 1.5.0', '< 1.6.0'
|
43
|
+
end
|
27
44
|
|
28
|
-
s.rubyforge_project =
|
45
|
+
s.rubyforge_project = 'scrypt'
|
29
46
|
|
30
|
-
s.extensions = [
|
47
|
+
s.extensions = ['ext/scrypt/Rakefile']
|
31
48
|
|
32
|
-
s.files = %w
|
33
|
-
s.files += Dir.glob(
|
34
|
-
s.test_files = Dir.glob(
|
35
|
-
s.require_paths = [
|
49
|
+
s.files = %w[Rakefile scrypt.gemspec README.md COPYING] + Dir.glob('{lib,spec,autotest}/**/*')
|
50
|
+
s.files += Dir.glob('ext/scrypt/*')
|
51
|
+
s.test_files = Dir.glob('spec/**/*')
|
52
|
+
s.require_paths = ['lib']
|
36
53
|
end
|
37
54
|
|
55
|
+
# rubocop:enable
|
data/spec/scrypt/engine_spec.rb
CHANGED
@@ -1,84 +1,81 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
4
|
+
|
5
|
+
describe 'The SCrypt engine' do
|
6
|
+
it 'should calculate a valid cost factor' do
|
7
|
+
first = SCrypt::Engine.calibrate(max_time: 0.2)
|
6
8
|
expect(SCrypt::Engine.valid_cost?(first)).to equal(true)
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
it "should produce strings" do
|
12
|
+
describe 'Generating SCrypt salts' do
|
13
|
+
it 'should produce strings' do
|
13
14
|
expect(SCrypt::Engine.generate_salt).to be_an_instance_of(String)
|
14
15
|
end
|
15
16
|
|
16
|
-
it
|
17
|
+
it 'should produce random data' do
|
17
18
|
expect(SCrypt::Engine.generate_salt).not_to equal(SCrypt::Engine.generate_salt)
|
18
19
|
end
|
19
20
|
|
20
|
-
it
|
21
|
+
it 'should used the saved cost factor' do
|
21
22
|
# Verify cost is different before saving
|
22
|
-
cost = SCrypt::Engine.calibrate(:
|
23
|
-
expect(SCrypt::Engine.generate_salt(:
|
23
|
+
cost = SCrypt::Engine.calibrate(max_time: 0.01)
|
24
|
+
expect(SCrypt::Engine.generate_salt(max_time: 30, max_mem: 64 * 1024 * 1024)).not_to start_with(cost)
|
24
25
|
|
25
|
-
cost = SCrypt::Engine.calibrate!(:
|
26
|
-
expect(SCrypt::Engine.generate_salt(:
|
26
|
+
cost = SCrypt::Engine.calibrate!(max_time: 0.01)
|
27
|
+
expect(SCrypt::Engine.generate_salt(max_time: 30, max_mem: 64 * 1024 * 1024)).to start_with(cost)
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
expect(SCrypt::Engine.autodetect_cost("2a$08$c3$randomjunkgoeshere")).to eq("2a$08$c3$")
|
31
|
+
describe 'Autodetecting of salt cost' do
|
32
|
+
it 'should work' do
|
33
|
+
expect(SCrypt::Engine.autodetect_cost('2a$08$c3$randomjunkgoeshere')).to eq('2a$08$c3$')
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
describe "Generating SCrypt hashes" do
|
39
|
-
|
37
|
+
describe 'Generating SCrypt hashes' do
|
40
38
|
class MyInvalidSecret
|
41
39
|
undef to_s
|
42
40
|
end
|
43
41
|
|
44
42
|
before :each do
|
45
43
|
@salt = SCrypt::Engine.generate_salt
|
46
|
-
@password =
|
44
|
+
@password = 'woo'
|
47
45
|
end
|
48
46
|
|
49
|
-
it
|
47
|
+
it 'should produce a string' do
|
50
48
|
expect(SCrypt::Engine.hash_secret(@password, @salt)).to be_an_instance_of(String)
|
51
49
|
end
|
52
50
|
|
53
|
-
it
|
54
|
-
expect(
|
51
|
+
it 'should raise an InvalidSalt error if the salt is invalid' do
|
52
|
+
expect(-> { SCrypt::Engine.hash_secret(@password, 'nino') }).to raise_error(SCrypt::Errors::InvalidSalt)
|
55
53
|
end
|
56
54
|
|
57
|
-
it
|
58
|
-
expect(
|
59
|
-
expect(
|
60
|
-
expect(
|
55
|
+
it 'should raise an InvalidSecret error if the secret is invalid' do
|
56
|
+
expect(-> { SCrypt::Engine.hash_secret(MyInvalidSecret.new, @salt) }).to raise_error(SCrypt::Errors::InvalidSecret)
|
57
|
+
expect(-> { SCrypt::Engine.hash_secret(nil, @salt) }).to_not raise_error
|
58
|
+
expect(-> { SCrypt::Engine.hash_secret(false, @salt) }).to_not raise_error
|
61
59
|
end
|
62
60
|
|
63
|
-
it
|
64
|
-
expect(SCrypt::Engine.hash_secret(false, @salt)).to eq(SCrypt::Engine.hash_secret(
|
61
|
+
it 'should call #to_s on the secret and use the return value as the actual secret data' do
|
62
|
+
expect(SCrypt::Engine.hash_secret(false, @salt)).to eq(SCrypt::Engine.hash_secret('false', @salt))
|
65
63
|
end
|
66
64
|
end
|
67
65
|
|
68
|
-
describe
|
69
|
-
it
|
70
|
-
|
66
|
+
describe 'SCrypt test vectors' do
|
67
|
+
it 'should match results of SCrypt function' do
|
71
68
|
expect(SCrypt::Engine.scrypt('', '', 16, 1, 1, 64).unpack('H*').first).to eq('77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906')
|
72
69
|
expect(SCrypt::Engine.scrypt('password', 'NaCl', 1024, 8, 16, 64).unpack('H*').first).to eq('fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640')
|
73
|
-
expect(SCrypt::Engine.scrypt('pleaseletmein', 'SodiumChloride',
|
70
|
+
expect(SCrypt::Engine.scrypt('pleaseletmein', 'SodiumChloride', 16_384, 8, 1, 64).unpack('H*').first).to eq('7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887')
|
74
71
|
# Raspberry is memory limited, and fails on this test
|
75
|
-
|
72
|
+
# expect(SCrypt::Engine.scrypt('pleaseletmein', 'SodiumChloride', 1048576, 8, 1, 64).unpack('H*').first).to eq('2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4')
|
76
73
|
end
|
77
74
|
|
78
|
-
it
|
75
|
+
it 'should match equivalent results sent through hash_secret() function' do
|
79
76
|
expect(SCrypt::Engine.hash_secret('', '10$1$1$0000000000000000', 64)).to match(/\$77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906$/)
|
80
77
|
expect(SCrypt::Engine.hash_secret('password', '400$8$10$000000004e61436c', 64)).to match(/\$fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640$/)
|
81
78
|
expect(SCrypt::Engine.hash_secret('pleaseletmein', '4000$8$1$536f6469756d43686c6f72696465', 64)).to match(/\$7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887$/)
|
82
|
-
|
79
|
+
# expect(SCrypt::Engine.hash_secret('pleaseletmein', '100000$8$1$536f6469756d43686c6f72696465', 64)).to match(/\$2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4$/)
|
83
80
|
end
|
84
81
|
end
|
@@ -1,137 +1,135 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
4
|
+
|
5
|
+
describe 'Creating a hashed password' do
|
4
6
|
before :each do
|
5
|
-
@password = SCrypt::Password.create(
|
7
|
+
@password = SCrypt::Password.create('s3cr3t', max_time: 0.25)
|
6
8
|
end
|
7
9
|
|
8
|
-
it
|
10
|
+
it 'should return a SCrypt::Password' do
|
9
11
|
expect(@password).to be_an_instance_of(SCrypt::Password)
|
10
12
|
end
|
11
13
|
|
12
|
-
it
|
13
|
-
expect(
|
14
|
+
it 'should return a valid password' do
|
15
|
+
expect(-> { SCrypt::Password.new(@password) }).to_not raise_error
|
14
16
|
end
|
15
17
|
|
16
|
-
it
|
17
|
-
expect(
|
18
|
-
expect(
|
19
|
-
expect(
|
18
|
+
it 'should behave normally if the secret is not a string' do
|
19
|
+
expect(-> { SCrypt::Password.create(nil) }).to_not raise_error
|
20
|
+
expect(-> { SCrypt::Password.create(woo: 'yeah') }).to_not raise_error
|
21
|
+
expect(-> { SCrypt::Password.create(false) }).to_not raise_error
|
20
22
|
end
|
21
23
|
|
22
|
-
it
|
23
|
-
expect(
|
24
|
-
expect(
|
25
|
-
expect(
|
24
|
+
it 'should tolerate empty string secrets' do
|
25
|
+
expect(-> { SCrypt::Password.create("\n".chop) }).to_not raise_error
|
26
|
+
expect(-> { SCrypt::Password.create('') }).to_not raise_error
|
27
|
+
expect(-> { SCrypt::Password.create('') }).to_not raise_error
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
|
-
|
30
|
-
describe "Reading a hashed password" do
|
31
|
+
describe 'Reading a hashed password' do
|
31
32
|
before :each do
|
32
|
-
@secret =
|
33
|
-
@hash =
|
33
|
+
@secret = 'my secret'
|
34
|
+
@hash = '400$8$d$173a8189751c095a29b933789560b73bf17b2e01$9bf66d74bd6f3ebcf99da3b379b689b89db1cb07'
|
34
35
|
end
|
35
36
|
|
36
|
-
it
|
37
|
+
it 'should read the cost, salt, and hash' do
|
37
38
|
password = SCrypt::Password.new(@hash)
|
38
|
-
expect(password.cost).to eq(
|
39
|
-
expect(password.salt).to eq(
|
39
|
+
expect(password.cost).to eq('400$8$d$')
|
40
|
+
expect(password.salt).to eq('173a8189751c095a29b933789560b73bf17b2e01')
|
40
41
|
expect(password.to_s).to eq(@hash)
|
41
42
|
end
|
42
43
|
|
43
|
-
it
|
44
|
-
expect(
|
44
|
+
it 'should raise an InvalidHashError when given an invalid hash' do
|
45
|
+
expect(-> { SCrypt::Password.new('not a valid hash') }).to raise_error(SCrypt::Errors::InvalidHash)
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
|
-
describe
|
49
|
+
describe 'Comparing a hashed password with a secret' do
|
49
50
|
before :each do
|
50
|
-
@secret =
|
51
|
+
@secret = 's3cr3t'
|
51
52
|
@password = SCrypt::Password.create(@secret)
|
52
53
|
end
|
53
54
|
|
54
|
-
it
|
55
|
+
it 'should compare successfully to the original secret' do
|
55
56
|
expect((@password == @secret)).to be(true)
|
56
57
|
end
|
57
58
|
|
58
|
-
it
|
59
|
-
expect((@password ==
|
59
|
+
it 'should compare unsuccessfully to anything besides original secret' do
|
60
|
+
expect((@password == '@secret')).to be(false)
|
60
61
|
end
|
61
|
-
|
62
62
|
end
|
63
63
|
|
64
|
-
describe
|
64
|
+
describe 'non-default salt sizes' do
|
65
65
|
before :each do
|
66
|
-
@secret =
|
66
|
+
@secret = 's3cret'
|
67
67
|
end
|
68
68
|
|
69
|
-
it
|
70
|
-
@password = SCrypt::Password.create(@secret, :
|
69
|
+
it 'should enforce a minimum salt of 8 bytes' do
|
70
|
+
@password = SCrypt::Password.create(@secret, salt_size: 7)
|
71
71
|
expect(@password.salt.length).to eq(8 * 2)
|
72
72
|
end
|
73
73
|
|
74
|
-
it
|
75
|
-
@password = SCrypt::Password.create(@secret, :
|
74
|
+
it 'should allow a salt of 32 bytes' do
|
75
|
+
@password = SCrypt::Password.create(@secret, salt_size: 32)
|
76
76
|
expect(@password.salt.length).to eq(32 * 2)
|
77
77
|
end
|
78
78
|
|
79
|
-
it
|
80
|
-
@password = SCrypt::Password.create(@secret, :
|
79
|
+
it 'should enforce a maximum salt of 32 bytes' do
|
80
|
+
@password = SCrypt::Password.create(@secret, salt_size: 33)
|
81
81
|
expect(@password.salt.length).to eq(32 * 2)
|
82
82
|
end
|
83
83
|
|
84
|
-
it
|
85
|
-
@password = SCrypt::Password.create(@secret, :
|
84
|
+
it 'should pad a 20-byte salt to not look like a 20-byte SHA1' do
|
85
|
+
@password = SCrypt::Password.create(@secret, salt_size: 20)
|
86
86
|
expect(@password.salt.length).to eq(41)
|
87
87
|
end
|
88
88
|
|
89
|
-
it
|
90
|
-
@password = SCrypt::Password.create(@secret, :
|
89
|
+
it 'should properly compare a non-standard salt hash' do
|
90
|
+
@password = SCrypt::Password.create(@secret, salt_size: 20)
|
91
91
|
expect((SCrypt::Password.new(@password.to_s) == @secret)).to be(true)
|
92
92
|
end
|
93
|
-
|
94
93
|
end
|
95
94
|
|
96
|
-
describe
|
95
|
+
describe 'non-default key lengths' do
|
97
96
|
before :each do
|
98
|
-
@secret =
|
97
|
+
@secret = 's3cret'
|
99
98
|
end
|
100
99
|
|
101
|
-
it
|
102
|
-
@password = SCrypt::Password.create(@secret, :
|
100
|
+
it 'should enforce a minimum keylength of 16 bytes' do
|
101
|
+
@password = SCrypt::Password.create(@secret, key_len: 15)
|
103
102
|
expect(@password.digest.length).to eq(16 * 2)
|
104
103
|
end
|
105
104
|
|
106
|
-
it
|
107
|
-
@password = SCrypt::Password.create(@secret, :
|
105
|
+
it 'should allow a keylength of 512 bytes' do
|
106
|
+
@password = SCrypt::Password.create(@secret, key_len: 512)
|
108
107
|
expect(@password.digest.length).to eq(512 * 2)
|
109
108
|
end
|
110
109
|
|
111
|
-
it
|
112
|
-
@password = SCrypt::Password.create(@secret, :
|
110
|
+
it 'should enforce a maximum keylength of 512 bytes' do
|
111
|
+
@password = SCrypt::Password.create(@secret, key_len: 513)
|
113
112
|
expect(@password.digest.length).to eq(512 * 2)
|
114
113
|
end
|
115
114
|
|
116
|
-
it
|
117
|
-
@password = SCrypt::Password.create(@secret, :
|
115
|
+
it 'should properly compare a non-standard hash' do
|
116
|
+
@password = SCrypt::Password.create(@secret, key_len: 512)
|
118
117
|
expect((SCrypt::Password.new(@password.to_s) == @secret)).to be(true)
|
119
118
|
end
|
120
|
-
|
121
119
|
end
|
122
120
|
|
123
|
-
describe
|
121
|
+
describe 'Old-style hashes' do
|
124
122
|
before :each do
|
125
|
-
@secret =
|
126
|
-
@hash =
|
123
|
+
@secret = 'my secret'
|
124
|
+
@hash = '400$8$d$173a8189751c095a29b933789560b73bf17b2e01$9bf66d74bd6f3ebcf99da3b379b689b89db1cb07'
|
127
125
|
end
|
128
126
|
|
129
|
-
it
|
127
|
+
it 'should compare successfully' do
|
130
128
|
expect((SCrypt::Password.new(@hash) == @secret)).to be(true)
|
131
129
|
end
|
132
130
|
end
|
133
131
|
|
134
|
-
describe
|
132
|
+
describe 'Respecting standard ruby behaviors' do
|
135
133
|
it 'should hash as an integer' do
|
136
134
|
password = SCrypt::Password.create('')
|
137
135
|
expect(password.hash).to be_kind_of(Integer)
|