From b02b85d242c0d60ae02981df590a079c32557ab4 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Fri, 26 Jan 2024 18:57:53 +0900 Subject: [PATCH] core/crypto/shake: SHAKE is an XOF, not a hash --- core/crypto/hash/low_level.odin | 27 ------- core/crypto/shake/shake.odin | 61 +++++++-------- tests/core/crypto/test_core_crypto.odin | 80 +++++++++++++++++++- tests/core/crypto/test_core_crypto_hash.odin | 30 -------- 4 files changed, 107 insertions(+), 91 deletions(-) diff --git a/core/crypto/hash/low_level.odin b/core/crypto/hash/low_level.odin index 34cf44630..f4f2fb0b1 100644 --- a/core/crypto/hash/low_level.odin +++ b/core/crypto/hash/low_level.odin @@ -4,7 +4,6 @@ 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" @@ -26,8 +25,6 @@ Algorithm :: enum { SHA3_256, SHA3_384, SHA3_512, - SHAKE_128, - SHAKE_256, SM3, Legacy_KECCAK_224, Legacy_KECCAK_256, @@ -51,8 +48,6 @@ ALGORITHM_NAMES := [Algorithm]string { .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", @@ -76,8 +71,6 @@ DIGEST_SIZES := [Algorithm]int { .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, @@ -96,7 +89,6 @@ Context :: struct { ^sha2.Context_256, ^sha2.Context_512, ^sha3.Context, - ^shake.Context, ^sm3.Context, ^keccak.Context, ^md5.Context, @@ -159,14 +151,6 @@ init :: proc(ctx: ^Context, algorithm: Algorithm, allocator := context.allocator 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) @@ -218,8 +202,6 @@ update :: proc(ctx: ^Context, data: []byte) { 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: @@ -250,8 +232,6 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { 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: @@ -304,10 +284,6 @@ clone :: proc(ctx, other: ^Context, allocator := context.allocator) { 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) @@ -348,9 +324,6 @@ reset :: proc(ctx: ^Context) { 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) diff --git a/core/crypto/shake/shake.odin b/core/crypto/shake/shake.odin index 7ba7138a3..072204800 100644 --- a/core/crypto/shake/shake.odin +++ b/core/crypto/shake/shake.odin @@ -1,3 +1,11 @@ +/* +package shake implements the SHAKE XOF algorithm family. + +The SHA3 hash algorithm can be found in the crypto/sha3. + +See: +- https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf +*/ package shake /* @@ -6,30 +14,22 @@ package shake List of contributors: zhibog, dotbmp: Initial implementation. - - Interface for the SHAKE XOF. The SHA3 hashing algorithm can be found - in package sha3. - - TODO: - - This should provide an incremental squeeze interface. - - DIGEST_SIZE is inaccurate, SHAKE-128 and SHAKE-256 are security - strengths. */ import "../_sha3" -DIGEST_SIZE_128 :: 16 -DIGEST_SIZE_256 :: 32 - +// Context is a SHAKE128 or SHAKE256 instance. Context :: distinct _sha3.Context +// init_128 initializes a Context for SHAKE128. init_128 :: proc(ctx: ^Context) { - ctx.mdlen = DIGEST_SIZE_128 + ctx.mdlen = 128 / 8 _init(ctx) } +// init_256 initializes a Context for SHAKE256. init_256 :: proc(ctx: ^Context) { - ctx.mdlen = DIGEST_SIZE_256 + ctx.mdlen = 256 / 8 _init(ctx) } @@ -38,36 +38,31 @@ _init :: proc(ctx: ^Context) { _sha3.init(transmute(^_sha3.Context)(ctx)) } -update :: proc(ctx: ^Context, data: []byte) { +// write writes more data into the SHAKE instance. This MUST not be called +// after any reads have been done, and attempts to do so will panic. +write :: proc(ctx: ^Context, data: []byte) { _sha3.update(transmute(^_sha3.Context)(ctx), data) } -final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { - // Rolling digest support is handled here instead of in the generic - // _sha3 package as SHAKE is more of an XOF than a hash, so the - // standard notion of "final", doesn't really exist when you can - // squeeze an unlimited amount of data. - // - // TODO/yawning: Strongly consider getting rid of this and rigidly - // defining SHAKE as an XOF. - - ctx := ctx - if finalize_clone { - tmp_ctx: Context - clone(&tmp_ctx, ctx) - ctx = &tmp_ctx - } - defer(reset(ctx)) - +// read reads output from the SHAKE instance. There is no practical upper +// limit to the amount of data that can be read from SHAKE. After read has +// been called one or more times, further calls to write will panic. +read :: proc(ctx: ^Context, dst: []byte) { ctx_ := transmute(^_sha3.Context)(ctx) - _sha3.shake_xof(ctx_) - _sha3.shake_out(ctx_, hash[:]) + if !ctx.is_finalized { + _sha3.shake_xof(ctx_) + } + + _sha3.shake_out(ctx_, dst) } +// clone clones the Context other into ctx. clone :: proc(ctx, other: ^Context) { _sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other)) } +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. reset :: proc(ctx: ^Context) { _sha3.reset(transmute(^_sha3.Context)(ctx)) } diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index 0aaf2d854..bab98ceb8 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -12,11 +12,13 @@ package test_core_crypto Where possible, the official test vectors are used to validate the implementation. */ +import "core:encoding/hex" import "core:fmt" +import "core:os" import "core:testing" import "core:crypto/siphash" -import "core:os" +import "core:crypto/shake" TEST_count := 0 TEST_fail := 0 @@ -43,6 +45,7 @@ main :: proc() { t := testing.T{} test_hash(&t) + test_shake(&t) test_siphash_2_4(&t) // "modern" crypto tests @@ -60,6 +63,81 @@ main :: proc() { } } +TestXOF :: struct { + sec_strength: int, + output: string, + str: string, +} + +@(test) +test_shake :: proc(t: ^testing.T) { + test_vectors := [?]TestXOF { + // SHAKE128 + { + 128, + "7f9c2ba4e88f827d616045507605853e", + "", + }, + { + 128, + "f4202e3c5852f9182a0430fd8144f0a7", + "The quick brown fox jumps over the lazy dog", + }, + { + 128, + "853f4538be0db9621a6cea659a06c110", + "The quick brown fox jumps over the lazy dof", + }, + + // SHAKE256 + { + 256, + "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f", + "", + }, + { + 256, + "2f671343d9b2e1604dc9dcf0753e5fe15c7c64a0d283cbbf722d411a0e36f6ca", + "The quick brown fox jumps over the lazy dog", + }, + { + 256, + "46b1ebb2e142c38b9ac9081bef72877fe4723959640fa57119b366ce6899d401", + "The quick brown fox jumps over the lazy dof", + }, + } + for v in test_vectors { + dst := make([]byte, len(v.output)/2, context.temp_allocator) + + data := transmute([]byte)(v.str) + + ctx: shake.Context + switch v.sec_strength { + case 128: + shake.init_128(&ctx) + case 256: + shake.init_256(&ctx) + } + + shake.write(&ctx, data) + shake.read(&ctx, dst) + + dst_str := string(hex.encode(dst, context.temp_allocator)) + + expect( + t, + dst_str == v.output, + fmt.tprintf( + "SHAKE%d: Expected: %s for input of %s, but got %s instead", + v.sec_strength, + v.output, + v.str, + dst_str, + ), + ) + } +} + @(test) test_siphash_2_4 :: proc(t: ^testing.T) { // Test vectors from diff --git a/tests/core/crypto/test_core_crypto_hash.odin b/tests/core/crypto/test_core_crypto_hash.odin index 653d3fc34..570a8d88d 100644 --- a/tests/core/crypto/test_core_crypto_hash.odin +++ b/tests/core/crypto/test_core_crypto_hash.odin @@ -339,36 +339,6 @@ test_hash :: proc(t: ^testing.T) { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", }, - // SHAKE-128 - TestHash{hash.Algorithm.SHAKE_128, "7f9c2ba4e88f827d616045507605853e", ""}, - TestHash { - hash.Algorithm.SHAKE_128, - "f4202e3c5852f9182a0430fd8144f0a7", - "The quick brown fox jumps over the lazy dog", - }, - TestHash { - hash.Algorithm.SHAKE_128, - "853f4538be0db9621a6cea659a06c110", - "The quick brown fox jumps over the lazy dof", - }, - - // SHAKE-256 - TestHash { - hash.Algorithm.SHAKE_256, - "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f", - "", - }, - TestHash { - hash.Algorithm.SHAKE_256, - "2f671343d9b2e1604dc9dcf0753e5fe15c7c64a0d283cbbf722d411a0e36f6ca", - "The quick brown fox jumps over the lazy dog", - }, - TestHash { - hash.Algorithm.SHAKE_256, - "46b1ebb2e142c38b9ac9081bef72877fe4723959640fa57119b366ce6899d401", - "The quick brown fox jumps over the lazy dof", - }, - // SM3 { hash.Algorithm.SM3,