Class: Passlib::SCrypt

Inherits:
Password show all
Defined in:
lib/passlib/scrypt.rb

Overview

Handles scrypt password hashing via the scrypt gem.

Two hash formats are accepted on load:

  • Passlib MCF: $scrypt$ln=<ln>,r=<r>,p=<p>$<salt>$<checksum>

  • SCrypt gem: the native hex format produced by the scrypt gem (normalized to MCF on load)

New hashes are always produced in the passlib MCF format.

Examples:

hash = Passlib::SCrypt.create("hunter2", ln: 14)
hash.verify("hunter2")  # => true
hash.to_s               # => "$scrypt$ln=14,r=8,p=1$...$..."

Constant Summary

Constants included from Internal::DSL

Internal::DSL::Config

Instance Attribute Summary

Attributes inherited from Password

#config, #string

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Password

available?, #initialize, #inspect, load, #pretty_print, #verify

Methods included from Internal::DSL

#identifier

Constructor Details

This class inherits a constructor from Passlib::Password

Class Method Details

.create(secret, **options) ⇒ SCrypt

Creates a new scrypt hash.

Parameters:

  • secret (String)

    the plaintext password

Options Hash (**options):

  • :ln (Integer)

    CPU/memory cost as a base-2 log (default: 16, meaning N=65536), mutually exclusive with :n

  • :n (Integer)

    CPU/memory cost as an integer power of two, converted to :ln internally

  • :r (Integer)

    block size (default: 8)

  • :p (Integer)

    parallelization factor (default: 1)

  • :salt (String)

    custom salt as a binary string (default: 16 random bytes)

  • :key_len (Integer)

    derived key length in bytes (default: 32)

Returns:



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/passlib/scrypt.rb', line 30

class SCrypt < Password
  MCF_PATTERN  = /\A\$scrypt\$ln=(\d+),r=(\d+),p=(\d+)\$(.+)\$(.+)\z/
  RUBY_PATTERN = /\A([0-9a-f]+)\$([0-9a-f]+)\$([0-9a-f]+)\$([0-9a-f]{16,64})\$([0-9a-f]{32,1024})\Z/
  private_constant :MCF_PATTERN, :RUBY_PATTERN

  external "scrypt", "~> 3.1"
  options :ln, :n, :r, :p, :salt, :key_len
  register mcf: "scrypt", pattern: RUBY_PATTERN

  # @param secret [String] the plaintext password to re-hash
  # @return [SCrypt] a new instance hashed with the same salt and parameters
  def create_comparable(secret)
    self.class.create(secret, salt: @salt, ln: @ln, r: @r, p: @p, key_len: @key_len)
  end

  def upgrade?
    target_ln = config.n ? Math.log2(config.n).to_i : (config.ln || 16)
    @ln != target_ln
  end

  private

  def create(secret)
    @ln       ||= config.n ? Math.log2(config.n).to_i : config.ln || 16
    @r        ||= config.r       || 8
    @p        ||= config.p       || 1
    @salt     ||= config.salt    || Internal.random_bytes(16)
    @key_len  ||= config.key_len || 32
    @checksum ||= ::SCrypt::Engine.scrypt(secret, @salt, 2 ** @ln, @r, @p, @key_len)
    "$scrypt$ln=#{@ln},r=#{@r},p=#{@p}$#{Internal.encode64(@salt)}$#{Internal.encode64(@checksum)}"
  end

  def load(string)
    case string
    when MCF_PATTERN
      # passlib format
      match     = Regexp.last_match
      @ln       = match[1].to_i
      @r        = match[2].to_i
      @p        = match[3].to_i
      @salt     = Internal.decode64(match[4])
      @checksum = Internal.decode64(match[5])
      @key_len  = @checksum.length
      string
    when RUBY_PATTERN
      # scrypt gem format
      match     = Regexp.last_match
      @ln       = Math.log2(match[1].to_i(16)).to_i
      @r        = match[2].to_i(16)
      @p        = match[3].to_i(16)
      @salt     = [match[4].sub(/^(00)+/, '')].pack('H*')
      @checksum = [match[5].sub(/^(00)+/, '')].pack('H*')
      @key_len  = @checksum.length
      create(nil)
    else
      raise UnknownHashFormat, "invalid scrypt hash format"
    end
  end
end

Instance Method Details

#create_comparable(secret) ⇒ SCrypt

Returns a new instance hashed with the same salt and parameters.

Parameters:

  • secret (String)

    the plaintext password to re-hash

Returns:

  • (SCrypt)

    a new instance hashed with the same salt and parameters



41
42
43
# File 'lib/passlib/scrypt.rb', line 41

def create_comparable(secret)
  self.class.create(secret, salt: @salt, ln: @ln, r: @r, p: @p, key_len: @key_len)
end

#upgrade?Boolean

Returns:

  • (Boolean)


45
46
47
48
# File 'lib/passlib/scrypt.rb', line 45

def upgrade?
  target_ln = config.n ? Math.log2(config.n).to_i : (config.ln || 16)
  @ln != target_ln
end