class SCrypt::Engine

Constants

DEFAULTS

Public Class Methods

autodetect_cost(salt) click to toggle source

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
calibrate(options = {}) click to toggle source

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
calibrate!(options = {}) click to toggle source

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
generate_salt(options = {}) click to toggle source

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
hash_secret(secret, salt, key_len = DEFAULTS[:key_len]) click to toggle source

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
memory_use(cost) click to toggle source

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
scrypt(secret, salt, *args) click to toggle source
# 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
valid_cost?(cost) click to toggle source

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
valid_salt?(salt) click to toggle source

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
valid_secret?(secret) click to toggle source

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

__sc_calibrate(max_mem, max_memfrac, max_time) click to toggle source
# 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
__sc_crypt(secret, salt, n, r, p, key_len) click to toggle source
# 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