class CryptCheckpass::Scrypt

This class is to support RFC7914-related hash variants.

### Format:

Life gets extremely hard here because Ruby's `scrypt` gem does not follow the Modular Crypt Format. You cannot tell if a string is scrypt-generated or not by looking at its beginning.

“`ruby %r{

(?<N>    [0-9a-f]+ ){0}
(?<r>    [0-9a-f]+ ){0}
(?<p>    [0-9a-f]+ ){0}
(?<salt> [0-9a-f]+ ){0}
(?<csum> [0-9a-f]+ ){0}

\A     \g<N>
   [$] \g<r>
   [$] \g<p>
   [$] \g<salt>
   [$] \g<csum>
\z

}x “`

- `N` is the CPU/Memory cost parameter N ("costParameter").

- `r` is the block size parameter r ("blockSize").

- `p` is the parallelization parameter p ("parallelizationParameter").

- `salt` is the salt string.

- `csum` is the checksum string.

All of above fields are represented as hexadecimal numbers.

This is too different from other password hashs. To ease the situation we also follow extra format that is compatible with Python's Passlib and npm's @phc/scrypt generates.

“`ruby %r{

(?<id>   scrypt         ){0}
(?<ln>   ln=[1-9][0-9]* ){0}
(?<r>    r=[1-9][0-9]*  ){0}
(?<p>    p=[1-9][0-9]*  ){0}
(?<salt> [a-zA-Z0-9+/]* ){0}
(?<csum> [a-zA-Z0-9+/]* ){0}

\A [$] \g<id>
   [$] \g<ln>
   [,] \g<r>
   [,] \g<p>
   [$] \g<salt>
   [$] \g<csum>
\z

}x “`

### Other formats:

Seems there are no such thing like a standard way to encode scrypt-generated passwords. Lots of wild formats are seen. We could support them if they have actual usage and to be migrated to another format.

@see en.wikipedia.org/wiki/Scrypt @see tools.ietf.org/html/rfc7914 @see blog.ircmaxell.com/2014/03/why-i-dont-recommend-scrypt.html @example

crypt_newhash 'password', id: 'scrypt'
# => "$scrypt$ln=8,r=8,p=1$aL2uvFKrfoVkxAgy1j/Y4OAJ8D0p1yP/uqFg3UU8t64$/xZGQyALLQrKzaBRGwzGCw+FGgRqFwyCfZddC5qvZYA"

@example

crypt_checkpass? 'password', '$scrypt$ln=8,r=8,p=1$aL2uvFKrfoVkxAgy1j/Y4OAJ8D0p1yP/uqFg3UU8t64$/xZGQyALLQrKzaBRGwzGCw+FGgRqFwyCfZddC5qvZYA'
# => true

Public Class Methods

checkpass?(pass, hash) click to toggle source

(see CryptCheckpass.checkpass?)

# File lib/crypt_checkpass/scrypt.rb, line 124
def self.checkpass? pass, hash
  require 'scrypt'

  case hash
  when /\A\$scrypt\$/ then return checkpass_phc pass, hash
  else                     return checkpass_gem pass, hash
  end
end
newhash(pass, id: 'scrypt', ln: 8, r: 8, p: 1) click to toggle source

(see CryptCheckpass.newhash)

@param pass [String] raw binary password string. @param id [String] name of the algorithm (ignored) @param ln [Integer] cost parameter in log2. @param r [Integer] block size. @param p [Integer] parallelism parameter.

# File lib/crypt_checkpass/scrypt.rb, line 145
def self.newhash pass, id: 'scrypt', ln: 8, r: 8, p: 1
  require 'scrypt'

  salt = SecureRandom.random_bytes ::SCrypt::Engine::DEFAULTS[:salt_size]
  klen = ::SCrypt::Engine::DEFAULTS[:key_len]
  csum = ::SCrypt::Engine.scrypt pass, salt, 2 ** ln, r, p, klen
  return phcencode 'scrypt', { ln: ln, r: r, p: p }, salt, csum
end
provide?(id) click to toggle source

(see CryptCheckpass.provide?)

# File lib/crypt_checkpass/scrypt.rb, line 134
def self.provide? id
  return id == 'scrypt'
end
understand?(str) click to toggle source

(see CryptCheckpass.understand?)

# File lib/crypt_checkpass/scrypt.rb, line 119
def self.understand? str
  return match? str, understander
end

Private Class Methods

checkpass_gem(pass, hash) click to toggle source
# File lib/crypt_checkpass/scrypt.rb, line 171
def checkpass_gem pass, hash
  obj = ::SCrypt::Password.new hash
  return obj == pass
end
checkpass_phc(pass, hash) click to toggle source
# File lib/crypt_checkpass/scrypt.rb, line 159
def checkpass_phc pass, hash
  require 'consttime_memequal'

  json     = phcdecode hash
  ln, r, p = json[:params].values_at("ln", "r", "p").map(&:to_i)
  expected = json[:hash]
  salt     = json[:salt]
  klen     = ::SCrypt::Engine::DEFAULTS[:key_len]
  actual   = ::SCrypt::Engine.scrypt pass, salt, 2 ** ln, r, p, klen
  return consttime_memequal? actual, expected
end
understander() click to toggle source
# File lib/crypt_checkpass/scrypt.rb, line 176
def understander
  return %r{
    (?<n1>    [0-9a-f]+          ){0}
    (?<r1>    [0-9a-f]+          ){0}
    (?<p1>    [0-9a-f]+          ){0}
    (?<salt1> [0-9a-f]+          ){0}
    (?<csum1> [0-9a-f]+          ){0}

    (?<id2>   scrypt             ){0}
    (?<ln2>   ln=[1-9][0-9]*     ){0}
    (?<r2>    r=[1-9][0-9]*      ){0}
    (?<p2>    p=[1-9][0-9]*      ){0}
    (?<salt2> [a-zA-Z0-9+/]*     ){0}
    (?<csum2> [a-zA-Z0-9+/]*     ){0}

    \A (?:
           \g<n1>
       [$] \g<r1>
       [$] \g<p1>
       [$] \g<salt1>
       [$] \g<csum1>

    |
       [$] \g<id2>
       [$] \g<ln2>
       [,] \g<r2>
       [,] \g<p2>
       [$] \g<salt2>
       [$] \g<csum2>
    ) \z
  }x
end