From 15287a771f4dfb1a267cc0bf3f87498b8f3e381f Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Thu, 29 Feb 2024 20:43:24 +0900 Subject: [PATCH] core/crypto/shake: Support cSHAKE --- core/crypto/_sha3/sha3.odin | 4 +- core/crypto/_sha3/sp800_185.odin | 130 ++++++++++++++++++++++++ core/crypto/shake/shake.odin | 23 +++-- tests/core/crypto/test_core_crypto.odin | 66 ++++++++++-- 4 files changed, 204 insertions(+), 19 deletions(-) create mode 100644 core/crypto/_sha3/sp800_185.odin diff --git a/core/crypto/_sha3/sha3.odin b/core/crypto/_sha3/sha3.odin index 0c558666b..2db76fce0 100644 --- a/core/crypto/_sha3/sha3.odin +++ b/core/crypto/_sha3/sha3.odin @@ -20,9 +20,7 @@ import "core:mem" ROUNDS :: 24 -RATE_SHAKE_128 :: 168 -RATE_SHAKE_256 :: 136 - +RATE_128 :: 1344 / 8 // ONLY for SHAKE128. RATE_224 :: 1152 / 8 RATE_256 :: 1088 / 8 RATE_384 :: 832 / 8 diff --git a/core/crypto/_sha3/sp800_185.odin b/core/crypto/_sha3/sp800_185.odin new file mode 100644 index 000000000..10824f9af --- /dev/null +++ b/core/crypto/_sha3/sp800_185.odin @@ -0,0 +1,130 @@ +package _sha3 + +import "core:encoding/endian" +import "core:math/bits" + +init_cshake :: proc(ctx: ^Context, n, s: []byte, sec_strength: int) { + rate: int + switch sec_strength { + case 128: + rate = RATE_128 + case 256: + rate = RATE_256 + case: + panic("crypto/sha3: invalid security strength") + } + + ctx.mdlen = sec_strength / 8 + + // No domain separator is equivalent to vanilla SHAKE. + if len(n) == 0 && len(s) == 0 { + ctx.dsbyte = DS_SHAKE + init(ctx) + return + } + + ctx.dsbyte = DS_CSHAKE + init(ctx) + bytepad(ctx, [][]byte{n, s}, rate) +} + +// right_encode and left_encode are defined to support 0 <= x < 2^2040 +// however, the largest value we will ever need to encode is `max(int) * 8`. +// +// This is unfortunate as the extreme upper edge is larger than +// `max(u64)`. While such values are impractical at present, +// they are possible (ie: https://arxiv.org/pdf/quant-ph/9908043.pdf). +// +// Thus we support 0 <= x < 2^128. + +@(private) +_PAD: [RATE_128]byte // Biggest possible value of w per spec. + +bytepad :: proc(ctx: ^Context, x_strings: [][]byte, w: int) { + // 1. z = left_encode(w) || X. + z_hi: u64 + z_lo := left_right_encode(ctx, 0, u64(w), true) + for x in x_strings { + // All uses of bytepad in SP 800-185 use the output from + // one or more encode_string values for `X`. + hi, lo := encode_string(ctx, x) + + carry: u64 + z_lo, carry = bits.add_u64(z_lo, lo, 0) + z_hi, carry = bits.add_u64(z_hi, hi, carry) + + // This isn't actually possible, at least with the currently + // defined SP 800-185 routines. + if carry != 0 { + panic("crypto/sha3: bytepad input length overflow") + } + } + + // We skip this step as we are doing a byte-oriented implementation + // rather than a bit oriented one. + // + // 2. while len(z) mod 8 ≠ 0: + // z = z || 0 + + // 3. while (len(z)/8) mod w ≠ 0: + // z = z || 00000000 + z_len := u128(z_hi) << 64 | u128(z_lo) + z_rem := int(z_len % u128(w)) + pad := _PAD[:w - z_rem] + + // We just add the padding to the state, instead of returning z. + // + // 4. return z. + update(ctx, pad) +} + +encode_string :: #force_inline proc(ctx: ^Context, s: []byte) -> (u64, u64) { + l := encode_byte_len(ctx, len(s), true) // left_encode + update(ctx, s) + + lo, hi := bits.add_u64(l, u64(len(s)), 0) + + return hi, lo +} + +encode_byte_len :: #force_inline proc(ctx: ^Context, l: int, is_left: bool) -> u64 { + hi, lo := bits.mul_u64(u64(l), 8) + return left_right_encode(ctx, hi, lo, is_left) +} + +@(private) +left_right_encode :: proc(ctx: ^Context, hi, lo: u64, is_left: bool) -> u64 { + HI_OFFSET :: 1 + LO_OFFSET :: HI_OFFSET + 8 + RIGHT_OFFSET :: LO_OFFSET + 8 + BUF_LEN :: RIGHT_OFFSET + 1 + + buf: [BUF_LEN]byte // prefix + largest uint + postfix + + endian.unchecked_put_u64be(buf[HI_OFFSET:], hi) + endian.unchecked_put_u64be(buf[LO_OFFSET:], lo) + + // 2. Strip leading `0x00` bytes. + off: int + for off = HI_OFFSET; off < RIGHT_OFFSET - 1; off = off + 1 {// Note: Minimum size is 1, not 0. + if buf[off] != 0 { + break + } + } + n := byte(RIGHT_OFFSET - off) + + // 3. Prefix (left_encode) or postfix (right_encode) the length in bytes. + b: []byte + switch is_left { + case true: + buf[off - 1] = n // n | x + b = buf[off - 1:RIGHT_OFFSET] + case false: + buf[RIGHT_OFFSET] = n // x | n + b = buf[off:] + } + + update(ctx, b) + + return u64(len(b)) +} diff --git a/core/crypto/shake/shake.odin b/core/crypto/shake/shake.odin index 1b09456db..7da427485 100644 --- a/core/crypto/shake/shake.odin +++ b/core/crypto/shake/shake.odin @@ -1,10 +1,11 @@ /* -package shake implements the SHAKE XOF algorithm family. +package shake implements the SHAKE and cSHAKE XOF algorithm families. The SHA3 hash algorithm can be found in the crypto/sha3. See: - https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf +- https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf */ package shake @@ -18,25 +19,27 @@ package shake import "../_sha3" -// Context is a SHAKE128 or SHAKE256 instance. +// Context is a SHAKE128, SHAKE256, cSHAKE128, or cSHAKE256 instance. Context :: distinct _sha3.Context // init_128 initializes a Context for SHAKE128. init_128 :: proc(ctx: ^Context) { - ctx.mdlen = 128 / 8 - _init(ctx) + _sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 128) } // init_256 initializes a Context for SHAKE256. init_256 :: proc(ctx: ^Context) { - ctx.mdlen = 256 / 8 - _init(ctx) + _sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 256) } -@(private) -_init :: proc(ctx: ^Context) { - ctx.dsbyte = _sha3.DS_SHAKE - _sha3.init(transmute(^_sha3.Context)(ctx)) +// init_cshake_128 initializes a Context for cSHAKE128. +init_cshake_128 :: proc(ctx: ^Context, domain_sep: []byte) { + _sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 128) +} + +// init_cshake_256 initializes a Context for cSHAKE256. +init_cshake_256 :: proc(ctx: ^Context, domain_sep: []byte) { + _sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 256) } // write writes more data into the SHAKE instance. This MUST not be called diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index df1076604..362a4f459 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -415,6 +415,7 @@ test_rand_bytes :: proc(t: ^testing.T) { TestXOF :: struct { sec_strength: int, + domainsep: string, output: string, str: string, } @@ -425,16 +426,19 @@ test_shake :: proc(t: ^testing.T) { // 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", }, @@ -442,31 +446,80 @@ test_shake :: proc(t: ^testing.T) { // 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", }, + + // cSHAKE128 + // - https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/cSHAKE_samples.pdf + { + 128, + "Email Signature", + "c1c36925b6409a04f1b504fcbca9d82b4017277cb5ed2b2065fc1d3814d5aaf5", + "00010203", + }, + { + 128, + "Email Signature", + "c5221d50e4f822d96a2e8881a961420f294b7b24fe3d2094baed2c6524cc166b", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7", + }, + + // cSHAKE256 + // - https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/cSHAKE_samples.pdf + { + 256, + "Email Signature", + "d008828e2b80ac9d2218ffee1d070c48b8e4c87bff32c9699d5b6896eee0edd164020e2be0560858d9c00c037e34a96937c561a74c412bb4c746469527281c8c", + "00010203", + }, + { + 256, + "Email Signature", + "07dc27b11e51fbac75bc7b3c1d983e8b4b85fb1defaf218912ac86430273091727f42b17ed1df63e8ec118f04b23633c1dfb1574c8fb55cb45da8e25afb092bb", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7", + }, } for v in test_vectors { dst := make([]byte, len(v.output)/2, context.temp_allocator) data := transmute([]byte)(v.str) + domainsep := transmute([]byte)(v.domainsep) + alg_prefix := "" ctx: shake.Context - switch v.sec_strength { - case 128: - shake.init_128(&ctx) - case 256: - shake.init_256(&ctx) + if len(domainsep) == 0 { + switch v.sec_strength { + case 128: + shake.init_128(&ctx) + case 256: + shake.init_256(&ctx) + } + } else { + alg_prefix = "c" + + // The cSHAKE samples from NIST are binary data. + data, _ = hex.decode(data) + + switch v.sec_strength { + case 128: + shake.init_cshake_128(&ctx, domainsep) + case 256: + shake.init_cshake_256(&ctx, domainsep) + } } shake.write(&ctx, data) @@ -478,7 +531,8 @@ test_shake :: proc(t: ^testing.T) { t, dst_str == v.output, fmt.tprintf( - "SHAKE%d: Expected: %s for input of %s, but got %s instead", + "%sSHAKE%d: Expected: %s for input of %s, but got %s instead", + alg_prefix, v.sec_strength, v.output, v.str,