mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-22 05:34:59 -07:00
44758f2a60
The max digest size for the foreseeable future will be 512 bits, and the max block size is currently 1152 bits (SHA3-224). If people add more exotic hash algorithms without bumping the constants when required, tests will fail. The stream buffer will currently be 576 bytes, which is "fine" to just stick on the stack, and is a sensible multiple of the more common block size of 64 bytes.
163 lines
4.1 KiB
Odin
163 lines
4.1 KiB
Odin
/*
|
||
package hmac implements the HMAC MAC algorithm.
|
||
|
||
See:
|
||
- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf
|
||
*/
|
||
package hmac
|
||
|
||
import "core:crypto"
|
||
import "core:crypto/hash"
|
||
import "core:mem"
|
||
|
||
// sum will compute the HMAC with the specified algorithm and key
|
||
// over msg, and write the computed digest to dst. It requires that
|
||
// the dst buffer is the tag size.
|
||
sum :: proc(algorithm: hash.Algorithm, dst, msg, key: []byte) {
|
||
ctx: Context
|
||
|
||
init(&ctx, algorithm, key)
|
||
update(&ctx, msg)
|
||
final(&ctx, dst)
|
||
}
|
||
|
||
// verify will verify the HMAC tag computed with the specified algorithm
|
||
// and key over msg and return true iff the tag is valid. It requires
|
||
// that the tag is correctly sized.
|
||
verify :: proc(algorithm: hash.Algorithm, tag, msg, key: []byte) -> bool {
|
||
tag_buf: [hash.MAX_DIGEST_SIZE]byte
|
||
|
||
derived_tag := tag_buf[:hash.DIGEST_SIZES[algorithm]]
|
||
sum(algorithm, derived_tag, msg, key)
|
||
|
||
return crypto.compare_constant_time(derived_tag, tag) == 1
|
||
}
|
||
|
||
// Context is a concrete instantiation of HMAC with a specific hash
|
||
// algorithm.
|
||
Context :: struct {
|
||
_o_hash: hash.Context, // H(k ^ ipad) (not finalized)
|
||
_i_hash: hash.Context, // H(k ^ opad) (not finalized)
|
||
_tag_sz: int,
|
||
_is_initialized: bool,
|
||
}
|
||
|
||
// init initializes a Context with a specific hash Algorithm and key.
|
||
init :: proc(ctx: ^Context, algorithm: hash.Algorithm, key: []byte) {
|
||
if ctx._is_initialized {
|
||
reset(ctx)
|
||
}
|
||
|
||
_init_hashes(ctx, algorithm, key)
|
||
|
||
ctx._tag_sz = hash.DIGEST_SIZES[algorithm]
|
||
ctx._is_initialized = true
|
||
}
|
||
|
||
// update adds more data to the Context.
|
||
update :: proc(ctx: ^Context, data: []byte) {
|
||
assert(ctx._is_initialized)
|
||
|
||
hash.update(&ctx._i_hash, data)
|
||
}
|
||
|
||
// final finalizes the Context, writes the tag to dst, and calls
|
||
// reset on the Context.
|
||
final :: proc(ctx: ^Context, dst: []byte) {
|
||
assert(ctx._is_initialized)
|
||
|
||
defer (reset(ctx))
|
||
|
||
if len(dst) != ctx._tag_sz {
|
||
panic("crypto/hmac: invalid destination tag size")
|
||
}
|
||
|
||
hash.final(&ctx._i_hash, dst) // H((k ^ ipad) || text)
|
||
|
||
hash.update(&ctx._o_hash, dst) // H((k ^ opad) || H((k ^ ipad) || text))
|
||
hash.final(&ctx._o_hash, dst)
|
||
}
|
||
|
||
// reset sanitizes the Context. The Context must be re-initialized to
|
||
// be used again.
|
||
reset :: proc(ctx: ^Context) {
|
||
if !ctx._is_initialized {
|
||
return
|
||
}
|
||
|
||
hash.reset(&ctx._o_hash)
|
||
hash.reset(&ctx._i_hash)
|
||
ctx._tag_sz = 0
|
||
ctx._is_initialized = false
|
||
}
|
||
|
||
// algorithm returns the Algorithm used by a Context instance.
|
||
algorithm :: proc(ctx: ^Context) -> hash.Algorithm {
|
||
assert(ctx._is_initialized)
|
||
|
||
return hash.algorithm(&ctx._i_hash)
|
||
}
|
||
|
||
// tag_size returns the tag size of a Context instance in bytes.
|
||
tag_size :: proc(ctx: ^Context) -> int {
|
||
assert(ctx._is_initialized)
|
||
|
||
return ctx._tag_sz
|
||
}
|
||
|
||
@(private)
|
||
_I_PAD :: 0x36
|
||
_O_PAD :: 0x5c
|
||
|
||
@(private)
|
||
_init_hashes :: proc(ctx: ^Context, algorithm: hash.Algorithm, key: []byte) {
|
||
K0_buf: [hash.MAX_BLOCK_SIZE]byte
|
||
kPad_buf: [hash.MAX_BLOCK_SIZE]byte
|
||
|
||
kLen := len(key)
|
||
B := hash.BLOCK_SIZES[algorithm]
|
||
K0 := K0_buf[:B]
|
||
defer mem.zero_explicit(raw_data(K0), B)
|
||
|
||
switch {
|
||
case kLen == B, kLen < B:
|
||
// If the length of K = B: set K0 = K.
|
||
//
|
||
// If the length of K < B: append zeros to the end of K to
|
||
// create a B-byte string K0 (e.g., if K is 20 bytes in
|
||
// length and B = 64, then K will be appended with 44 zero
|
||
// bytes x’00’).
|
||
//
|
||
// K0 is zero-initialized, so the copy handles both cases.
|
||
copy(K0, key)
|
||
case kLen > B:
|
||
// If the length of K > B: hash K to obtain an L byte string,
|
||
// then append (B-L) zeros to create a B-byte string K0
|
||
// (i.e., K0 = H(K) || 00...00).
|
||
tmpCtx := &ctx._o_hash // Saves allocating a hash.Context.
|
||
hash.init(tmpCtx, algorithm)
|
||
hash.update(tmpCtx, key)
|
||
hash.final(tmpCtx, K0)
|
||
}
|
||
|
||
// Initialize the hashes, and write the padded keys:
|
||
// - ctx._i_hash -> H(K0 ^ ipad)
|
||
// - ctx._o_hash -> H(K0 ^ opad)
|
||
|
||
hash.init(&ctx._o_hash, algorithm)
|
||
hash.init(&ctx._i_hash, algorithm)
|
||
|
||
kPad := kPad_buf[:B]
|
||
defer mem.zero_explicit(raw_data(kPad), B)
|
||
|
||
for v, i in K0 {
|
||
kPad[i] = v ~ _I_PAD
|
||
}
|
||
hash.update(&ctx._i_hash, kPad)
|
||
|
||
for v, i in K0 {
|
||
kPad[i] = v ~ _O_PAD
|
||
}
|
||
hash.update(&ctx._o_hash, kPad)
|
||
}
|