# encoding: binary # frozen_string_literal: true module RbNaCl module PasswordHash # The scrypt sequential memory hard password hashing function # # scrypt is a password hash (or password based KDF). That is to say, where # most hash functions are designed to be fast because hashing is often a # bottleneck, scrypt is slow by design, because it's trying to "strengthen" # the password by combining it with a random "salt" value then perform a # series of operation on the result which are slow enough to defeat # brute-force password cracking attempts. # # scrypt is similar to the bcrypt and pbkdf2 password hashes in that it's # designed to strengthen passwords, but includes a new design element # called "sequential memory hardness" which helps defeat attempts by # attackers to compensate for their lack of memory (since they're typically # on GPUs or FPGAs) with additional computation. class SCrypt extend Sodium sodium_type :pwhash sodium_primitive :scryptsalsa208sha256 sodium_constant :SALTBYTES sodium_constant :OPSLIMIT_SENSITIVE sodium_constant :MEMLIMIT_SENSITIVE sodium_function :scrypt, :crypto_pwhash_scryptsalsa208sha256, %i[pointer ulong_long pointer ulong_long pointer ulong_long size_t] # Create a new SCrypt password hash object # # @param [Integer] opslimit the CPU cost (e.g. 2**20) # @param [Integer] memlimit the memory cost (e.g. 2**24) # # @return [RbNaCl::PasswordHash::SCrypt] An SCrypt password hasher object def initialize(opslimit, memlimit, digest_size = 64) # TODO: sanity check these parameters @opslimit = opslimit @memlimit = memlimit # TODO: check digest size validity # raise LengthError, "digest size too short" if @digest_size < BYTES_MIN # raise LengthError, "digest size too long" if @digest_size > BYTES_MAX @digest_size = digest_size end # Calculate an scrypt digest for a given password and salt # # @param [String] password to be hashed # @param [String] salt to make the digest unique # # @return [String] scrypt digest of the string as raw bytes def digest(password, salt) digest = Util.zeros(@digest_size) salt = Util.check_string(salt, SALTBYTES, "salt") success = self.class.scrypt(digest, @digest_size, password, password.bytesize, salt, @opslimit, @memlimit) raise CryptoError, "scrypt failed!" unless success digest end end end end