mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-05 23:28:48 +00:00
core:crypto/hash: Add a generic higher level hash interface
There is a lot of code duplicated in convenience methods in each hash implementation, and having a generic hash type makes implementing higher-level constructs such as HMAC significantly easier down the road.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
package hash provides a generic interface to the supported hash algorithms.
|
||||
|
||||
A high-level convenience procedure group `hash` is provided to easily
|
||||
accomplish common tasks.
|
||||
- `hash_string` - Hash a given string and return the digest.
|
||||
- `hash_bytes` - Hash a given byte slice and return the digest.
|
||||
- `hash_string_to_buffer` - Hash a given string and put the digest in
|
||||
the third parameter. It requires that the destination buffer
|
||||
is at least as big as the digest size.
|
||||
- `hash_bytes_to_buffer` - Hash a given string and put the computed
|
||||
digest in the third parameter. It requires that the destination
|
||||
buffer is at least as big as the digest size.
|
||||
- `hash_stream` - Incrementally fully consume a `io.Stream`, and return
|
||||
the computed digest.
|
||||
- `hash_file` - Takes a file handle and returns the computed digest.
|
||||
A third optional boolean parameter controls if the file is streamed
|
||||
(default), or or read at once.
|
||||
|
||||
```odin
|
||||
package hash_example
|
||||
|
||||
import "core:crypto/hash"
|
||||
|
||||
main :: proc() {
|
||||
input := "Feed the fire."
|
||||
|
||||
// Compute the digest, using the high level API.
|
||||
returned_digest := hash.hash(hash.Algorithm.SHA512_256, input)
|
||||
defer delete(returned_digest)
|
||||
|
||||
// Variant that takes a destination buffer, instead of returning
|
||||
// the digest.
|
||||
digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.BLAKE2B]) // @note: Destination buffer has to be at least as big as the digest size of the hash.
|
||||
defer delete(digest)
|
||||
hash.hash(hash.Algorithm.BLAKE2B, input, digest)
|
||||
}
|
||||
```
|
||||
|
||||
A generic low level API is provided supporting the init/update/final interface
|
||||
that is typical with cryptographic hash function implementations.
|
||||
|
||||
```odin
|
||||
package hash_example
|
||||
|
||||
import "core:crypto/hash"
|
||||
|
||||
main :: proc() {
|
||||
input := "Let the cinders burn."
|
||||
|
||||
// Compute the digest, using the low level API.
|
||||
ctx: hash.Context
|
||||
digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.SHA3_512])
|
||||
defer delete(digest)
|
||||
|
||||
hash.init(&ctx, hash.Algorithm.SHA3_512)
|
||||
hash.update(&ctx, transmute([]byte)input)
|
||||
hash.final(&ctx, digest)
|
||||
}
|
||||
```
|
||||
*/
|
||||
package crypto_hash
|
||||
@@ -0,0 +1,118 @@
|
||||
package crypto_hash
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
*/
|
||||
|
||||
import "core:io"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
|
||||
// hash_bytes will hash the given input and return the computed digest
|
||||
// in a newly allocated slice.
|
||||
hash_string :: proc(algorithm: Algorithm, data: string, allocator := context.allocator) -> []byte {
|
||||
return hash_bytes(algorithm, transmute([]byte)(data), allocator)
|
||||
}
|
||||
|
||||
// hash_bytes will hash the given input and return the computed digest
|
||||
// in a newly allocated slice.
|
||||
hash_bytes :: proc(algorithm: Algorithm, data: []byte, allocator := context.allocator) -> []byte {
|
||||
dst := make([]byte, DIGEST_SIZES[algorithm], allocator)
|
||||
hash_bytes_to_buffer(algorithm, data, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
// hash_string_to_buffer will hash the given input and assign the
|
||||
// computed digest to the third parameter. It requires that the
|
||||
// destination buffer is at least as big as the digest size.
|
||||
hash_string_to_buffer :: proc(algorithm: Algorithm, data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer(algorithm, transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer will hash the given input and write the
|
||||
// computed digest into the third parameter. It requires that the
|
||||
// destination buffer is at least as big as the digest size.
|
||||
hash_bytes_to_buffer :: proc(algorithm: Algorithm, data, hash: []byte) {
|
||||
ctx: Context
|
||||
|
||||
init(&ctx, algorithm, context.temp_allocator)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream will incrementally fully consume a stream, and return the
|
||||
// computed digest in a newly allocated slice.
|
||||
hash_stream :: proc(
|
||||
algorithm: Algorithm,
|
||||
s: io.Stream,
|
||||
allocator := context.allocator,
|
||||
) -> (
|
||||
[]byte,
|
||||
io.Error,
|
||||
) {
|
||||
ctx: Context
|
||||
|
||||
init(&ctx, algorithm, context.temp_allocator)
|
||||
|
||||
_BUFFER_SIZE :: 512
|
||||
buf := make([]byte, _BUFFER_SIZE, context.temp_allocator)
|
||||
defer mem.zero_explicit(raw_data(buf), _BUFFER_SIZE)
|
||||
defer delete(buf)
|
||||
|
||||
loop: for {
|
||||
n, err := io.read(s, buf)
|
||||
if n > 0 {
|
||||
// XXX/yawning: Can io.read return n > 0 and EOF?
|
||||
update(&ctx, buf[:n])
|
||||
}
|
||||
#partial switch err {
|
||||
case .None:
|
||||
case .EOF:
|
||||
break loop
|
||||
case:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dst := make([]byte, DIGEST_SIZES[algorithm], allocator)
|
||||
final(&ctx, dst)
|
||||
|
||||
return dst, io.Error.None
|
||||
}
|
||||
|
||||
// hash_file will read the file provided by the given handle and return the
|
||||
// computed digest in a newly allocated slice.
|
||||
hash_file :: proc(
|
||||
algorithm: Algorithm,
|
||||
hd: os.Handle,
|
||||
load_at_once := false,
|
||||
allocator := context.allocator,
|
||||
) -> (
|
||||
[]byte,
|
||||
io.Error,
|
||||
) {
|
||||
if !load_at_once {
|
||||
return hash_stream(algorithm, os.stream_from_handle(hd), allocator)
|
||||
}
|
||||
|
||||
buf, ok := os.read_entire_file(hd, allocator)
|
||||
if !ok {
|
||||
return nil, io.Error.Unknown
|
||||
}
|
||||
defer delete(buf)
|
||||
|
||||
return hash_bytes(algorithm, buf, allocator), io.Error.None
|
||||
}
|
||||
|
||||
hash :: proc {
|
||||
hash_stream,
|
||||
hash_file,
|
||||
hash_bytes,
|
||||
hash_string,
|
||||
hash_bytes_to_buffer,
|
||||
hash_string_to_buffer,
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
package crypto_hash
|
||||
|
||||
import "core:crypto/blake2b"
|
||||
import "core:crypto/blake2s"
|
||||
import "core:crypto/sha2"
|
||||
import "core:crypto/sha3"
|
||||
import "core:crypto/shake"
|
||||
import "core:crypto/sm3"
|
||||
import "core:crypto/legacy/keccak"
|
||||
import "core:crypto/legacy/md5"
|
||||
import "core:crypto/legacy/sha1"
|
||||
|
||||
import "core:mem"
|
||||
|
||||
// Algorithm is the algorithm identifier associated with a given Context.
|
||||
Algorithm :: enum {
|
||||
Invalid,
|
||||
BLAKE2B,
|
||||
BLAKE2S,
|
||||
SHA224,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512,
|
||||
SHA512_256,
|
||||
SHA3_224,
|
||||
SHA3_256,
|
||||
SHA3_384,
|
||||
SHA3_512,
|
||||
SHAKE_128,
|
||||
SHAKE_256,
|
||||
SM3,
|
||||
Legacy_KECCAK_224,
|
||||
Legacy_KECCAK_256,
|
||||
Legacy_KECCAK_384,
|
||||
Legacy_KECCAK_512,
|
||||
Insecure_MD5,
|
||||
Insecure_SHA1,
|
||||
}
|
||||
|
||||
// ALGORITHM_NAMES is the Algorithm to algorithm name string.
|
||||
ALGORITHM_NAMES := [Algorithm]string {
|
||||
.Invalid = "Invalid",
|
||||
.BLAKE2B = "BLAKE2b",
|
||||
.BLAKE2S = "BLAKE2s",
|
||||
.SHA224 = "SHA-224",
|
||||
.SHA256 = "SHA-256",
|
||||
.SHA384 = "SHA-384",
|
||||
.SHA512 = "SHA-512",
|
||||
.SHA512_256 = "SHA-512/256",
|
||||
.SHA3_224 = "SHA3-224",
|
||||
.SHA3_256 = "SHA3-256",
|
||||
.SHA3_384 = "SHA3-384",
|
||||
.SHA3_512 = "SHA3-512",
|
||||
.SHAKE_128 = "SHAKE-128",
|
||||
.SHAKE_256 = "SHAKE-256",
|
||||
.SM3 = "SM3",
|
||||
.Legacy_KECCAK_224 = "Keccak-224",
|
||||
.Legacy_KECCAK_256 = "Keccak-256",
|
||||
.Legacy_KECCAK_384 = "Keccak-384",
|
||||
.Legacy_KECCAK_512 = "Keccak-512",
|
||||
.Insecure_MD5 = "MD5",
|
||||
.Insecure_SHA1 = "SHA-1",
|
||||
}
|
||||
|
||||
// DIGEST_SIZES is the Algorithm to digest size.
|
||||
DIGEST_SIZES := [Algorithm]int {
|
||||
.Invalid = 0,
|
||||
.BLAKE2B = blake2b.DIGEST_SIZE,
|
||||
.BLAKE2S = blake2s.DIGEST_SIZE,
|
||||
.SHA224 = sha2.DIGEST_SIZE_224,
|
||||
.SHA256 = sha2.DIGEST_SIZE_256,
|
||||
.SHA384 = sha2.DIGEST_SIZE_384,
|
||||
.SHA512 = sha2.DIGEST_SIZE_512,
|
||||
.SHA512_256 = sha2.DIGEST_SIZE_512_256,
|
||||
.SHA3_224 = sha3.DIGEST_SIZE_224,
|
||||
.SHA3_256 = sha3.DIGEST_SIZE_256,
|
||||
.SHA3_384 = sha3.DIGEST_SIZE_384,
|
||||
.SHA3_512 = sha3.DIGEST_SIZE_512,
|
||||
.SHAKE_128 = shake.DIGEST_SIZE_128,
|
||||
.SHAKE_256 = shake.DIGEST_SIZE_256,
|
||||
.SM3 = sm3.DIGEST_SIZE,
|
||||
.Legacy_KECCAK_224 = keccak.DIGEST_SIZE_224,
|
||||
.Legacy_KECCAK_256 = keccak.DIGEST_SIZE_256,
|
||||
.Legacy_KECCAK_384 = keccak.DIGEST_SIZE_384,
|
||||
.Legacy_KECCAK_512 = keccak.DIGEST_SIZE_512,
|
||||
.Insecure_MD5 = md5.DIGEST_SIZE,
|
||||
.Insecure_SHA1 = sha1.DIGEST_SIZE,
|
||||
}
|
||||
|
||||
// Context is a concrete instantiation of a specific hash algorithm.
|
||||
Context :: struct {
|
||||
_algo: Algorithm,
|
||||
_impl: union {
|
||||
^blake2b.Context,
|
||||
^blake2s.Context,
|
||||
^sha2.Context_256,
|
||||
^sha2.Context_512,
|
||||
^sha3.Context,
|
||||
^shake.Context,
|
||||
^sm3.Context,
|
||||
^keccak.Context,
|
||||
^md5.Context,
|
||||
^sha1.Context,
|
||||
},
|
||||
_allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
// init initializes a Context with a specific hash Algorithm.
|
||||
//
|
||||
// Warning: Internal state is allocated, and resources must be freed
|
||||
// either implicitly via a call to final, or explicitly via calling reset.
|
||||
init :: proc(ctx: ^Context, algorithm: Algorithm, allocator := context.allocator) {
|
||||
if ctx._impl != nil {
|
||||
reset(ctx)
|
||||
}
|
||||
|
||||
switch algorithm {
|
||||
case .BLAKE2B:
|
||||
impl := new(blake2b.Context, allocator)
|
||||
blake2b.init(impl)
|
||||
ctx._impl = impl
|
||||
case .BLAKE2S:
|
||||
impl := new(blake2s.Context, allocator)
|
||||
blake2s.init(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA224:
|
||||
impl := new(sha2.Context_256, allocator)
|
||||
sha2.init_224(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA256:
|
||||
impl := new(sha2.Context_256, allocator)
|
||||
sha2.init_256(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA384:
|
||||
impl := new(sha2.Context_512, allocator)
|
||||
sha2.init_384(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA512:
|
||||
impl := new(sha2.Context_512, allocator)
|
||||
sha2.init_512(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA512_256:
|
||||
impl := new(sha2.Context_512, allocator)
|
||||
sha2.init_512_256(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA3_224:
|
||||
impl := new(sha3.Context, allocator)
|
||||
sha3.init_224(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA3_256:
|
||||
impl := new(sha3.Context, allocator)
|
||||
sha3.init_256(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA3_384:
|
||||
impl := new(sha3.Context, allocator)
|
||||
sha3.init_384(impl)
|
||||
ctx._impl = impl
|
||||
case .SHA3_512:
|
||||
impl := new(sha3.Context, allocator)
|
||||
sha3.init_512(impl)
|
||||
ctx._impl = impl
|
||||
case .SHAKE_128:
|
||||
impl := new(shake.Context, allocator)
|
||||
shake.init_128(impl)
|
||||
ctx._impl = impl
|
||||
case .SHAKE_256:
|
||||
impl := new(shake.Context, allocator)
|
||||
shake.init_256(impl)
|
||||
ctx._impl = impl
|
||||
case .SM3:
|
||||
impl := new(sm3.Context, allocator)
|
||||
sm3.init(impl)
|
||||
ctx._impl = impl
|
||||
case .Legacy_KECCAK_224:
|
||||
impl := new(keccak.Context, allocator)
|
||||
keccak.init_224(impl)
|
||||
ctx._impl = impl
|
||||
case .Legacy_KECCAK_256:
|
||||
impl := new(keccak.Context, allocator)
|
||||
keccak.init_256(impl)
|
||||
ctx._impl = impl
|
||||
case .Legacy_KECCAK_384:
|
||||
impl := new(keccak.Context, allocator)
|
||||
keccak.init_384(impl)
|
||||
ctx._impl = impl
|
||||
case .Legacy_KECCAK_512:
|
||||
impl := new(keccak.Context, allocator)
|
||||
keccak.init_512(impl)
|
||||
ctx._impl = impl
|
||||
case .Insecure_MD5:
|
||||
impl := new(md5.Context, allocator)
|
||||
md5.init(impl)
|
||||
ctx._impl = impl
|
||||
case .Insecure_SHA1:
|
||||
impl := new(sha1.Context, allocator)
|
||||
sha1.init(impl)
|
||||
ctx._impl = impl
|
||||
case .Invalid:
|
||||
panic("crypto/hash: uninitialized algorithm")
|
||||
case:
|
||||
panic("crypto/hash: invalid algorithm")
|
||||
}
|
||||
|
||||
ctx._algo = algorithm
|
||||
ctx._allocator = allocator
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
switch impl in ctx._impl {
|
||||
case ^blake2b.Context:
|
||||
blake2b.update(impl, data)
|
||||
case ^blake2s.Context:
|
||||
blake2s.update(impl, data)
|
||||
case ^sha2.Context_256:
|
||||
sha2.update(impl, data)
|
||||
case ^sha2.Context_512:
|
||||
sha2.update(impl, data)
|
||||
case ^sha3.Context:
|
||||
sha3.update(impl, data)
|
||||
case ^shake.Context:
|
||||
shake.update(impl, data)
|
||||
case ^sm3.Context:
|
||||
sm3.update(impl, data)
|
||||
case ^keccak.Context:
|
||||
keccak.update(impl, data)
|
||||
case ^md5.Context:
|
||||
md5.update(impl, data)
|
||||
case ^sha1.Context:
|
||||
sha1.update(impl, data)
|
||||
case:
|
||||
panic("crypto/hash: uninitialized algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
// reset on the Context.
|
||||
//
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
switch impl in ctx._impl {
|
||||
case ^blake2b.Context:
|
||||
blake2b.final(impl, hash, finalize_clone)
|
||||
case ^blake2s.Context:
|
||||
blake2s.final(impl, hash, finalize_clone)
|
||||
case ^sha2.Context_256:
|
||||
sha2.final(impl, hash, finalize_clone)
|
||||
case ^sha2.Context_512:
|
||||
sha2.final(impl, hash, finalize_clone)
|
||||
case ^sha3.Context:
|
||||
sha3.final(impl, hash, finalize_clone)
|
||||
case ^shake.Context:
|
||||
shake.final(impl, hash, finalize_clone)
|
||||
case ^sm3.Context:
|
||||
sm3.final(impl, hash, finalize_clone)
|
||||
case ^keccak.Context:
|
||||
keccak.final(impl, hash, finalize_clone)
|
||||
case ^md5.Context:
|
||||
md5.final(impl, hash, finalize_clone)
|
||||
case ^sha1.Context:
|
||||
sha1.final(impl, hash, finalize_clone)
|
||||
case:
|
||||
panic("crypto/hash: uninitialized algorithm")
|
||||
}
|
||||
|
||||
if !finalize_clone {
|
||||
reset(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context, allocator := context.allocator) {
|
||||
// XXX/yawning: Maybe these cases should panic, because both cases,
|
||||
// are probably bugs.
|
||||
if ctx == other {
|
||||
return
|
||||
}
|
||||
if ctx._impl != nil {
|
||||
reset(ctx)
|
||||
}
|
||||
|
||||
ctx._algo = other._algo
|
||||
ctx._allocator = allocator
|
||||
|
||||
switch src_impl in other._impl {
|
||||
case ^blake2b.Context:
|
||||
impl := new(blake2b.Context, allocator)
|
||||
blake2b.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^blake2s.Context:
|
||||
impl := new(blake2s.Context, allocator)
|
||||
blake2s.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^sha2.Context_256:
|
||||
impl := new(sha2.Context_256, allocator)
|
||||
sha2.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^sha2.Context_512:
|
||||
impl := new(sha2.Context_512, allocator)
|
||||
sha2.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^sha3.Context:
|
||||
impl := new(sha3.Context, allocator)
|
||||
sha3.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^shake.Context:
|
||||
impl := new(shake.Context, allocator)
|
||||
shake.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^sm3.Context:
|
||||
impl := new(sm3.Context, allocator)
|
||||
sm3.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^keccak.Context:
|
||||
impl := new(keccak.Context, allocator)
|
||||
keccak.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^md5.Context:
|
||||
impl := new(md5.Context, allocator)
|
||||
md5.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case ^sha1.Context:
|
||||
impl := new(sha1.Context, allocator)
|
||||
sha1.clone(impl, src_impl)
|
||||
ctx._impl = impl
|
||||
case:
|
||||
panic("crypto/hash: uninitialized algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
// reset sanitizes the Context and frees resources internal to the
|
||||
// Context. The Context must be re-initialized to be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
switch impl in ctx._impl {
|
||||
case ^blake2b.Context:
|
||||
blake2b.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^blake2s.Context:
|
||||
blake2s.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^sha2.Context_256:
|
||||
sha2.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^sha2.Context_512:
|
||||
sha2.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^sha3.Context:
|
||||
sha3.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^shake.Context:
|
||||
shake.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^sm3.Context:
|
||||
sm3.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^keccak.Context:
|
||||
keccak.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^md5.Context:
|
||||
md5.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case ^sha1.Context:
|
||||
sha1.reset(impl)
|
||||
free(impl, ctx._allocator)
|
||||
case:
|
||||
// Unlike clone, calling reset repeatedly is fine.
|
||||
}
|
||||
|
||||
ctx._algo = .Invalid
|
||||
ctx._impl = nil
|
||||
}
|
||||
|
||||
// algorithm returns the Algorithm used by a Context instance.
|
||||
algorithm :: proc(ctx: ^Context) -> Algorithm {
|
||||
return ctx._algo
|
||||
}
|
||||
|
||||
// digest_size returns the digest size of a Context instance.
|
||||
digest_size :: proc(ctx: ^Context) -> int {
|
||||
return DIGEST_SIZES[ctx._algo]
|
||||
}
|
||||
Reference in New Issue
Block a user