class SCrypt::Engine
Constants
- DEFAULTS
Public Class Methods
Autodetects the cost from the salt string.
# File lib/scrypt.rb, line 133 def self.autodetect_cost(salt) salt[/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$/] end
Returns the cost value which will result in computation limits less than the given options.
Options: :max_time
specifies the maximum number of seconds the
computation should take. :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. :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.
Example:
# should take less than 200ms SCrypt::Engine.calibrate(:max_time => 0.2)
# File lib/scrypt.rb, line 115 def self.calibrate(options = {}) options = DEFAULTS.merge(options) "%x$%x$%x$" % __sc_calibrate(options[:max_mem], options[:max_memfrac], options[:max_time]) end
Calls ::calibrate and saves the cost string for future calls to ::generate_salt.
# File lib/scrypt.rb, line 122 def self.calibrate!(options = {}) DEFAULTS[:cost] = calibrate(options) end
Generates a random salt with a given computational cost. Uses a saved cost if ::calibrate! has been called.
Options: :cost
is a cost string returned by ::calibrate
# File lib/scrypt.rb, line 77 def self.generate_salt(options = {}) options = DEFAULTS.merge(options) cost = options[:cost] || calibrate(options) salt = OpenSSL::Random.random_bytes(options[:salt_size]).unpack('H*').first.rjust(16,'0') if salt.length == 40 #If salt is 40 characters, the regexp will think that it is an old-style hash, so add a '0'. salt = '0' + salt end cost + salt end
Given a secret and a valid salt (see ::generate_salt) calculates an scrypt password hash.
# File lib/scrypt.rb, line 51 def self.hash_secret(secret, salt, key_len = DEFAULTS[:key_len]) if valid_secret?(secret) if valid_salt?(salt) cost = autodetect_cost(salt) salt_only = salt[/\$([A-Za-z0-9]{16,64})$/, 1] if salt_only.length == 40 # Old-style hash with 40-character salt salt + "$" + Digest::SHA1.hexdigest(scrypt(secret.to_s, salt, cost, 256)) else # New-style hash salt_only = [salt_only.sub(/^(00)+/, '')].pack('H*') salt + "$" + scrypt(secret.to_s, salt_only, cost, key_len).unpack('H*').first.rjust(key_len * 2, '0') end else raise Errors::InvalidSalt.new("invalid salt") end else raise Errors::InvalidSecret.new("invalid secret") end end
Computes the memory use of the given cost
# File lib/scrypt.rb, line 127 def self.memory_use(cost) n, r, p = cost.scanf("%x$%x$%x$") (128 * r * p) + (256 * r) + (128 * r * n); end
# File lib/scrypt.rb, line 34 def self.scrypt(secret, salt, *args) if args.length == 2 # args is [cost_string, key_len] n, r, p = args[0].split('$').map{ |x| x.to_i(16) } key_len = args[1] __sc_crypt(secret, salt, n, r, p, key_len) elsif args.length == 4 # args is [n, r, p, key_len] n, r, p = args[0, 3] key_len = args[3] __sc_crypt(secret, salt, n, r, p, key_len) else raise ArgumentError.new("invalid number of arguments (4 or 6)") end end
Returns true if cost
is a valid cost, false if not.
# File lib/scrypt.rb, line 89 def self.valid_cost?(cost) cost.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$$/) != nil end
Returns true if salt
is a valid salt, false if not.
# File lib/scrypt.rb, line 94 def self.valid_salt?(salt) salt.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{16,64}$/) != nil end
Returns true if secret
is a valid secret, false if not.
# File lib/scrypt.rb, line 99 def self.valid_secret?(secret) secret.respond_to?(:to_s) end
Private Class Methods
# File lib/scrypt.rb, line 145 def self.__sc_calibrate(max_mem, max_memfrac, max_time) result = nil calibration = Calibration.new retval = SCrypt::Ext.sc_calibrate(max_mem, max_memfrac, max_time, calibration) if retval == 0 result = [calibration[:n], calibration[:r], calibration[:p]] else raise "calibration error #{result}" end result end
# File lib/scrypt.rb, line 160 def self.__sc_crypt(secret, salt, n, r, p, key_len) result = nil FFI::MemoryPointer.new(:char, key_len) do |buffer| retval = SCrypt::Ext.crypto_scrypt( secret, secret.bytesize, salt, salt.bytesize, n, r, p, buffer, key_len ) if retval == 0 result = buffer.read_string(key_len) else raise "scrypt error #{retval}" end end result end