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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27fb084e997c8f5ef6c5eabc8c9823c62b9beb54be2297818ca70c47137b6b12
4
- data.tar.gz: d37621255d1c455593543773f887ac00b12d8040664d886ed5838a5435c0b1c0
3
+ metadata.gz: dc64f3f7f338072fc62985fc8aeb406dd88659018f4d52adbe5f6c76e16bcf83
4
+ data.tar.gz: 832a0fb1e6137422a9de7497f6d17fcb7feab3d1b0c76cdea57a86bfeca0b08a
5
5
  SHA512:
6
- metadata.gz: 706b757097bbca08633e852dce8ac3e1e90f8eb1d82e4833ad9f2beefa9e50b3ccc304357b7a55d7cbfe8cfcaca31605bfbf4f28c68661f59e17f978073134be
7
- data.tar.gz: 08de38a6a613855792aafc026df7320e67e2512b76f71231b2a5d46fb56e27c9d99d194435d23507e9d5ea4d3105840cbc7a619746fb523587b7a455bf4cd94d
6
+ metadata.gz: 1ae13fbf6b63924096d36990f87d0244d8d6442400f7aeb27f0571bf8eed7f036fb934ec49e69e8472bc653616c4bbaf161c721d974e06ef602d4ec8d1831019
7
+ data.tar.gz: f619909e8e6e011e59b2a7102d5684910cbb1e028b51bc5a08225d35e1d04810348be17b95d3e59cf2b354f1677dd64e0952873db472846c7ea960a388649c04
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -1,79 +1,205 @@
1
- scrypt [![Build Status](https://secure.travis-ci.org/pbhogan/scrypt.svg)](http://travis-ci.org/pbhogan/scrypt)
2
- ======
1
+ # scrypt
3
2
 
4
- 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.
3
+ A Ruby library providing a secure password hashing solution using the scrypt key derivation function.
5
4
 
6
- * http://www.tarsnap.com/scrypt.html
7
- * http://github.com/pbhogan/scrypt
5
+ [![Gem Version](https://badge.fury.io/rb/scrypt.svg)](https://badge.fury.io/rb/scrypt) [![Ruby](https://github.com/pbhogan/scrypt/actions/workflows/ruby.yml/badge.svg)](https://github.com/pbhogan/scrypt/actions/workflows/ruby.yml)
6
+
7
+ ## About scrypt
8
+
9
+ 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. It accomplishes this by being deliberately memory-intensive, making it expensive to implement in hardware.
10
+
11
+ **Key Features:**
12
+ - Memory-hard function that resists ASIC and FPGA attacks
13
+ - Configurable computational cost, memory usage, and parallelization
14
+ - Drop-in replacement for bcrypt in most applications
15
+ - Production-ready and battle-tested
16
+
17
+ **Resources:**
18
+ - [Original scrypt paper](http://www.tarsnap.com/scrypt.html)
19
+ - [GitHub repository](http://github.com/pbhogan/scrypt)
8
20
 
9
21
  ## Why you should use scrypt
10
22
 
11
23
  ![KDF comparison](https://github.com/tarcieri/scrypt/raw/modern-readme/kdf-comparison.png)
12
24
 
13
- 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.
25
+ 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 4,000 times greater than the cost of a similar attack against bcrypt (to find the same password), and 20,000 times greater than a similar attack against PBKDF2.
26
+
27
+ ## Installation
14
28
 
15
- ## How to install scrypt
29
+ Add this line to your application's Gemfile:
16
30
 
31
+ ```ruby
32
+ gem 'scrypt'
17
33
  ```
34
+
35
+ And then execute:
36
+
37
+ ```bash
38
+ bundle install
39
+ ```
40
+
41
+ Or install it yourself as:
42
+
43
+ ```bash
18
44
  gem install scrypt
19
45
  ```
20
46
 
21
- ## How to use scrypt
47
+ ## Basic Usage
22
48
 
23
- It works pretty similarly to ruby-bcrypt with a few minor differences, especially where the cost factor is concerned.
49
+ The scrypt gem works similarly to ruby-bcrypt with a few minor differences, especially regarding the cost factor configuration.
24
50
 
25
51
  ```ruby
26
52
  require "scrypt"
27
53
 
28
- # hash a user's password
54
+ # Hash a user's password
29
55
  password = SCrypt::Password.create("my grand secret")
30
56
  # => "400$8$36$78f4ae6983f76119$37ec6ce55a2b928dc56ff9a7d0cdafbd7dbde49d9282c38a40b1434e88f24cf5"
31
57
 
32
- # compare to strings
58
+ # Compare passwords
33
59
  password == "my grand secret" # => true
34
60
  password == "a paltry guess" # => false
35
61
  ```
36
62
 
37
- Password.create takes five options which will determine the key length and salt size, as well as the cost limits of the computation:
63
+ ### Configuration Options
38
64
 
39
- * `:key_len` 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).
40
- * `:salt_size` specifies the size in bytes of the random salt you want to generate. The default and maximum is 32 bytes (256 bits). Minimum is 8 bytes (64 bits).
41
- * `:max_time` specifies the maximum number of seconds the computation should take.
42
- * `:max_mem` specifies the maximum number of bytes the computation should take. A value of 0 specifies no upper limit. The minimum is always 1 MB.
43
- * `:max_memfrac` 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.
44
- * `:cost` specifies a cost string (e.g. `'400$8$19$'`) from the `calibrate` method. The `:max_*` options will be ignored if this option is given, or if `calibrate!` has been called.
65
+ `Password.create` accepts several options to customize the key length, salt size, and computational cost limits:
45
66
 
46
- Default options will result in calculation time of approx. 200 ms with 16 MB memory use.
67
+ * **`:key_len`** - Length in bytes of the generated key. Default: 32 bytes (256 bits). Range: 16-512 bytes.
68
+ * **`:salt_size`** - Size in bytes of the random salt. Default: 32 bytes (256 bits). Range: 8-32 bytes.
69
+ * **`:max_time`** - Maximum computation time in seconds. Default: 0.2 seconds.
70
+ * **`:max_mem`** - Maximum memory usage in bytes. Default: 16 MB. Set to 0 for no limit (minimum 1 MB).
71
+ * **`:max_memfrac`** - Maximum memory as a fraction of available resources. Default: 0.5. Range: 0-0.5.
72
+ * **`:cost`** - Explicit cost string from `calibrate` method (e.g., `'400$8$19$'`). When provided, `max_*` options are ignored.
47
73
 
48
- ## Other things you can do
74
+ **Note:** Default options result in approximately 200ms computation time with 16 MB memory usage.
75
+
76
+ ## Advanced Usage
77
+
78
+ ### Engine Methods
79
+
80
+ The scrypt gem provides low-level access to the scrypt algorithm through the `SCrypt::Engine` class:
49
81
 
50
82
  ```ruby
51
83
  require "scrypt"
52
84
 
85
+ # Calibrate scrypt parameters for your system
53
86
  SCrypt::Engine.calibrate
54
87
  # => "400$8$25$"
55
88
 
89
+ # Generate a salt with default parameters
56
90
  salt = SCrypt::Engine.generate_salt
57
91
  # => "400$8$26$b62e0f787a5fc373"
58
92
 
59
- SCrypt::Engine.hash_secret "my grand secret", salt
93
+ # Hash a secret with a specific salt
94
+ SCrypt::Engine.hash_secret("my grand secret", salt)
60
95
  # => "400$8$26$b62e0f787a5fc373$0399ccd4fa26642d92741b17c366b7f6bd12ccea5214987af445d2bed97bc6a2"
61
96
 
97
+ # Calibrate with custom memory limits and save for future use
62
98
  SCrypt::Engine.calibrate!(max_mem: 16 * 1024 * 1024)
63
99
  # => "4000$8$4$"
64
100
 
101
+ # Subsequent salt generation will use the calibrated parameters
65
102
  SCrypt::Engine.generate_salt
66
103
  # => "4000$8$4$c6d101522d3cb045"
67
104
  ```
68
105
 
106
+ ### Password Creation with Custom Options
107
+
108
+ ```ruby
109
+ # Create password with custom parameters
110
+ password = SCrypt::Password.create("my secret", {
111
+ key_len: 64,
112
+ salt_size: 16,
113
+ max_time: 0.5,
114
+ max_mem: 32 * 1024 * 1024
115
+ })
116
+
117
+ # Create password with pre-calibrated cost
118
+ cost = SCrypt::Engine.calibrate(max_time: 0.1)
119
+ password = SCrypt::Password.create("my secret", cost: cost)
120
+ ```
121
+
69
122
  ## Usage in Rails (and the like)
70
123
 
71
124
  ```ruby
72
- # store it safely in the user model
125
+ ## Usage in Rails (and similar frameworks)
126
+
127
+ # Store password safely in the user model
73
128
  user.update_attribute(:password, SCrypt::Password.create("my grand secret"))
74
129
 
75
- # read it back later
130
+ # Read it back later
76
131
  user.reload!
77
132
  password = SCrypt::Password.new(user.password)
78
133
  password == "my grand secret" # => true
79
134
  ```
135
+
136
+ ## Security Considerations
137
+
138
+ * **Memory Safety**: The scrypt algorithm requires significant memory, making it resistant to hardware-based attacks
139
+ * **Time-Memory Trade-off**: Higher memory requirements make it expensive to parallelize attacks
140
+ * **Parameter Selection**: Use `calibrate` to find optimal parameters for your system's performance requirements
141
+ * **Salt Generation**: Always use cryptographically secure random salts (handled automatically)
142
+
143
+ ## Performance Tuning
144
+
145
+ The scrypt parameters can be tuned based on your security and performance requirements:
146
+
147
+ ```ruby
148
+ # For high-security applications (slower)
149
+ password = SCrypt::Password.create("secret", max_time: 1.0, max_mem: 64 * 1024 * 1024)
150
+
151
+ # For faster authentication (less secure)
152
+ password = SCrypt::Password.create("secret", max_time: 0.1, max_mem: 8 * 1024 * 1024)
153
+
154
+ # Calibrate once and reuse parameters
155
+ SCrypt::Engine.calibrate!(max_time: 0.5)
156
+ # All subsequent Password.create calls will use these parameters
157
+ ```
158
+
159
+ ## Error Handling
160
+
161
+ The library raises specific exceptions for different error conditions:
162
+
163
+ ```ruby
164
+ begin
165
+ SCrypt::Password.new("invalid_hash_format")
166
+ rescue SCrypt::Errors::InvalidHash => e
167
+ puts "Invalid hash format: #{e.message}"
168
+ end
169
+
170
+ begin
171
+ SCrypt::Engine.hash_secret(nil, "salt")
172
+ rescue SCrypt::Errors::InvalidSecret => e
173
+ puts "Invalid secret: #{e.message}"
174
+ end
175
+ ```
176
+
177
+ ## Contributing
178
+
179
+ 1. Fork the repository
180
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
181
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
182
+ 4. Push to the branch (`git push origin my-new-feature`)
183
+ 5. Create a new Pull Request
184
+
185
+ ## Acknowledgments
186
+
187
+ ### Original scrypt Algorithm
188
+ - **Colin Percival** and **Tarsnap** for creating the scrypt key derivation function and providing the reference implementation
189
+ - The original scrypt paper: [Stronger Key Derivation via Sequential Memory-Hard Functions](http://www.tarsnap.com/scrypt.html)
190
+
191
+ ### Core Collaborators
192
+
193
+ - **Patrick Hogan** ([@pbhogan](https://github.com/pbhogan))
194
+ - **Stephen von Takach** ([@stakach](https://github.com/stakach))
195
+ - **Rene van Paassen** ([@repagh](https://github.com/repagh))
196
+ - **Johanns Gregorian** ([@johanns](https://github.com/johanns))
197
+
198
+ ### Special Thanks
199
+ - The Ruby community for testing and feedback
200
+ - Contributors who have submitted bug reports, feature requests, and patches
201
+ - The cryptography community for security reviews and recommendations
202
+
203
+ ## License
204
+
205
+ This project is licensed under the BSD-3-Clause License - see the [COPYING](COPYING) file for details.
data/Rakefile CHANGED
@@ -1,46 +1,60 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/setup'
2
4
  require 'bundler/gem_tasks'
5
+ require 'digest/sha2'
6
+
7
+ require 'ffi'
8
+ require 'ffi-compiler/compile_task'
3
9
 
10
+ require 'fileutils'
4
11
  require 'rake'
5
12
  require 'rake/clean'
13
+ require 'rdoc/task'
6
14
 
7
15
  require 'rspec/core/rake_task'
8
16
 
9
- require 'ffi'
10
- require 'ffi-compiler/compile_task'
11
-
12
- require 'digest/sha2'
13
- require './lib/scrypt/version'
14
-
15
17
  require 'rubygems'
16
18
  require 'rubygems/package_task'
17
19
 
18
- require 'rdoc/task'
20
+ require './lib/scrypt/version'
19
21
 
20
- task :default => [:clean, :compile_ffi, :spec]
22
+ task default: %i[clean compile_ffi spec]
21
23
 
22
- desc 'clean, make and run specs'
23
- task :spec do
24
- RSpec::Core::RakeTask.new
24
+ desc 'Run all specs'
25
+ RSpec::Core::RakeTask.new(:spec) do |t|
26
+ t.rspec_opts = ['--color', '--backtrace', '--format', 'documentation']
25
27
  end
26
28
 
27
- desc 'generate checksum'
29
+ desc 'Generate checksum for built gem'
28
30
  task :checksum do
29
31
  built_gem_path = "pkg/scrypt-#{SCrypt::VERSION}.gem"
32
+
33
+ unless File.exist?(built_gem_path)
34
+ puts "Gem file not found: #{built_gem_path}"
35
+ puts "Run 'rake build' first to create the gem."
36
+ exit 1
37
+ end
38
+
30
39
  checksum = Digest::SHA512.new.hexdigest(File.read(built_gem_path))
31
40
  checksum_path = "checksum/scrypt-#{SCrypt::VERSION}.gem.sha512"
32
- File.open(checksum_path, 'w' ) {|f| f.write(checksum) }
41
+
42
+ # Ensure checksum directory exists
43
+ FileUtils.mkdir_p(File.dirname(checksum_path))
44
+
45
+ File.write(checksum_path, checksum)
46
+ puts "Checksum written to: #{checksum_path}"
33
47
  end
34
48
 
35
- desc 'FFI compiler'
36
- namespace 'ffi-compiler' do
49
+ desc 'Compile FFI extension'
50
+ namespace :ffi_compiler do
37
51
  FFI::Compiler::CompileTask.new('ext/scrypt/scrypt_ext') do |t|
38
52
  target_cpu = RbConfig::CONFIG['target_cpu']
39
53
 
40
54
  t.cflags << '-Wall -std=c99'
41
- t.cflags << '-msse -msse2' if t.platform.arch.include? '86'
55
+ t.cflags << '-msse -msse2' if t.platform.arch.include?('86')
42
56
  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/
57
+ t.cflags << '-D_POSIX_C_SOURCE=200809L' if RbConfig::CONFIG['host_os'].downcase =~ /linux/
44
58
 
45
59
  if 1.size == 4 && target_cpu =~ /i386|x86_32/ && t.platform.mac?
46
60
  t.cflags << '-arch i386'
@@ -53,22 +67,23 @@ namespace 'ffi-compiler' do
53
67
  t.add_define 'WINDOWS_OS' if FFI::Platform.windows?
54
68
  end
55
69
  end
56
- task :compile_ffi => ['ffi-compiler:default']
70
+ task compile_ffi: ['ffi_compiler:default']
57
71
 
58
72
  CLEAN.include('ext/scrypt/*{.o,.log,.so,.bundle}')
59
73
  CLEAN.include('lib/**/*{.o,.log,.so,.bundle}')
60
74
 
61
- desc 'Generate RDoc'
62
- rd = Rake::RDocTask.new do |rdoc|
75
+ desc 'Generate RDoc documentation'
76
+ RDoc::Task.new(:rdoc) do |rdoc|
63
77
  rdoc.rdoc_dir = 'doc/rdoc'
64
- rdoc.options << '--title' << 'scrypt-ruby' << '--line-numbers' << '--inline-source' << '--main' << 'README'
78
+ rdoc.options << '--force-update'
79
+ rdoc.options << '-V'
80
+
65
81
  rdoc.template = ENV['TEMPLATE'] if ENV['TEMPLATE']
66
- rdoc.rdoc_files.include('COPYING', 'lib/**/*.rb')
67
82
  end
68
83
 
69
84
  desc 'Run all specs'
70
85
  RSpec::Core::RakeTask.new do |_t|
71
- rspec_opts = ['--colour', '--backtrace']
86
+ # Task automatically runs specs based on RSpec defaults
72
87
  end
73
88
 
74
89
  def gem_spec
@@ -80,4 +95,3 @@ Gem::PackageTask.new(gem_spec) do |pkg|
80
95
  pkg.need_tar = true
81
96
  pkg.package_dir = 'pkg'
82
97
  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'
data/ext/scrypt/warnp.c CHANGED
@@ -36,7 +36,13 @@ warnp_setprogname(const char * progname)
36
36
  p = progname + 1;
37
37
 
38
38
  /* Copy the name string. */
39
- name = strdup(p);
39
+ name = malloc(strlen(p) + 1);
40
+ if (name == NULL) {
41
+ /* No cleanup handler needs to be registered on failure. */
42
+ return;
43
+ }
44
+ strncpy(name, p, strlen(p) + 1);
45
+ name[strlen(p)] = '\0'; /* Ensure null termination */
40
46
 
41
47
  /* If we haven't already done so, register our exit handler. */
42
48
  if (initialized == 0) {
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'openssl'
5
+
6
+ module SCrypt
7
+ module Ext
8
+ # Bind the external functions
9
+ attach_function :sc_calibrate,
10
+ %i[size_t double double pointer],
11
+ :int,
12
+ blocking: true
13
+
14
+ attach_function :crypto_scrypt,
15
+ %i[pointer size_t pointer size_t uint64 uint32 uint32 pointer size_t],
16
+ :int,
17
+ blocking: true # Use blocking: true for CPU-intensive operations to avoid GIL issues
18
+ end
19
+
20
+ class Engine
21
+ # Regular expressions for validation
22
+ COST_PATTERN = /^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$$/.freeze
23
+ SALT_PATTERN = /^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{16,64}$/.freeze
24
+ HASH_PATTERN = /^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{16,64}\$[A-Za-z0-9]{32,1024}$/.freeze
25
+
26
+ # Constants for salt handling
27
+ OLD_STYLE_SALT_LENGTH = 40
28
+ SALT_MIN_LENGTH = 16
29
+
30
+ DEFAULTS = {
31
+ key_len: 32,
32
+ salt_size: 32,
33
+ max_mem: 16 * 1024 * 1024,
34
+ max_memfrac: 0.5,
35
+ max_time: 0.2,
36
+ cost: nil
37
+ }.freeze
38
+
39
+ # Class variable to store calibrated cost, separate from defaults
40
+ @calibrated_cost = nil
41
+
42
+ class << self
43
+ attr_accessor :calibrated_cost
44
+ end
45
+
46
+ class Calibration < FFI::Struct
47
+ layout :n, :uint64,
48
+ :r, :uint32,
49
+ :p, :uint32
50
+ end
51
+
52
+ class << self
53
+ def scrypt(secret, salt, *args)
54
+ case args.length
55
+ when 2
56
+ # args is [cost_string, key_len]
57
+ cost_string, key_len = args
58
+ cpu_cost, memory_cost, parallelization = parse_cost_string(cost_string)
59
+ __sc_crypt(secret, salt, cpu_cost, memory_cost, parallelization, key_len)
60
+ when 4
61
+ # args is [n, r, p, key_len]
62
+ cpu_cost, memory_cost, parallelization, key_len = args
63
+ __sc_crypt(secret, salt, cpu_cost, memory_cost, parallelization, key_len)
64
+ else
65
+ raise ArgumentError, 'invalid number of arguments (4 or 6)'
66
+ end
67
+ end
68
+
69
+ # Given a secret and a valid salt (see SCrypt::Engine.generate_salt) calculates an scrypt password hash.
70
+ def hash_secret(secret, salt, key_len = DEFAULTS[:key_len])
71
+ raise Errors::InvalidSecret, 'invalid secret' unless valid_secret?(secret)
72
+ raise Errors::InvalidSalt, 'invalid salt' unless valid_salt?(salt)
73
+
74
+ cost = autodetect_cost(salt)
75
+ salt_only = extract_salt_from_string(salt)
76
+
77
+ if old_style_hash?(salt_only)
78
+ generate_old_style_hash(secret, salt, cost)
79
+ else
80
+ generate_new_style_hash(secret, salt, salt_only, cost, key_len)
81
+ end
82
+ end
83
+
84
+ # Generates a random salt with a given computational cost. Uses a saved
85
+ # cost if SCrypt::Engine.calibrate! has been called.
86
+ #
87
+ # Options:
88
+ # <tt>:cost</tt> is a cost string returned by SCrypt::Engine.calibrate
89
+ def generate_salt(options = {})
90
+ options = DEFAULTS.merge(options)
91
+ cost = options[:cost] || @calibrated_cost || calibrate(options)
92
+ salt = OpenSSL::Random.random_bytes(options[:salt_size]).unpack('H*').first.rjust(SALT_MIN_LENGTH, '0')
93
+
94
+ salt = avoid_old_style_collision(salt)
95
+ cost + salt
96
+ end
97
+
98
+ # Returns true if +cost+ is a valid cost, false if not.
99
+ def valid_cost?(cost)
100
+ !COST_PATTERN.match(cost).nil?
101
+ end
102
+
103
+ # Returns true if +salt+ is a valid salt, false if not.
104
+ def valid_salt?(salt)
105
+ !SALT_PATTERN.match(salt).nil?
106
+ end
107
+
108
+ # Returns true if +secret+ is a valid secret, false if not.
109
+ def valid_secret?(secret)
110
+ secret.respond_to?(:to_s)
111
+ end
112
+
113
+ # Returns the cost value which will result in computation limits less than the given options.
114
+ #
115
+ # Options:
116
+ # <tt>:max_time</tt> specifies the maximum number of seconds the computation should take.
117
+ # <tt>:max_mem</tt> specifies the maximum number of bytes the computation should take.
118
+ # A value of 0 specifies no upper limit. The minimum is always 1 MB.
119
+ # <tt>:max_memfrac</tt> specifies the maximum memory in a fraction of available resources to use.
120
+ # Any value equal to 0 or greater than 0.5 will result in 0.5 being used.
121
+ #
122
+ # Example:
123
+ #
124
+ # # should take less than 200ms
125
+ # SCrypt::Engine.calibrate(:max_time => 0.2)
126
+ #
127
+ def calibrate(options = {})
128
+ options = DEFAULTS.merge(options)
129
+ '%x$%x$%x$' % __sc_calibrate(options[:max_mem], options[:max_memfrac], options[:max_time])
130
+ end
131
+
132
+ # Calls SCrypt::Engine.calibrate and saves the cost string for future calls to
133
+ # SCrypt::Engine.generate_salt.
134
+ def calibrate!(options = {})
135
+ @calibrated_cost = calibrate(options)
136
+ end
137
+
138
+ # Computes the memory use of the given +cost+
139
+ def memory_use(cost)
140
+ cpu_cost, memory_cost, parallelization = parse_cost_string(cost)
141
+ (128 * memory_cost * parallelization) + (256 * memory_cost) + (128 * memory_cost * cpu_cost)
142
+ end
143
+
144
+ # Autodetects the cost from the salt string.
145
+ def autodetect_cost(salt)
146
+ salt[/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$/]
147
+ end
148
+
149
+ private
150
+
151
+ # Extracts the salt portion from a salt string
152
+ def extract_salt_from_string(salt)
153
+ salt[/\$([A-Za-z0-9]{16,64})$/, 1]
154
+ end
155
+
156
+ # Checks if this is an old-style hash based on salt length
157
+ def old_style_hash?(salt_only)
158
+ salt_only.length == OLD_STYLE_SALT_LENGTH
159
+ end
160
+
161
+ # Generates old-style hash with SHA1
162
+ def generate_old_style_hash(secret, salt, cost)
163
+ "#{salt}$#{Digest::SHA1.hexdigest(scrypt(secret.to_s, salt, cost, 256))}"
164
+ end
165
+
166
+ # Generates new-style hash
167
+ def generate_new_style_hash(secret, salt, salt_only, cost, key_len)
168
+ processed_salt = [salt_only.sub(/^(00)+/, '')].pack('H*')
169
+ hash_bytes = scrypt(secret.to_s, processed_salt, cost, key_len)
170
+ "#{salt}$#{hash_bytes.unpack('H*').first.rjust(key_len * 2, '0')}"
171
+ end
172
+
173
+ # Avoids collision with old-style hash detection
174
+ def avoid_old_style_collision(salt)
175
+ if salt.length == OLD_STYLE_SALT_LENGTH
176
+ # If salt is 40 characters, the regexp will think that it is an old-style hash, so add a '0'.
177
+ "0#{salt}"
178
+ else
179
+ salt
180
+ end
181
+ end
182
+
183
+ # Parses a cost string into its component values
184
+ def parse_cost_string(cost_string)
185
+ cost_string.split('$').map { |component| component.to_i(16) }
186
+ end
187
+
188
+ def __sc_calibrate(max_mem, max_memfrac, max_time)
189
+ raise ArgumentError, 'max_mem must be non-negative' if max_mem.negative?
190
+ raise ArgumentError, 'max_memfrac must be between 0 and 1' unless (0..1).cover?(max_memfrac)
191
+ raise ArgumentError, 'max_time must be positive' if max_time <= 0
192
+
193
+ calibration = Calibration.new
194
+ ret_val = SCrypt::Ext.sc_calibrate(max_mem, max_memfrac, max_time, calibration)
195
+
196
+ raise "calibration error: return value #{ret_val}" unless ret_val.zero?
197
+
198
+ [calibration[:n], calibration[:r], calibration[:p]]
199
+ end
200
+
201
+ def __sc_crypt(secret, salt, cpu_cost, memory_cost, parallelization, key_len)
202
+ raise ArgumentError, 'secret cannot be nil' if secret.nil?
203
+ raise ArgumentError, 'salt cannot be nil' if salt.nil?
204
+ raise ArgumentError, 'key_len must be positive' if key_len <= 0
205
+ raise ArgumentError, 'cpu_cost must be positive' if cpu_cost <= 0
206
+ raise ArgumentError, 'memory_cost must be positive' if memory_cost <= 0
207
+ raise ArgumentError, 'parallelization must be positive' if parallelization <= 0
208
+
209
+ result = nil
210
+
211
+ FFI::MemoryPointer.new(:char, key_len) do |buffer|
212
+ ret_val = SCrypt::Ext.crypto_scrypt(
213
+ secret, secret.bytesize, salt, salt.bytesize,
214
+ cpu_cost, memory_cost, parallelization,
215
+ buffer, key_len
216
+ )
217
+
218
+ raise "scrypt error: return value #{ret_val}" unless ret_val.zero?
219
+
220
+ result = buffer.read_string(key_len)
221
+ end
222
+
223
+ result
224
+ end
225
+ end
226
+ end
227
+ 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