lib/rbnacl/hash/blake2b.rb in rbnacl-4.0.2 vs lib/rbnacl/hash/blake2b.rb in rbnacl-5.0.0

- old
+ new

@@ -26,57 +26,157 @@ sodium_function :generichash_blake2b, :crypto_generichash_blake2b_salt_personal, [:pointer, :size_t, :pointer, :ulong_long, :pointer, :size_t, :pointer, :pointer] + sodium_function :generichash_blake2b_init, + :crypto_generichash_blake2b_init_salt_personal, + [:pointer, :pointer, :size_t, :size_t, :pointer, :pointer] + + sodium_function :generichash_blake2b_update, + :crypto_generichash_blake2b_update, + [:pointer, :pointer, :ulong_long] + + sodium_function :generichash_blake2b_final, + :crypto_generichash_blake2b_final, + [:pointer, :pointer, :size_t] + EMPTY_PERSONAL = ("\0" * PERSONALBYTES).freeze EMPTY_SALT = ("\0" * SALTBYTES).freeze - # Create a new Blake2b hash object + # Calculate a Blake2b digest # - # @param [Hash] opts Blake2b configuration + # @param [String] message Message to be hashed + # @param [Hash] options Blake2b configuration # @option opts [String] :key for Blake2b keyed mode # @option opts [Integer] :digest_size size of output digest in bytes # @option opts [String] :salt Provide a salt to support randomised hashing. # This is mixed into the parameters block to start the hashing. # @option opts [Personal] :personal Provide personalisation string to allow pinning a hash for a particular purpose. # This is mixed into the parameters block to start the hashing # # @raise [RbNaCl::LengthError] Invalid length specified for one or more options # - # @return [RbNaCl::Hash::Blake2b] A Blake2b hasher object - def initialize(opts = {}) - @key = opts.fetch(:key, nil) + # @return [String] Blake2b digest of the string as raw bytes + def self.digest(message, options) + opts = validate_opts(options) + digest = Util.zeros(opts[:digest_size]) + generichash_blake2b(digest, opts[:digest_size], message, message.bytesize, + opts[:key], opts[:key_size], opts[:salt], opts[:personal]) || + raise(CryptoError, "Hashing failed!") + digest + end - if @key - @key_size = @key.bytesize - raise LengthError, "key too short" if @key_size < KEYBYTES_MIN - raise LengthError, "key too long" if @key_size > KEYBYTES_MAX + # Validate and sanitize values for Blake2b configuration + # + # @param [Hash] options Blake2b configuration + # @option opts [String] :key for Blake2b keyed mode + # @option opts [Integer] :digest_size size of output digest in bytes + # @option opts [String] :salt Provide a salt to support randomised hashing. + # This is mixed into the parameters block to start the hashing. + # @option opts [Personal] :personal Provide personalisation string to allow pinning a hash for a particular purpose. + # This is mixed into the parameters block to start the hashing + # + # @raise [RbNaCl::LengthError] Invalid length specified for one or more options + # + # @return [Hash] opts Configuration hash with sanitized values + def self.validate_opts(opts) + key = opts.fetch(:key, nil) + if key + key_size = key.bytesize + raise LengthError, "key too short" if key_size < KEYBYTES_MIN + raise LengthError, "key too long" if key_size > KEYBYTES_MAX else - @key_size = 0 + key_size = 0 end + opts[:key_size] = key_size - @digest_size = opts.fetch(:digest_size, BYTES_MAX) - raise LengthError, "digest size too short" if @digest_size < BYTES_MIN - raise LengthError, "digest size too long" if @digest_size > BYTES_MAX + digest_size = opts.fetch(:digest_size, BYTES_MAX) + raise LengthError, "digest size too short" if digest_size < BYTES_MIN + raise LengthError, "digest size too long" if digest_size > BYTES_MAX + opts[:digest_size] = digest_size - @personal = opts.fetch(:personal, EMPTY_PERSONAL) - @personal = Util.zero_pad(PERSONALBYTES, @personal) + personal = opts.fetch(:personal, EMPTY_PERSONAL) + opts[:personal] = Util.zero_pad(PERSONALBYTES, personal) - @salt = opts.fetch(:salt, EMPTY_SALT) - @salt = Util.zero_pad(SALTBYTES, @salt) + salt = opts.fetch(:salt, EMPTY_SALT) + opts[:salt] = Util.zero_pad(SALTBYTES, salt) + opts end - # Calculate a Blake2b digest + private_class_method :validate_opts + + def self.new(opts = {}) + opts = validate_opts(opts) + super + end + + # Create a new Blake2b hash object # + # @param [Hash] opts Blake2b configuration + # @option opts [String] :key for Blake2b keyed mode + # @option opts [Integer] :digest_size size of output digest in bytes + # @option opts [String] :salt Provide a salt to support randomised hashing. + # This is mixed into the parameters block to start the hashing. + # @option opts [Personal] :personal Provide personalisation string to allow pinning a hash for a particular purpose. + # This is mixed into the parameters block to start the hashing + # + # @raise [RbNaCl::LengthError] Invalid length specified for one or more options + # + # @return [RbNaCl::Hash::Blake2b] A Blake2b hasher object + def initialize(opts = {}) + @key = opts[:key] + @key_size = opts[:key_size] + @digest_size = opts[:digest_size] + @personal = opts[:personal] + @salt = opts[:salt] + + @incycle = false + @instate = nil + end + + # Initialize state for Blake2b hash calculation, + # this will be called automatically from #update if needed + def reset + @instate.release if @instate + @instate = State.new + self.class.generichash_blake2b_init(@instate.pointer, @key, @key_size, @digest_size, @salt, @personal) || + raise(CryptoError, "Hash init failed!") + @incycle = true + @digest = nil + end + + # Reentrant version of Blake2b digest calculation method + # # @param [String] message Message to be hashed + def update(message) + reset unless @incycle + self.class.generichash_blake2b_update(@instate.pointer, message, message.bytesize) || + raise(CryptoError, "Hashing failed!") + end + alias << update + + # Finalize digest calculation, return cached digest if any # # @return [String] Blake2b digest of the string as raw bytes - def digest(message) - digest = Util.zeros(@digest_size) - self.class.generichash_blake2b(digest, @digest_size, message, message.bytesize, @key, @key_size, @salt, @personal) || - raise(CryptoError, "Hashing failed!") - digest + def digest + raise(CryptoError, "No message to hash yet!") unless @incycle + return @digest if @digest + @digest = Util.zeros(@digest_size) + self.class.generichash_blake2b_final(@instate.pointer, @digest, @digest_size) || + raise(CryptoError, "Hash finalization failed!") + @digest + end + + # The crypto_generichash_blake2b_state struct representation + # ref: jedisct1/libsodium/src/libsodium/include/sodium/crypto_generichash_blake2b.h#L23 + class State < FFI::Struct + layout :h, [:uint64, 8], + :t, [:uint64, 2], + :f, [:uint64, 2], + :buf, [:uint8, 2 * 128], + :buflen, :size_t, + :last_node, :uint8 end end end end