Skip to content

Commit

Permalink
Merge 0085869 into 0928cc0
Browse files Browse the repository at this point in the history
  • Loading branch information
fudanchii committed Jun 3, 2017
2 parents 0928cc0 + 0085869 commit a8dbb1f
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 24 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ Style/SpaceBeforeFirstArg:

Style/GlobalVars:
Enabled: false

Style/SafeNavigation:
Enabled: false
2 changes: 1 addition & 1 deletion lib/rbnacl/hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def self.sha512(data)
#
# @return [String] The Blake2b hash digest as raw bytes
def self.blake2b(data, options = {})
Blake2b.new(options).digest(data)
Blake2b.digest(data, options)
end
end
end
146 changes: 123 additions & 23 deletions lib/rbnacl/hash/blake2b.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,25 @@ class 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.
Expand All @@ -43,39 +56,126 @@ class Blake2b
#
# @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
Expand Down
70 changes: 70 additions & 0 deletions spec/rbnacl/hash/blake2b_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@
expect(RbNaCl::Hash.blake2b("")).to eq empty_string_hash
end

context "arbitrary length message API" do
let(:blake2b) { RbNaCl::Hash::Blake2b.new }

it "calculates the correct hash for a reference string" do
blake2b << reference_string
expect(blake2b.digest).to eq reference_string_hash
end

it "calculates the correct hash for an empty string" do
blake2b << ""
expect(blake2b.digest).to eq empty_string_hash
end

it "raise CryptoError when digest called without reset / message" do
expect { blake2b.digest }.to raise_error(RbNaCl::CryptoError)
end

it "calculates hash for empty string when digest called directly after reset" do
blake2b.reset
expect(blake2b.digest).to eq empty_string_hash
end
end

context "keyed" do
let(:reference_string) { vector :blake2b_keyed_message }
let(:reference_key) { vector :blake2b_key }
Expand All @@ -26,6 +49,23 @@
it "doesn't accept empty strings as a key" do
expect { RbNaCl::Hash.blake2b(reference_string, key: "") }.to raise_error(RbNaCl::LengthError)
end

context "arbitrary length message API" do
let(:blake2b) { RbNaCl::Hash::Blake2b.new(key: "") }
let(:blake2b_wk) { RbNaCl::Hash::Blake2b.new(key: reference_key) }

it "calculates keyed hashes correctly" do
blake2b_wk << reference_string
expect(blake2b_wk.digest).to eq reference_string_hash
end

it "doesn't accept empty strings as a key" do
expect do
blake2b << reference_string
blake2b.digest
end.to raise_error(RbNaCl::LengthError)
end
end
end

context "personalized" do
Expand All @@ -42,6 +82,21 @@
it "calculates personalised hashes correctly with a short personal" do
expect(RbNaCl::Hash.blake2b(reference_string, personal: reference_personal_short)).to eq reference_personal_short_hash
end

context "arbitrary length message API" do
let(:blake2b) { RbNaCl::Hash::Blake2b.new(personal: reference_personal) }
let(:blake2b_sh) { RbNaCl::Hash::Blake2b.new(personal: reference_personal_short) }

it "calculates personalised hashes correctly" do
blake2b << reference_string
expect(blake2b.digest).to eq reference_personal_hash
end

it "calculates personalised hashes correctly with a short personal" do
blake2b_sh << reference_string
expect(blake2b_sh.digest).to eq reference_personal_short_hash
end
end
end

context "salted" do
Expand All @@ -58,5 +113,20 @@
it "calculates saltised hashes correctly with a short salt" do
expect(RbNaCl::Hash.blake2b(reference_string, salt: reference_salt_short)).to eq reference_salt_short_hash
end

context "arbitrary length message API" do
let(:blake2b) { RbNaCl::Hash::Blake2b.new(salt: reference_salt) }
let(:blake2b_sh) { RbNaCl::Hash::Blake2b.new(salt: reference_salt_short) }

it "calculates saltised hashes correctly" do
blake2b << reference_string
expect(blake2b.digest).to eq reference_salt_hash
end

it "calculates saltised hashes correctly with a short salt" do
blake2b_sh << reference_string
expect(blake2b_sh.digest).to eq reference_salt_short_hash
end
end
end
end

0 comments on commit a8dbb1f

Please sign in to comment.