scrypt 3.0.6 → 3.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27fb084e997c8f5ef6c5eabc8c9823c62b9beb54be2297818ca70c47137b6b12
4
- data.tar.gz: d37621255d1c455593543773f887ac00b12d8040664d886ed5838a5435c0b1c0
3
+ metadata.gz: 8179f4b0bf8f79e8476cc36f386e1fa349efa0db814a7f40a7f67b521a3a80b2
4
+ data.tar.gz: 99dd37077aa2003bf966336aee1f395cc655fcf61aac44ed12f049e36e66b62a
5
5
  SHA512:
6
- metadata.gz: 706b757097bbca08633e852dce8ac3e1e90f8eb1d82e4833ad9f2beefa9e50b3ccc304357b7a55d7cbfe8cfcaca31605bfbf4f28c68661f59e17f978073134be
7
- data.tar.gz: 08de38a6a613855792aafc026df7320e67e2512b76f71231b2a5d46fb56e27c9d99d194435d23507e9d5ea4d3105840cbc7a619746fb523587b7a455bf4cd94d
6
+ metadata.gz: 94b935e06c8d3e342b5de545cc18a8d4e04ed589d22c9006b90049e39970f1063939b660da88be43f293d67156ec394b238955bb68477bc3aefb0d70b74f8bdd
7
+ data.tar.gz: e13414929c7bb835c456e7e4ad3db8f4dedf8092cf0e3fd7413fd423c39591b9b80575d9baeea35ced8d70cb9c82718d9b3fd6429d2c4452d7a87025013d960b
checksums.yaml.gz.sig CHANGED
Binary file
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/setup'
2
4
  require 'bundler/gem_tasks'
3
5
 
@@ -17,7 +19,7 @@ require 'rubygems/package_task'
17
19
 
18
20
  require 'rdoc/task'
19
21
 
20
- task :default => [:clean, :compile_ffi, :spec]
22
+ task default: [:clean, :compile_ffi, :spec]
21
23
 
22
24
  desc 'clean, make and run specs'
23
25
  task :spec do
@@ -29,7 +31,7 @@ task :checksum do
29
31
  built_gem_path = "pkg/scrypt-#{SCrypt::VERSION}.gem"
30
32
  checksum = Digest::SHA512.new.hexdigest(File.read(built_gem_path))
31
33
  checksum_path = "checksum/scrypt-#{SCrypt::VERSION}.gem.sha512"
32
- File.open(checksum_path, 'w' ) {|f| f.write(checksum) }
34
+ File.open(checksum_path, 'w') { |f| f.write(checksum) }
33
35
  end
34
36
 
35
37
  desc 'FFI compiler'
@@ -40,7 +42,7 @@ namespace 'ffi-compiler' do
40
42
  t.cflags << '-Wall -std=c99'
41
43
  t.cflags << '-msse -msse2' if t.platform.arch.include? '86'
42
44
  t.cflags << '-D_GNU_SOURCE=1' if RbConfig::CONFIG['host_os'].downcase =~ /mingw/
43
- t.cflags << '-D_POSIX_C_SOURCE=199309L' if RbConfig::CONFIG['host_os'].downcase =~ /linux/
45
+ t.cflags << '-D_POSIX_C_SOURCE=200809L' if RbConfig::CONFIG['host_os'].downcase =~ /linux/
44
46
 
45
47
  if 1.size == 4 && target_cpu =~ /i386|x86_32/ && t.platform.mac?
46
48
  t.cflags << '-arch i386'
@@ -53,7 +55,7 @@ namespace 'ffi-compiler' do
53
55
  t.add_define 'WINDOWS_OS' if FFI::Platform.windows?
54
56
  end
55
57
  end
56
- task :compile_ffi => ['ffi-compiler:default']
58
+ task compile_ffi: ['ffi-compiler:default']
57
59
 
58
60
  CLEAN.include('ext/scrypt/*{.o,.log,.so,.bundle}')
59
61
  CLEAN.include('lib/**/*{.o,.log,.so,.bundle}')
@@ -80,4 +82,3 @@ Gem::PackageTask.new(gem_spec) do |pkg|
80
82
  pkg.need_tar = true
81
83
  pkg.package_dir = 'pkg'
82
84
  end
83
-
data/ext/scrypt/Rakefile CHANGED
@@ -6,7 +6,7 @@ FFI::Compiler::CompileTask.new('scrypt_ext') do |t|
6
6
  t.cflags << '-Wall -std=c99'
7
7
  t.cflags << '-msse -msse2' if t.platform.arch.include? '86'
8
8
  t.cflags << '-D_GNU_SOURCE=1' if RbConfig::CONFIG['host_os'].downcase =~ /mingw/
9
- t.cflags << '-D_POSIX_C_SOURCE=199309L' if RbConfig::CONFIG['host_os'].downcase =~ /linux/
9
+ t.cflags << '-D_POSIX_C_SOURCE=200809L' if RbConfig::CONFIG['host_os'].downcase =~ /linux/
10
10
 
11
11
  if 1.size == 4 && target_cpu =~ /i386|x86_32/ && t.platform.mac?
12
12
  t.cflags << '-arch i386'
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'openssl'
5
+
6
+ module SCrypt
7
+ module Ext
8
+ # rubocop:disable Style/SymbolArray
9
+ # Bind the external functions
10
+ attach_function :sc_calibrate,
11
+ [:size_t, :double, :double, :pointer],
12
+ :int,
13
+ blocking: true
14
+
15
+ attach_function :crypto_scrypt,
16
+ [:pointer, :size_t, :pointer, :size_t, :uint64, :uint32, :uint32, :pointer, :size_t],
17
+ :int,
18
+ blocking: true # todo
19
+ # rubocop:enable
20
+ end
21
+
22
+ class Engine
23
+ # rubocop:disable Style/MutableConstant
24
+ DEFAULTS = {
25
+ key_len: 32,
26
+ salt_size: 32,
27
+ max_mem: 16 * 1024 * 1024,
28
+ max_memfrac: 0.5,
29
+ max_time: 0.2,
30
+ cost: nil
31
+ }
32
+ # rubocop:enable
33
+
34
+ class Calibration < FFI::Struct
35
+ layout :n, :uint64,
36
+ :r, :uint32,
37
+ :p, :uint32
38
+ end
39
+
40
+ class << self
41
+ def scrypt(secret, salt, *args)
42
+ if args.length == 2
43
+ # args is [cost_string, key_len]
44
+ n, r, p = args[0].split('$').map { |x| x.to_i(16) }
45
+ key_len = args[1]
46
+
47
+ __sc_crypt(secret, salt, n, r, p, key_len)
48
+ elsif args.length == 4
49
+ # args is [n, r, p, key_len]
50
+ n, r, p = args[0, 3]
51
+ key_len = args[3]
52
+
53
+ __sc_crypt(secret, salt, n, r, p, key_len)
54
+ else
55
+ raise ArgumentError, 'invalid number of arguments (4 or 6)'
56
+ end
57
+ end
58
+
59
+ # Given a secret and a valid salt (see SCrypt::Engine.generate_salt) calculates an scrypt password hash.
60
+ def hash_secret(secret, salt, key_len = DEFAULTS[:key_len])
61
+ raise Errors::InvalidSecret, 'invalid secret' unless valid_secret?(secret)
62
+ raise Errors::InvalidSalt, 'invalid salt' unless valid_salt?(salt)
63
+
64
+ cost = autodetect_cost(salt)
65
+ salt_only = salt[/\$([A-Za-z0-9]{16,64})$/, 1]
66
+
67
+ if salt_only.length == 40
68
+ # Old-style hash with 40-character salt
69
+ salt + '$' + Digest::SHA1.hexdigest(scrypt(secret.to_s, salt, cost, 256))
70
+ else
71
+ # New-style hash
72
+ salt_only = [salt_only.sub(/^(00)+/, '')].pack('H*')
73
+ salt + '$' + scrypt(secret.to_s, salt_only, cost, key_len).unpack('H*').first.rjust(key_len * 2, '0')
74
+ end
75
+ end
76
+
77
+ # Generates a random salt with a given computational cost. Uses a saved
78
+ # cost if SCrypt::Engine.calibrate! has been called.
79
+ #
80
+ # Options:
81
+ # <tt>:cost</tt> is a cost string returned by SCrypt::Engine.calibrate
82
+ def generate_salt(options = {})
83
+ options = DEFAULTS.merge(options)
84
+ cost = options[:cost] || calibrate(options)
85
+ salt = OpenSSL::Random.random_bytes(options[:salt_size]).unpack('H*').first.rjust(16, '0')
86
+
87
+ if salt.length == 40
88
+ # If salt is 40 characters, the regexp will think that it is an old-style hash, so add a '0'.
89
+ salt = '0' + salt
90
+ end
91
+ cost + salt
92
+ end
93
+
94
+ # Returns true if +cost+ is a valid cost, false if not.
95
+ def valid_cost?(cost)
96
+ cost.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$$/) != nil
97
+ end
98
+
99
+ # Returns true if +salt+ is a valid salt, false if not.
100
+ def valid_salt?(salt)
101
+ salt.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{16,64}$/) != nil
102
+ end
103
+
104
+ # Returns true if +secret+ is a valid secret, false if not.
105
+ def valid_secret?(secret)
106
+ secret.respond_to?(:to_s)
107
+ end
108
+
109
+ # Returns the cost value which will result in computation limits less than the given options.
110
+ #
111
+ # Options:
112
+ # <tt>:max_time</tt> specifies the maximum number of seconds the computation should take.
113
+ # <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.
114
+ # <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.
115
+ #
116
+ # Example:
117
+ #
118
+ # # should take less than 200ms
119
+ # SCrypt::Engine.calibrate(:max_time => 0.2)
120
+ #
121
+ def calibrate(options = {})
122
+ options = DEFAULTS.merge(options)
123
+ '%x$%x$%x$' % __sc_calibrate(options[:max_mem], options[:max_memfrac], options[:max_time])
124
+ end
125
+
126
+ # Calls SCrypt::Engine.calibrate and saves the cost string for future calls to
127
+ # SCrypt::Engine.generate_salt.
128
+ def calibrate!(options = {})
129
+ DEFAULTS[:cost] = calibrate(options)
130
+ end
131
+
132
+ # Computes the memory use of the given +cost+
133
+ def memory_use(cost)
134
+ n, r, p = cost.split('$').map { |i| i.to_i(16) }
135
+ (128 * r * p) + (256 * r) + (128 * r * n)
136
+ end
137
+
138
+ # Autodetects the cost from the salt string.
139
+ def autodetect_cost(salt)
140
+ salt[/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$/]
141
+ end
142
+
143
+ private
144
+
145
+ def __sc_calibrate(max_mem, max_memfrac, max_time)
146
+ result = nil
147
+
148
+ calibration = Calibration.new
149
+ ret_val = SCrypt::Ext.sc_calibrate(max_mem, max_memfrac, max_time, calibration)
150
+
151
+ raise "calibration error #{result}" unless ret_val.zero?
152
+
153
+ [calibration[:n], calibration[:r], calibration[:p]]
154
+ end
155
+
156
+ def __sc_crypt(secret, salt, n, r, p, key_len)
157
+ result = nil
158
+
159
+ FFI::MemoryPointer.new(:char, key_len) do |buffer|
160
+ ret_val = SCrypt::Ext.crypto_scrypt(
161
+ secret, secret.bytesize, salt, salt.bytesize,
162
+ n, r, p,
163
+ buffer, key_len
164
+ )
165
+
166
+ raise "scrypt error #{ret_val}" unless ret_val.zero?
167
+
168
+ result = buffer.read_string(key_len)
169
+ end
170
+
171
+ result
172
+ end
173
+ end
174
+ end
175
+ end
@@ -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
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ffi'
2
4
  require 'ffi-compiler/loader'
3
5
 
4
6
  module SCrypt
5
7
  module Ext
6
8
  extend FFI::Library
9
+
7
10
  ffi_lib FFI::Compiler::Loader.find('scrypt_ext')
8
11
  end
9
12
  end
@@ -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 == 0
21
+ res.zero?
20
22
  end
21
- module_function :secure_compare
22
23
  end
23
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SCrypt
2
- VERSION = "3.0.6"
4
+ VERSION = "3.0.8"
3
5
  end
data/lib/scrypt.rb CHANGED
@@ -1,269 +1,10 @@
1
- # A wrapper for the scrypt algorithm.
2
-
3
- require "scrypt/scrypt_ext"
4
- require "scrypt/security_utils"
5
- require "openssl"
6
- require "scanf"
7
- require "ffi"
8
-
9
-
10
- module SCrypt
11
-
12
- module Ext
13
- # Bind the external functions
14
- attach_function :sc_calibrate, [:size_t, :double, :double, :pointer], :int, :blocking => true
15
- attach_function :crypto_scrypt, [:pointer, :size_t, :pointer, :size_t, :uint64, :uint32, :uint32, :pointer, :size_t], :int, :blocking => true # todo
16
- end
17
-
18
- module Errors
19
- class InvalidSalt < StandardError; end # The salt parameter provided is invalid.
20
- class InvalidHash < StandardError; end # The hash parameter provided is invalid.
21
- class InvalidSecret < StandardError; end # The secret parameter provided is invalid.
22
- end
23
-
24
- class Engine
25
- DEFAULTS = {
26
- :key_len => 32,
27
- :salt_size => 32,
28
- :max_mem => 16 * 1024 * 1024,
29
- :max_memfrac => 0.5,
30
- :max_time => 0.2,
31
- :cost => nil
32
- }
33
-
34
- def self.scrypt(secret, salt, *args)
35
- if args.length == 2
36
- # args is [cost_string, key_len]
37
- n, r, p = args[0].split('$').map{ |x| x.to_i(16) }
38
- key_len = args[1]
39
- __sc_crypt(secret, salt, n, r, p, key_len)
40
- elsif args.length == 4
41
- # args is [n, r, p, key_len]
42
- n, r, p = args[0, 3]
43
- key_len = args[3]
44
- __sc_crypt(secret, salt, n, r, p, key_len)
45
- else
46
- raise ArgumentError.new("invalid number of arguments (4 or 6)")
47
- end
48
- end
49
-
50
- # Given a secret and a valid salt (see SCrypt::Engine.generate_salt) calculates an scrypt password hash.
51
- def self.hash_secret(secret, salt, key_len = DEFAULTS[:key_len])
52
- if valid_secret?(secret)
53
- if valid_salt?(salt)
54
- cost = autodetect_cost(salt)
55
- salt_only = salt[/\$([A-Za-z0-9]{16,64})$/, 1]
56
- if salt_only.length == 40
57
- # Old-style hash with 40-character salt
58
- salt + "$" + Digest::SHA1.hexdigest(scrypt(secret.to_s, salt, cost, 256))
59
- else
60
- # New-style hash
61
- salt_only = [salt_only.sub(/^(00)+/, '')].pack('H*')
62
- salt + "$" + scrypt(secret.to_s, salt_only, cost, key_len).unpack('H*').first.rjust(key_len * 2, '0')
63
- end
64
- else
65
- raise Errors::InvalidSalt.new("invalid salt")
66
- end
67
- else
68
- raise Errors::InvalidSecret.new("invalid secret")
69
- end
70
- end
71
-
72
- # Generates a random salt with a given computational cost. Uses a saved
73
- # cost if SCrypt::Engine.calibrate! has been called.
74
- #
75
- # Options:
76
- # <tt>:cost</tt> is a cost string returned by SCrypt::Engine.calibrate
77
- def self.generate_salt(options = {})
78
- options = DEFAULTS.merge(options)
79
- cost = options[:cost] || calibrate(options)
80
- salt = OpenSSL::Random.random_bytes(options[:salt_size]).unpack('H*').first.rjust(16,'0')
81
- if salt.length == 40
82
- #If salt is 40 characters, the regexp will think that it is an old-style hash, so add a '0'.
83
- salt = '0' + salt
84
- end
85
- cost + salt
86
- end
87
-
88
- # Returns true if +cost+ is a valid cost, false if not.
89
- def self.valid_cost?(cost)
90
- cost.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$$/) != nil
91
- end
92
-
93
- # Returns true if +salt+ is a valid salt, false if not.
94
- def self.valid_salt?(salt)
95
- salt.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{16,64}$/) != nil
96
- end
97
-
98
- # Returns true if +secret+ is a valid secret, false if not.
99
- def self.valid_secret?(secret)
100
- secret.respond_to?(:to_s)
101
- end
102
-
103
- # Returns the cost value which will result in computation limits less than the given options.
104
- #
105
- # Options:
106
- # <tt>:max_time</tt> specifies the maximum number of seconds the computation should take.
107
- # <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.
108
- # <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.
109
- #
110
- # Example:
111
- #
112
- # # should take less than 200ms
113
- # SCrypt::Engine.calibrate(:max_time => 0.2)
114
- #
115
- def self.calibrate(options = {})
116
- options = DEFAULTS.merge(options)
117
- "%x$%x$%x$" % __sc_calibrate(options[:max_mem], options[:max_memfrac], options[:max_time])
118
- end
119
-
120
- # Calls SCrypt::Engine.calibrate and saves the cost string for future calls to
121
- # SCrypt::Engine.generate_salt.
122
- def self.calibrate!(options = {})
123
- DEFAULTS[:cost] = calibrate(options)
124
- end
125
-
126
- # Computes the memory use of the given +cost+
127
- def self.memory_use(cost)
128
- n, r, p = cost.scanf("%x$%x$%x$")
129
- (128 * r * p) + (256 * r) + (128 * r * n);
130
- end
1
+ # frozen_string_literal: true
131
2
 
132
- # Autodetects the cost from the salt string.
133
- def self.autodetect_cost(salt)
134
- salt[/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$/]
135
- end
136
-
137
- private
138
-
139
- class Calibration < FFI::Struct
140
- layout :n, :uint64,
141
- :r, :uint32,
142
- :p, :uint32
143
- end
144
-
145
- def self.__sc_calibrate(max_mem, max_memfrac, max_time)
146
- result = nil
147
-
148
- calibration = Calibration.new
149
- retval = SCrypt::Ext.sc_calibrate(max_mem, max_memfrac, max_time, calibration)
150
-
151
- if retval == 0
152
- result = [calibration[:n], calibration[:r], calibration[:p]]
153
- else
154
- raise "calibration error #{result}"
155
- end
156
-
157
- result
158
- end
159
-
160
- def self.__sc_crypt(secret, salt, n, r, p, key_len)
161
- result = nil
162
-
163
- FFI::MemoryPointer.new(:char, key_len) do |buffer|
164
- retval = SCrypt::Ext.crypto_scrypt(
165
- secret, secret.bytesize, salt, salt.bytesize,
166
- n, r, p,
167
- buffer, key_len
168
- )
169
- if retval == 0
170
- result = buffer.read_string(key_len)
171
- else
172
- raise "scrypt error #{retval}"
173
- end
174
- end
175
-
176
- result
177
- end
178
- end
179
-
180
- # A password management class which allows you to safely store users' passwords and compare them.
181
- #
182
- # Example usage:
183
- #
184
- # include "scrypt"
185
- #
186
- # # hash a user's password
187
- # @password = Password.create("my grand secret")
188
- # @password #=> "2000$8$1$f5f2fa5fe5484a7091f1299768fbe92b5a7fbc77$6a385f22c54d92c314b71a4fd5ef33967c93d679"
189
- #
190
- # # store it safely
191
- # @user.update_attribute(:password, @password)
192
- #
193
- # # read it back
194
- # @user.reload!
195
- # @db_password = Password.new(@user.password)
196
- #
197
- # # compare it after retrieval
198
- # @db_password == "my grand secret" #=> true
199
- # @db_password == "a paltry guess" #=> false
200
- #
201
- class Password < String
202
- # The hash portion of the stored password hash.
203
- attr_reader :digest
204
- # The salt of the store password hash
205
- attr_reader :salt
206
- # The cost factor used to create the hash.
207
- attr_reader :cost
208
-
209
- class << self
210
- # Hashes a secret, returning a SCrypt::Password instance.
211
- # Takes five options (optional), which will determine the salt/key's length and the cost limits of the computation.
212
- # <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).
213
- # <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).
214
- # <tt>:max_time</tt> specifies the maximum number of seconds the computation should take.
215
- # <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.
216
- # <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.
217
- # 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.
218
- # 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.
219
- # Default options will result in calculation time of approx. 200 ms with 1 MB memory use.
220
- #
221
- # Example:
222
- # @password = SCrypt::Password.create("my secret", :max_time => 0.25)
223
- #
224
- def create(secret, options = {})
225
- options = SCrypt::Engine::DEFAULTS.merge(options)
226
- #Clamp minimum/maximum keylen
227
- options[:key_len] = 16 if options[:key_len] < 16
228
- options[:key_len] = 512 if options[:key_len] > 512
229
- #Clamp minimum/maximum salt_size
230
- options[:salt_size] = 8 if options[:salt_size] < 8
231
- options[:salt_size] = 32 if options[:salt_size] > 32
232
- salt = SCrypt::Engine.generate_salt(options)
233
- hash = SCrypt::Engine.hash_secret(secret, salt, options[:key_len])
234
- Password.new(hash)
235
- end
236
- end
237
-
238
- # Initializes a SCrypt::Password instance with the data from a stored hash.
239
- def initialize(raw_hash)
240
- if valid_hash?(raw_hash)
241
- self.replace(raw_hash)
242
- @cost, @salt, @digest = split_hash(self.to_s)
243
- else
244
- raise Errors::InvalidHash.new("invalid hash")
245
- end
246
- end
247
-
248
- # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
249
- def ==(secret)
250
- SecurityUtils.secure_compare(self, SCrypt::Engine.hash_secret(secret, @cost + @salt, self.digest.length / 2))
251
- end
252
- alias_method :is_password?, :==
3
+ # A wrapper for the scrypt algorithm.
253
4
 
254
- private
255
- # Returns true if +h+ is a valid hash.
256
- def valid_hash?(h)
257
- h.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{16,64}\$[A-Za-z0-9]{32,1024}$/) != nil
258
- end
5
+ require 'scrypt/errors'
6
+ require 'scrypt/scrypt_ext'
7
+ require 'scrypt/security_utils'
259
8
 
260
- # call-seq:
261
- # split_hash(raw_hash) -> cost, salt, hash
262
- #
263
- # Splits +h+ into cost, salt, and hash and returns them in that order.
264
- def split_hash(h)
265
- n, v, r, salt, hash = h.split('$')
266
- return [n, v, r].join('$') + "$", salt, hash
267
- end
268
- end
269
- end
9
+ require 'scrypt/engine'
10
+ require 'scrypt/password'