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/3absorbsK_I || HeaderIinto the inner hash state andK_Ointo the outer hash state. The key itself is not kept, only its length.add/2absorbsEncode(M_i)into the inner state and countsn.result/2finishes 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.
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
@type function_id() :: 1 | 2
Function indicator F: 1 for SequenceMAC, 2 for SequenceHash.
@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
Adds one input M_i as a separate, length-encoded item.
@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.
@spec f_seqhsh() :: 2
Function indicator F_SEQHSH.
@spec f_seqmac() :: 1
Function indicator F_SEQMAC.
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.
@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"
@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
@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"
Returns the final output for customizer.