constantine/constantine/mac/mac_hmac.nim
Mamy Ratsimbazafy 094445482b
Eip2333 (#202)
* HMAC-SHA256

* EIP2333

* activate EIP2333 tests and faster random test case generation
2022-08-16 12:07:57 +02:00

106 lines
3.5 KiB
Nim

# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../hashes,
../platforms/primitives
# HMAC: Keyed-Hashing for Message Authentication
# ----------------------------------------------
#
# https://datatracker.ietf.org/doc/html/rfc2104
#
# Test vectors:
# - https://datatracker.ietf.org/doc/html/rfc4231
# - https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program
# - http://csrc.nist.gov/groups/STM/cavp/documents/mac/hmactestvectors.zip
{.push raises: [].} # No exceptions
type HMAC*[H: CryptoHash] = object
inner: H
outer: H
func init*[H: CryptoHash, T: char|byte](ctx: var HMAC[H], secretKey: openArray[T]) =
## Initialize a HMAC-based Message Authentication Code
## with a pre-shared secret key
## between the parties that want to authenticate messages between each other.
##
## Keys should be at least the same size as the hash function output size.
##
## Keys need to be chosen at random (or using a cryptographically strong
## pseudo-random generator seeded with a random seed), and periodically
## refreshed.
var key{.noInit.}: array[H.internalBlockSize(), byte]
if secretKey.len <= key.len:
copy(key, 0, secretKey, 0, secretKey.len)
for i in secretKey.len ..< key.len:
key[i] = byte 0
else:
ctx.inner.init()
ctx.inner.update(secretKey)
ctx.inner.finish(cast[ptr array[32, byte]](key.addr)[])
for i in H.digestSize() ..< key.len:
key[i] = byte 0
# Spec: inner hash
for i in 0 ..< H.internalBlockSize():
key[i] = key[i] xor byte 0x36
ctx.inner.init()
ctx.inner.update(key)
# Spec: outer hash (by cancelling previous xor)
for i in 0 ..< H.internalBlockSize():
key[i] = key[i] xor (byte 0x36 xor byte 0x5C)
ctx.outer.init()
ctx.outer.update(key)
func update*[H: CryptoHash, T: char|byte](ctx: var HMAC[H], message: openArray[T]) =
## Append a message to a HMAC authentication context.
## for incremental HMAC computation.
ctx.inner.update(message)
func finish*[H: CryptoHash, T: char|byte, N: static int](ctx: var HMAC[H], tag: var array[N, T]) =
## Finalize a HMAC authentication
## and output an authentication tag to the `tag` buffer
##
## Output may be used truncated, with the leftmost bits are kept.
## It is recommended that the tag length is at least half the length of the hash output
## and at least 80-bits.
static: doAssert N == H.digestSize()
ctx.inner.finish(tag)
ctx.outer.update(tag)
ctx.outer.finish(tag)
func clear*[H: CryptoHash](ctx: var HMAC[H]) =
## Clear the context internal buffers
# TODO: ensure compiler cannot optimize the code away
ctx.inner.clear()
ctx.outer.clear()
func mac*[T: char|byte, H: CryptoHash, N: static int](
Hash: type HMAC[H],
tag: var array[N, byte],
message: openArray[T],
secretKey: openarray[T],
clearMem = false) =
## Produce an authentication tag from a message
## and a preshared unique non-reused secret key
static: doAssert N == H.digestSize()
var ctx {.noInit.}: HMAC[H]
ctx.init(secretKey)
ctx.update(message)
ctx.finish(tag)
if clearMem:
ctx.clear()