9   PBKDF2_ITERATIONS = 10000
 
  10   DIGEST_ALGORITHM = "sha512".freeze
 
  12   def self.create(password)
 
  13     salt = SecureRandom.base64(SALT_BYTE_SIZE)
 
  14     hash = self.hash(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE, DIGEST_ALGORITHM)
 
  15     [hash, [DIGEST_ALGORITHM, PBKDF2_ITERATIONS, salt].join("!")]
 
  18   def self.check(hash, salt, candidate)
 
  20       candidate = Digest::MD5.hexdigest(candidate)
 
  21     elsif salt.include?("!")
 
  22       algorithm, iterations, salt = salt.split("!")
 
  23       size = Base64.strict_decode64(hash).length
 
  24       candidate = self.hash(candidate, salt, iterations.to_i, size, algorithm)
 
  26       candidate = Digest::MD5.hexdigest(salt + candidate)
 
  32   def self.upgrade?(hash, salt)
 
  35     elsif salt.include?("!")
 
  36       algorithm, iterations, salt = salt.split("!")
 
  37       return true if Base64.strict_decode64(salt).length != SALT_BYTE_SIZE
 
  38       return true if Base64.strict_decode64(hash).length != HASH_BYTE_SIZE
 
  39       return true if iterations.to_i != PBKDF2_ITERATIONS
 
  40       return true if algorithm != DIGEST_ALGORITHM
 
  48   def self.hash(password, salt, iterations, size, algorithm)
 
  49     digest = OpenSSL::Digest.new(algorithm)
 
  50     pbkdf2 = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, size, digest)
 
  51     Base64.strict_encode64(pbkdf2)