scrypt 3.0.6 → 3.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.
@@ -0,0 +1,127 @@
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
+ # Key length constraints
27
+ MIN_KEY_LENGTH = 16
28
+ MAX_KEY_LENGTH = 512
29
+ # Salt size constraints
30
+ MIN_SALT_SIZE = 8
31
+ MAX_SALT_SIZE = 32
32
+
33
+ # The hash portion of the stored password hash.
34
+ attr_reader :digest
35
+ # The salt of the store password hash
36
+ attr_reader :salt
37
+ # The cost factor used to create the hash.
38
+ attr_reader :cost
39
+
40
+ class << self
41
+ # Hashes a secret, returning a SCrypt::Password instance.
42
+ # Takes five options (optional), which will determine the salt/key's length and
43
+ # the cost limits of the computation.
44
+ # <tt>:key_len</tt> specifies the length in bytes of the key you want to generate.
45
+ # The default is 32 bytes (256 bits). Minimum is 16 bytes (128 bits). Maximum is 512 bytes (4096 bits).
46
+ # <tt>:salt_size</tt> specifies the size in bytes of the random salt you want to generate.
47
+ # The default and minimum is 8 bytes (64 bits). Maximum is 32 bytes (256 bits).
48
+ # <tt>:max_time</tt> specifies the maximum number of seconds the computation should take.
49
+ # <tt>:max_mem</tt> specifies the maximum number of bytes the computation should take.
50
+ # A value of 0 specifies no upper limit. The minimum is always 1 MB.
51
+ # <tt>:max_memfrac</tt> specifies the maximum memory in a fraction of available resources to use.
52
+ # Any value equal to 0 or greater than 0.5 will result in 0.5 being used.
53
+ # The scrypt key derivation function is designed to be far more secure against hardware
54
+ # brute-force attacks than alternative functions such as PBKDF2 or bcrypt.
55
+ # The designers of scrypt estimate that on modern (2009) hardware, if 5 seconds are spent
56
+ # computing a derived key, the cost of a hardware brute-force attack against scrypt is roughly
57
+ # 4000 times greater than the cost of a similar attack against bcrypt (to find the same password),
58
+ # and 20000 times greater than a similar attack against PBKDF2.
59
+ # Default options will result in calculation time of approx. 200 ms with 1 MB memory use.
60
+ #
61
+ # Example:
62
+ # @password = SCrypt::Password.create("my secret", :max_time => 0.25)
63
+ #
64
+ def create(secret, options = {})
65
+ options = SCrypt::Engine::DEFAULTS.merge(options)
66
+
67
+ options[:key_len] = clamp_key_length(options[:key_len])
68
+ options[:salt_size] = clamp_salt_size(options[:salt_size])
69
+
70
+ salt = SCrypt::Engine.generate_salt(options)
71
+ hash = SCrypt::Engine.hash_secret(secret, salt, options[:key_len])
72
+
73
+ Password.new(hash)
74
+ end
75
+
76
+ private
77
+
78
+ # Clamps key length to valid range
79
+ def clamp_key_length(key_len)
80
+ return MIN_KEY_LENGTH if key_len < MIN_KEY_LENGTH
81
+ return MAX_KEY_LENGTH if key_len > MAX_KEY_LENGTH
82
+
83
+ key_len
84
+ end
85
+
86
+ # Clamps salt size to valid range
87
+ def clamp_salt_size(salt_size)
88
+ return MIN_SALT_SIZE if salt_size < MIN_SALT_SIZE
89
+ return MAX_SALT_SIZE if salt_size > MAX_SALT_SIZE
90
+
91
+ salt_size
92
+ end
93
+ end
94
+
95
+ # Initializes a SCrypt::Password instance with the data from a stored hash.
96
+ def initialize(raw_hash)
97
+ raise Errors::InvalidHash, 'invalid hash' unless valid_hash?(raw_hash)
98
+
99
+ replace(raw_hash)
100
+
101
+ @cost, @salt, @digest = split_hash(to_s)
102
+ end
103
+
104
+ # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
105
+ def ==(other)
106
+ SecurityUtils.secure_compare(self, SCrypt::Engine.hash_secret(other, @cost + @salt, digest.length / 2))
107
+ end
108
+ alias is_password? ==
109
+
110
+ private
111
+
112
+ # Returns true if +h+ is a valid hash.
113
+ def valid_hash?(h)
114
+ !SCrypt::Engine::HASH_PATTERN.match(h).nil?
115
+ end
116
+
117
+ # call-seq:
118
+ # split_hash(raw_hash) -> cost, salt, hash
119
+ #
120
+ # Splits +h+ into cost, salt, and hash and returns them in that order.
121
+ def split_hash(hash_string)
122
+ cpu_cost, version, memory_cost, salt, hash = hash_string.split('$')
123
+ cost_string = "#{[cpu_cost, version, memory_cost].join('$')}$"
124
+ [cost_string, salt, hash]
125
+ end
126
+ end
127
+ end
@@ -1,9 +1,16 @@
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
7
- ffi_lib FFI::Compiler::Loader.find('scrypt_ext')
9
+
10
+ begin
11
+ ffi_lib FFI::Compiler::Loader.find('scrypt_ext')
12
+ rescue LoadError => e
13
+ raise LoadError, "Failed to load scrypt extension library: #{e.message}"
14
+ end
8
15
  end
9
16
  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.1.0'
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'
data/scrypt.gemspec CHANGED
@@ -1,37 +1,42 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "scrypt/version"
1
+ # frozen_string_literal: true
2
+
3
+ $:.push File.expand_path('lib', __dir__)
4
+ require 'scrypt/version'
4
5
 
5
6
  Gem::Specification.new do |s|
6
- s.name = "scrypt"
7
+ s.name = 'scrypt'
7
8
  s.version = SCrypt::VERSION
8
- s.authors = ["Patrick Hogan", "Stephen von Takach", "Rene van Paassen" ]
9
- s.email = ["pbhogan@gmail.com", "steve@advancedcontrol.com.au",
10
- "rene.vanpaassen@gmail.com" ]
9
+ s.authors = ['Patrick Hogan',
10
+ 'Stephen von Takach',
11
+ 'Rene van Paassen',
12
+ 'Johanns Gregorian']
13
+ s.email = ['pbhogan@gmail.com',
14
+ 'steve@advancedcontrol.com.au',
15
+ 'rene.vanpaassen@gmail.com',
16
+ 'io+scrypt@jsg.io']
11
17
  s.cert_chain = ['certs/pbhogan.pem']
12
18
  s.license = 'BSD-3-Clause'
13
- s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
14
- s.homepage = "https://github.com/pbhogan/scrypt"
15
- s.summary = "scrypt password hashing algorithm."
16
- s.description = <<-EOF
19
+
20
+ s.signing_key = File.expand_path('~/.ssh/gem-private_key.pem') if $0 =~ /gem\z/
21
+ s.metadata['rubygems_mfa_required'] = 'true'
22
+
23
+ s.homepage = 'https://github.com/pbhogan/scrypt'
24
+ s.summary = 'scrypt password hashing algorithm.'
25
+
26
+ s.description = <<-DESC
17
27
  The scrypt key derivation function is designed to be far
18
28
  more secure against hardware brute-force attacks than
19
29
  alternative functions such as PBKDF2 or bcrypt.
20
- EOF
30
+ DESC
21
31
 
22
- s.add_dependency 'ffi-compiler', '>= 1.0', '< 2.0'
23
- s.add_development_dependency 'rake', '>= 9', '< 13'
24
- s.add_development_dependency 'rspec', '>= 3', '< 4'
25
- s.add_development_dependency 'rdoc', '>= 4', '< 5'
26
- s.add_development_dependency 'awesome_print', '>= 1', '< 2'
32
+ s.required_ruby_version = '>= 2.3.0'
27
33
 
28
- s.rubyforge_project = "scrypt"
34
+ s.add_dependency 'ffi-compiler', '>= 1.0', '< 2.0'
35
+ s.add_dependency 'rake', '~> 13'
29
36
 
30
- s.extensions = ["ext/scrypt/Rakefile"]
37
+ s.extensions = ['ext/scrypt/Rakefile']
31
38
 
32
- s.files = %w(Rakefile scrypt.gemspec README.md COPYING) + Dir.glob("{lib,spec,autotest}/**/*")
33
- s.files += Dir.glob("ext/scrypt/*")
34
- s.test_files = Dir.glob("spec/**/*")
35
- s.require_paths = ["lib"]
39
+ s.files = %w[Rakefile scrypt.gemspec README.md COPYING] + Dir.glob('{lib,spec}/**/*')
40
+ s.files += Dir.glob('ext/scrypt/*')
41
+ s.require_paths = ['lib']
36
42
  end
37
-
@@ -0,0 +1,67 @@
1
+ # SCrypt Test Vectors
2
+ # These are the official test vectors from the scrypt specification
3
+ # Used to verify our implementation matches the reference
4
+
5
+ scrypt_vectors:
6
+ - description: "Empty string test"
7
+ password: ""
8
+ salt: ""
9
+ n: 16
10
+ r: 1
11
+ p: 1
12
+ key_len: 64
13
+ expected: "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906"
14
+
15
+ - description: "Standard test vector"
16
+ password: "password"
17
+ salt: "NaCl"
18
+ n: 1024
19
+ r: 8
20
+ p: 16
21
+ key_len: 64
22
+ expected: "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640"
23
+
24
+ - description: "High memory test vector"
25
+ password: "pleaseletmein"
26
+ salt: "SodiumChloride"
27
+ n: 16384
28
+ r: 8
29
+ p: 1
30
+ key_len: 64
31
+ expected: "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887"
32
+
33
+ - description: "Very high memory test (disabled on memory-constrained systems)"
34
+ password: "pleaseletmein"
35
+ salt: "SodiumChloride"
36
+ n: 1048576
37
+ r: 8
38
+ p: 1
39
+ key_len: 64
40
+ expected: "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4"
41
+ skip_reason: "Memory limited systems (like Raspberry Pi) may fail this test"
42
+
43
+ hash_secret_vectors:
44
+ - description: "Empty string via hash_secret"
45
+ password: ""
46
+ salt: "10$1$1$0000000000000000"
47
+ key_len: 64
48
+ expected_pattern: "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906"
49
+
50
+ - description: "Standard test via hash_secret"
51
+ password: "password"
52
+ salt: "400$8$10$000000004e61436c"
53
+ key_len: 64
54
+ expected_pattern: "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640"
55
+
56
+ - description: "High memory test via hash_secret"
57
+ password: "pleaseletmein"
58
+ salt: "4000$8$1$536f6469756d43686c6f72696465"
59
+ key_len: 64
60
+ expected_pattern: "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887"
61
+
62
+ - description: "Very high memory test via hash_secret (disabled)"
63
+ password: "pleaseletmein"
64
+ salt: "100000$8$1$536f6469756d43686c6f72696465"
65
+ key_len: 64
66
+ expected_pattern: "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4"
67
+ skip_reason: "Memory limited systems may fail this test"