Skip to content

Generic hashing

Asmod4n edited this page Sep 9, 2017 · 2 revisions

Generic hashing

This API transforms an arbitrary-long input into a fixed length fingerprint, and offers a lot of flexibility regarding the key and output sizes.

It accepts any key size between 128 and 256 bits, and can produce a result of any size from 128 bits to 524,280 bits.

Single-part example without a key

context = "Examples"
message = "Arbitrary data to hash"

hash = Hydro::Hash.hash(Hydro::Hash::BYTES, message, context);

Single-part example with a key

context = "Examples"
message = "Arbitrary data to hash"

key = Hydro::Hash.keygen(Hydro::Hash::KEYBYTES)

hash = Hydro::Hash.hash(Hydro::Hash::BYTES, message, context, key)

Multi-part example with a key

context = "Examples"
message1 = "Arbitrary data to hash"
message2 = "is longer than expected"

key = Hydro::Hash.keygen(Hydro::Hash::KEYBYTES)
state = Hydro::Hash.new(context, key)
state.update(message1)
state << message2 # << is a alias for update
hash = state.final(Hydro::Hash::BYTES)

Purpose

This API computes a fixed-length fingerprint for an arbitrary long message.

Sample use cases:

  • File integrity checking
  • Creating unique identifiers to index arbitrary long data.

Usage

This API requires a context. Refer to the documentation on contexts for more information.

key = Hydro::Hash.keygen(key_len)

The Hydro::Hash.keygen function creates a secret key of size key_len suitable for use with the Hydro::Hash* API.

out = Hydro::Hash.hash(out_len, in, ctx, key = nil)

The Hydro::Hash.hash() function puts a fingerprint of the message in into out. The output size can be chosen by the application.

The minimum recommended output size is Hydro::Hash::BYTES. This size makes it practically impossible for two messages to produce the same fingerprint.

But for specific use cases, the size can be any value between Hydro::Hash::BYTES_MIN (included) and Hydro::Hash::BYTES_MAX (included).

key can be nil . In this case, a message will always have the same fingerprint, similar to the MD5 or SHA-1 functions for which Hydro::Hash.hash() is a faster and more secure alternative.

But a key can also be specified. A message will always have the same fingerprint for a given key, but different keys used to hash the same message are very likely to produce distinct fingerprints.

In particular, the key can be used to make sure that different applications generate different fingerprints even if they process the same data.

The recommended key size is Hydro::Hash::KEYBYTES bytes.

However, the key size can by any value between Hydro::Hash::KEYBYTES_MIN (included) and Hydro::Hash::KEYBYTES_MAX (included).

state = Hydro::Hash.new(ctx, key = nil)

Hydro::Hash#update(in)

out = Hydro::Hash#final(out_len)

The message doesn't have to be provided as a single chunk. The hash operation also supports a streaming API.

The Hydro::Hash.new() function initializes a state state with a key key (that can be nil), in order to eventually produce out_len bytes of output.

Each chunk of the complete message can then be sequentially processed by calling Hydro::Hash#update() and pointer to the chunk in.

The Hydro::Hash#final() function completes the operation and puts the final fingerprint into out as out_len bytes.

After Hydro::Hash#final() returns, the state should not be used any more.

This alternative API is especially useful to process very large files and data streams.

Constants

Hydro::Hash::BYTES=32
Hydro::Hash::BYTES_MAX=65535
Hydro::Hash::BYTES_MIN=16
Hydro::Hash::CONTEXTBYTES=8
Hydro::Hash::KEYBYTES=32
Hydro::Hash::KEYBYTES_MAX=32
Hydro::Hash::KEYBYTES_MIN=16

Notes

Unlike MD5, SHA-1 and SHA-256, this function is safe against hash length extension attacks.

However, this API is not suitable for hashing passwords.

Algorithm

The construction used for generic hashing is similar to the NIST SP 800-185 KMAC construction, leveraging the Gimli hash function instead of cSHAKE.

H(pad(str_enc("kmac") || str_enc(context)) || pad(str_enc(k)) ||
  msg || right_enc(msg_len) || 0x00)

We also define a variant that extends KMAC to include a 64 bit tweak, currently used for key derivation:

H(pad(str_enc("tmac") || str_enc(context)) || pad(str_enc(k)) ||
  pad(right_enc(tweak)) || msg || right_enc(msg_len) || 0x00)