class CryptCheckpass::PBKDF2

PBKDF2 is the most beloved algorithm by security professionals.

### Newhash:

You can use `crypto_newhash` to create a new password hash using PBKDF2:

“`ruby crypt_newhash(password, id: 'pbkdf2-sha1', rounds: 1024) “`

where:

- `password` is the raw binary password that you want to digest.

- `id` is  `"pbkdf2-{digest}"`.  You  can specify sha1  / sha256  / sha512.
  Unlike plain SHA1, PBKDF2 + SHA1  combination still has no known weakness
  as of writing so specifying pbkdf-sha1 should just suffice normally.

- `rounds` is for iteration rounds.

The generated password hash has following format.

### Format:

This algorithm does not have a standard hash format. Here we follow npm's @phc/pbkdf2.

“`ruby %r{

(?<id>   pbkdf2-[\w\d]+ ){0}
(?<i>    i=[1-9][0-9]*  ){0}
(?<salt> [a-zA-Z0-9+/]* ){0}
(?<csum> [a-zA-Z0-9+/]* ){0}

\A [$] \g<id>
   [$] \g<i>
   [$] \g<salt>
   [$] \g<csum>
\z

}x “`

### Other formats:

Python Passlib generates something different, in the same `$pbkdf2-{digest}$` id. Passlib's and @phc/pbkdf2's are distinguishable because Passlib does not follow PHC String Format.

@see en.wikipedia.org/wiki/PBKDF2 @see tools.ietf.org/html/rfc2898 @example

crypt_newhash 'password', id: 'pbkdf2-sha1'
# => "$pbkdf2-sha1$i=1024$a9b0ggwILmLgiAwV34bpzA$nJ+GYjlNDao8BJedGVc8UROXpcU"

@example

crypt_checkpass? 'password', '$pbkdf2-sha1$i=1024$a9b0ggwILmLgiAwV34bpzA$nJ+GYjlNDao8BJedGVc8UROXpcU'
# => true

Public Class Methods

checkpass?(pass, hash) click to toggle source

(see CryptCheckpass.checkpass?)

# File lib/crypt_checkpass/pbkdf2.rb, line 105
def self.checkpass? pass, hash
  require 'consttime_memequal'

  json     = phcdecode hash
  id       = json[:id]
  i        = json[:params]['i'].to_i
  salt     = json[:salt]
  expected = json[:hash]
  dklen    = expected.bytesize
  actual   = __derive_key id, i, salt, pass, dklen

  return consttime_memequal? expected, actual
end
newhash(pass, id: 'pbkdf2-sha1', rounds: 1024) click to toggle source

(see CryptCheckpass.newhash)

@param pass [String] raw binary password string. @param id [String] name of the algorithm @param rounds [Integer] iteration rounds

# File lib/crypt_checkpass/pbkdf2.rb, line 133
def self.newhash pass, id: 'pbkdf2-sha1', rounds: 1024
  salt = SecureRandom.random_bytes 16
  csum = __derive_key id, rounds, salt, pass
  return phcencode id, { i: rounds }, salt, csum
end
provide?(id) click to toggle source

(see CryptCheckpass.provide?)

# File lib/crypt_checkpass/pbkdf2.rb, line 120
def self.provide? id
  case id when 'pbkdf2-sha1', 'pbkdf2-sha256', 'pbkdf2-sha512' then
    return true
  else
    return false
  end
end
understand?(str) click to toggle source

(see CryptCheckpass.understand?)

# File lib/crypt_checkpass/pbkdf2.rb, line 89
def self.understand? str
  return match? str, %r{
    (?<id>   pbkdf2-sha(1|256|512) ){0}
    (?<i>    i=[1-9][0-9]*         ){0}
    (?<salt> [a-zA-Z0-9+/]*        ){0}
    (?<csum> [a-zA-Z0-9+/]*        ){0}

    \A [$] \g<id>
       [$] \g<i>
       [$] \g<salt>
       [$] \g<csum>
    \z
  }x
end

Private Class Methods

__default_dklen_for(digest) click to toggle source
# File lib/crypt_checkpass/pbkdf2.rb, line 154
def __default_dklen_for digest
  case digest
  when 'pbkdf2-sha1'   then return 20, 'sha1'
  when 'pbkdf2-sha256' then return 32, 'sha256'
  when 'pbkdf2-sha512' then return 64, 'sha512'
  else raise "NOTREACHED: %s", id
  end
end
__derive_key(id, iter, salt, pass, dklen = nil) click to toggle source
# File lib/crypt_checkpass/pbkdf2.rb, line 163
def __derive_key id, iter, salt, pass, dklen = nil
  __load_openssl

  n, d    = __default_dklen_for id
  dklen ||= n
  return OpenSSL::PKCS5.pbkdf2_hmac pass, salt, iter, dklen, d
end
__load_openssl() click to toggle source
# File lib/crypt_checkpass/pbkdf2.rb, line 145
def __load_openssl
  require 'openssl'
end