SequenceHash.SequenceFunction (SequenceHash v0.1.0)

Copy Markdown View Source

SequenceFunction: the construction both SequenceHash and SequenceMAC are built on (spec section "SequenceFunction").

Given a hash function H with block size b and output length L, a key K (empty for SequenceHash), a customization string S, a function indicator F and inputs M_1 .. M_n:

SequenceFunction(H, K, S, F; M_1, ..., M_n) =
    H( K_O || HeaderO(b, F, S, K) || S' || EncodeMSBF(n) || EncodeMSBF(L) ||
       H( K_I || HeaderI(b, F, K) || Encode(M_1) || ... || Encode(M_n) ) )

where K_I = Derive(K, H, 0x55), K_O = Derive(K, H, 0xaa) and S' = Derive(S, H, 0x00).

How this module maps to the formula

  • new/3 absorbs K_I || HeaderI into the inner hash state and K_O into the outer hash state. The key itself is not kept, only its length.
  • add/2 absorbs Encode(M_i) into the inner state and counts n.
  • result/2 finishes the inner hash and feeds the outer hash. It works on copies, so the state can keep growing or be finished again with a different customization string.

:crypto hash states are persistent: :crypto.hash_update/2 returns a new state and leaves the old one valid. That is what makes this struct an ordinary immutable value.

Keys

new/3 enforces the key rules for each function indicator, so this module cannot build a SequenceMAC with a key the specification forbids: SequenceMAC (F = 1) keys must be at least 32 bytes, and SequenceHash (F = 2) has no key, so its key must be empty.

Secret material

For SequenceMAC, inner and outer have absorbed the derived key blocks, so the struct is as sensitive as the key. Its Inspect output omits them. Functions that receive a key check their arguments in the function body and raise errors that do not include them, so a bad call cannot copy the key into a crash report. The BEAM cannot reliably zeroize memory, so derived key blocks are not wiped (the specification says SHOULD); they are short-lived binaries that are reclaimed by garbage collection.

Most callers should use SequenceHash or SequenceHash.MAC instead of this module. It is public so that auditors can check each step against the specification and so the test suite can check the intermediate values in the C2SP/CCTV vectors.

Summary

Types

Function indicator F: 1 for SequenceMAC, 2 for SequenceHash.

t()

A SequenceFunction computation in progress.

Functions

Adds one input M_i as a separate, length-encoded item.

Adds every input in messages, in order. The result is the same as calling add/2 for each one.

Function indicator F_SEQHSH.

Function indicator F_SEQMAC.

Returns the inner hash H(K_I || HeaderI || Encode(M_1) || ...).

HeaderI(b, F, K): the inner header block.

Starts a computation for algorithm, function_id and key.

HeaderO(b, F, S, K): the outer header block.

Returns the final output for customizer.

Types

function_id()

@type function_id() :: 1 | 2

Function indicator F: 1 for SequenceMAC, 2 for SequenceHash.

t()

@type t() :: %SequenceHash.SequenceFunction{
  algorithm: SequenceHash.Algorithm.t(),
  count: non_neg_integer(),
  function_id: function_id(),
  inner: :crypto.hash_state(),
  key_length: non_neg_integer(),
  outer: :crypto.hash_state()
}

A SequenceFunction computation in progress.

Functions

add(state, message)

@spec add(t(), binary()) :: t()

Adds one input M_i as a separate, length-encoded item.

add_all(state, messages)

@spec add_all(t(), Enumerable.t(binary())) :: t()

Adds every input in messages, in order. The result is the same as calling add/2 for each one.

Inputs are passed to the hash in batches, so this is faster than add/2 for many small inputs. Raises ArgumentError if an input is not a binary.

f_seqhsh()

@spec f_seqhsh() :: 2

Function indicator F_SEQHSH.

f_seqmac()

@spec f_seqmac() :: 1

Function indicator F_SEQMAC.

inner_hash(sequence_function)

@spec inner_hash(t()) :: binary()

Returns the inner hash H(K_I || HeaderI || Encode(M_1) || ...).

It does not depend on the customization string, which is why the specification notes it can be reused across customizers.

inner_header(block_size, function_id, key_length)

@spec inner_header(pos_integer(), function_id(), non_neg_integer()) :: binary()

HeaderI(b, F, K): the inner header block.

iex> SequenceHash.SequenceFunction.inner_header(64, 2, 0) |> Base.encode16(case: :lower)
"5345514853485f490000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000"

new(algorithm, function_id, key)

@spec new(SequenceHash.Algorithm.t(), function_id(), binary()) :: t()

Starts a computation for algorithm, function_id and key.

Raises ArgumentError for an unsupported algorithm, a function indicator other than 1 or 2, a SequenceMAC key shorter than 32 bytes, or a non-empty SequenceHash key. The error never includes the key.

iex> SequenceHash.SequenceFunction.new(:sha256, 1, "too short")
** (ArgumentError) SequenceMAC keys must be at least 32 bytes

outer_header(block_size, function_id, customizer_length, key_length)

@spec outer_header(pos_integer(), function_id(), non_neg_integer(), non_neg_integer()) ::
  binary()

HeaderO(b, F, S, K): the outer header block.

It always differs from the inner header in its first eight bytes, so the inner and outer hashes never process the same byte sequence, even for SequenceHash where the key is empty.

iex> SequenceHash.SequenceFunction.outer_header(64, 1, 4, 32) |> Base.encode16(case: :lower)
"5345514853485f4f0000000000000000000000000000000100000000000000000000000000000004000000000000000000000000000000200000000000000000"

result(state, customizer)

@spec result(t(), binary()) :: binary()

Returns the final output for customizer.