class SCrypt::Engine

Constants

DEFAULTS

Public Class Methods

calibrate(options = {}) click to toggle source
N  r p

“400$8$25$”

# File lib/scrypt-ruby.rb, line 115
def self.calibrate(options = {})
  options = DEFAULTS.merge(options)
  max_mem = options.delete :max_mem
  max_memfrac = options.delete :max_memfrac
  max_time = options.delete :max_time

  mem_limit = memtouse(max_mem, max_memfrac)
  ops_limit = [cpuperf * max_time, 32768].min
  debug "ops_limit #{ops_limit} ops"

  n, r, p = nil, 8, nil

  if ops_limit < mem_limit/32
    debug "pick based on CPU limit"
    # Set p = 1 and choose N based on the CPU limit.
    p = 1
    max_n = ops_limit / (r * 4)
    n = 1 << (1...63).find { |i| (1 << i) > max_n / 2 }
        else
    debug "pick based on memory limit"
    # Set N based on the memory limit. */
          max_n = mem_limit / (r * 128)
    n = 1 << (1..63).find { |i| (1 << i) > max_n / 2 }

    # Choose p based on the CPU limit. */
    max_rp = [(ops_limit / 4) / (1 << n), 0x3fffffff].min
    p = max_rp / r
  end
  debug "calibrated using: N #{n} r #{r} p #{p}"
  "#{n.to_s(16)}$#{r.to_s(16)}$#{p.to_s(16)}$"
end
calibrate!(options = {}) click to toggle source
N   r p

“4000$8$4$”

# File lib/scrypt-ruby.rb, line 105
def self.calibrate!(options = {})
  DEFAULTS[:cost] = calibrate(options)
end
debug(*args) click to toggle source
# File lib/scrypt-ruby.rb, line 109
def self.debug(*args)
  SCrypt.debug(*args)
end
generate_salt(options = {}) click to toggle source

“4000$8$4$c6d101522d3cb045”

# File lib/scrypt-ruby.rb, line 91
def self.generate_salt(options = {})
  options = DEFAULTS.merge(options)
  cost = options[:cost] || calibrate(options)
  salt_size = options.delete :salt_size
  salt = OpenSSL::Random.random_bytes(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

“400$8$26$b62e0f787a5fc373$0399ccd4fa26642d92741b17c366b7f6bd12ccea5214987af445d2bed97bc6a2”

# File lib/scrypt-ruby.rb, line 172
def self.hash_secret(secret, salt, key_len = DEFAULTS[:key_len])
  raise Errors::InvalidSalt, "invalid salt" unless valid_salt? salt
  raise Errors::InvalidSecret, "invalid secret" unless valid_secret? secret

   cost = cost_from_salt(salt)
   salt_only = salt_only(salt)

   if salt_only.length == 40
     hash_secret_old(secret, salt, cost)
   else
     salt + "$" + hash_secret_new(secret, salt_only, cost, key_len)
   end
end
hash_secret_new(secret, salt_only, cost, key_len) click to toggle source
# File lib/scrypt-ruby.rb, line 166
def self.hash_secret_new(secret, salt_only, cost, key_len)
  salt_only = [salt_only.sub(/^(00)+/, '')].pack('H*')
  scrypt(secret.to_s, salt_only, cost, key_len).unpack('H*').first.rjust(key_len * 2, '0')
end
hash_secret_old(secret, salt, cost) click to toggle source
# File lib/scrypt-ruby.rb, line 162
def self.hash_secret_old(secret, salt, cost)
  salt + "$" + Digest::SHA1.hexdigest(scrypt(secret.to_s, salt, cost, 256))
end
scrypt(plaintext_password, salt, *args) click to toggle source

@param plaintext_password String @param salt String @param args [String, key_len] or [n, r, p, key_len]

# File lib/scrypt-ruby.rb, line 75
def self.scrypt(plaintext_password, salt, *args)
  key_len = args.last
  n = r = p = nil
  case args.length
  when 2
    n, r, p = args[0].split('$').map{ |x| x.to_i(16) }
  when 4
    n, r, p = args
  else
    raise ArgumentError, 'only 4 or 6 arguments allowed'
  end
  raw_encrypted_password = com.lambdaworks.crypto.SCrypt.scrypt(plaintext_password.to_s.to_java_bytes, salt.to_java_bytes, n, r, p, key_len)
  String.from_java_bytes raw_encrypted_password
end
valid_cost?(cost) click to toggle source

@returns Boolean

# File lib/scrypt-ruby.rb, line 148
def self.valid_cost?(cost)
  !!cost.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$$/)
end
valid_salt?(salt) click to toggle source

@returns Boolean

# File lib/scrypt-ruby.rb, line 153
def self.valid_salt?(salt)
  !!salt.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{16,64}$/)
end
valid_secret?(secret) click to toggle source

@returns Boolean

# File lib/scrypt-ruby.rb, line 158
def self.valid_secret?(secret)
  secret.respond_to?(:to_s)
end

Private Class Methods

cost_from_salt(salt) click to toggle source
# File lib/scrypt-ruby.rb, line 188
def self.cost_from_salt(salt)
  salt[/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$/]
end
cpuperf() click to toggle source

Estimate how many scrypts / second

# File lib/scrypt-ruby.rb, line 197
def self.cpuperf
  iterations = 1000
  empty_array = "".to_java_bytes.freeze
  run = -> { iterations.times { com.lambdaworks.crypto.SCrypt.scrypt(empty_array, empty_array, 128, 1, 1, 1) } }
  # warm-up
  run.()
  loop do 
    time = Benchmark.realtime { |x| run.() }
    if time > 0.33
      per_sec = iterations/time
      debug "cpuperf #{per_sec} ops/s"
      return per_sec
    end
    iterations *= 10
  end
end
memtouse(max_mem, max_memfrac) click to toggle source
# File lib/scrypt-ruby.rb, line 215
def self.memtouse(max_mem, max_memfrac)
  max_memfrac = 0.5 if max_memfrac > 0.5 || max_memfrac == 0

  memlimit_min = java.lang.Runtime.getRuntime.freeMemory
  debug "free mem #{memlimit_min / 1048576} MiB"

  mem_avail = max_memfrac * memlimit_min
  mem_avail = [mem_avail, max_mem].min if max_mem > 0
  mem_avail = [mem_avail, 1024*1024].max
  debug "memtouse #{mem_avail / 1048576} MiB"
  mem_avail
end
salt_only(salt) click to toggle source
# File lib/scrypt-ruby.rb, line 192
def self.salt_only(salt)
  salt[/\$([A-Za-z0-9]{16,64})$/, 1]
end