From b471887df20a64b6c3b6dbd6c8f451ba51ef194d Mon Sep 17 00:00:00 2001 From: SyoBoN Date: Thu, 5 Dec 2024 08:14:15 +0900 Subject: [PATCH] crypto.sha3: add support for Keccak-256 and Keccak-512 (#23058) --- vlib/crypto/sha3/sha3.v | 109 ++++++++++++++++++++++------------ vlib/crypto/sha3/sha3_test.v | 31 ++++++++++ vlib/crypto/sha3/usage_test.v | 95 +++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+), 37 deletions(-) create mode 100644 vlib/crypto/sha3/usage_test.v diff --git a/vlib/crypto/sha3/sha3.v b/vlib/crypto/sha3/sha3.v index 4dff8f5886..3fde63c65c 100644 --- a/vlib/crypto/sha3/sha3.v +++ b/vlib/crypto/sha3/sha3.v @@ -33,12 +33,6 @@ pub const xof_rate_128 = 168 // xof_rate_256 is the capacity, in bytes, of a 256 bit extended output function sponge pub const xof_rate_256 = 136 -// the low order pad bits for a hash function -const hash_pad = u8(0x06) - -// the low order pad bits for an extended output function -const xof_pad = u8(0x1f) - // new512 initializes the digest structure for a sha3 512 bit hash pub fn new512() !&Digest { return new_digest(rate_512, size_512)! @@ -59,6 +53,16 @@ pub fn new224() !&Digest { return new_digest(rate_224, size_224)! } +// new256keccak initializes the digest structure for a keccak 256 bit hash +pub fn new256keccak() !&Digest { + return new_digest(rate_256, size_256, padding: .keccak)! +} + +// new512keccak initializes the digest structure for a keccak 512 bit hash +pub fn new512keccak() !&Digest { + return new_digest(rate_512, size_512, padding: .keccak)! +} + // new256_xof initializes the digest structure for a sha3 256 bit extended output function pub fn new256xof(output_len int) !&Digest { return new_xof_digest(xof_rate_256, output_len)! @@ -108,22 +112,66 @@ fn (err XOFSizeError) msg() string { struct Digest { rate int // the number of bytes absorbed per permutation - suffix u8 // the domain suffix, 0x06 for hash, 0x1f for extended output + suffix u8 // the domain suffix, 0x06 for hash, 0x01 for keccak, 0x1f for extended output output_len int // the number of bytes to output mut: input_buffer []u8 // temporary holding buffer for input bytes s State // the state of a kaccak-p[1600, 24] sponge } +// the low order pad bits for a hash function +pub enum Padding as u8 { + keccak = 0x01 + sha3 = 0x06 + xof = 0x1f +} + +@[params] +pub struct PaddingConfig { +pub: + padding Padding = .sha3 +} + // new_digest creates an initialized digest structure based on -// the hash size and whether or not you specify a MAC key. +// the hash size. // // absorption_rate is the number of bytes to be absorbed into the // sponge per permutation. // // hash_size - the number if bytes in the generated hash. // Legal values are 224, 256, 384, and 512. -pub fn new_digest(absorption_rate int, hash_size int) !&Digest { +// +// config - the padding setting for hash generation. .sha3 should be used for FIPS PUB 202 compliant SHA3-224, SHA3-256, SHA3-384 and SHA3-512. Use .keccak if you want a legacy Keccak-224, Keccak-256, Keccak-384 or Keccak-512 algorithm. .xof is for extended output functions. +pub fn new_digest(absorption_rate int, hash_size int, config PaddingConfig) !&Digest { + match config.padding { + .sha3, .keccak { validate_sha3(absorption_rate, hash_size)! } + .xof { validate_xof(absorption_rate, hash_size)! } + } + + d := Digest{ + rate: absorption_rate + suffix: u8(config.padding) + output_len: hash_size + s: State{} + } + + return &d +} + +// new_xof_digest creates an initialized digest structure based on +// the absorption rate and how many bytes of output you need +// +// absorption_rate is the number of bytes to be absorbed into the +// sponge per permutation. Legal values are xof_rate_128 and +// xof_rate_256. +// +// hash_size - the number if bytes in the generated hash. +// Legal values are positive integers. +pub fn new_xof_digest(absorption_rate int, hash_size int) !&Digest { + return new_digest(absorption_rate, hash_size, padding: .xof) +} + +fn validate_sha3(absorption_rate int, hash_size int) ! { match hash_size { size_224 { if absorption_rate != rate_224 { @@ -163,27 +211,9 @@ pub fn new_digest(absorption_rate int, hash_size int) !&Digest { } } } - - d := Digest{ - rate: absorption_rate - suffix: hash_pad - output_len: hash_size - s: State{} - } - - return &d } -// new_xof_digest creates an initialized digest structure based on -// the absorption rate and how many bytes of output you need -// -// absorption_rate is the number of bytes to be absorbed into the -// sponge per permutation. Legal values are xof_rate_128 and -// xof_rate_256. -// -// hash_size - the number if bytes in the generated hash. -// Legal values are positive integers. -pub fn new_xof_digest(absorption_rate int, hash_size int) !&Digest { +fn validate_xof(absorption_rate int, hash_size int) ! { match absorption_rate { xof_rate_128, xof_rate_256 { if hash_size < 1 { @@ -198,15 +228,6 @@ pub fn new_xof_digest(absorption_rate int, hash_size int) !&Digest { } } } - - d := Digest{ - rate: absorption_rate - suffix: xof_pad - output_len: hash_size - s: State{} - } - - return &d } // write adds bytes to the sponge. @@ -345,6 +366,20 @@ pub fn sum224(data []u8) []u8 { return d.checksum_internal() or { panic(err) } } +// keccak256 returns the keccak 256 bit checksum of the data. +pub fn keccak256(data []u8) []u8 { + mut d := new256keccak() or { panic(err) } + d.write(data) or { panic(err) } + return d.checksum_internal() or { panic(err) } +} + +// keccak512 returns the keccak 512 bit checksum of the data. +pub fn keccak512(data []u8) []u8 { + mut d := new512keccak() or { panic(err) } + d.write(data) or { panic(err) } + return d.checksum_internal() or { panic(err) } +} + // shake256 returns the sha3 shake256 bit extended output pub fn shake256(data []u8, output_len int) []u8 { mut d := new256xof(output_len) or { panic(err) } diff --git a/vlib/crypto/sha3/sha3_test.v b/vlib/crypto/sha3/sha3_test.v index fcec038d5b..6edff2f686 100644 --- a/vlib/crypto/sha3/sha3_test.v +++ b/vlib/crypto/sha3/sha3_test.v @@ -15,6 +15,14 @@ const empty_message_sha3_512 = [u8(0xA6), 0x9F, 0x73, 0xCC, 0xA2, 0x3A, 0x9A, 0x 0xDC, 0xC1, 0x47, 0x5C, 0x80, 0xA6, 0x15, 0xB2, 0x12, 0x3A, 0xF1, 0xF5, 0xF9, 0x4C, 0x11, 0xE3, 0xE9, 0x40, 0x2C, 0x3A, 0xC5, 0x58, 0xF5, 0x00, 0x19, 0x9D, 0x95, 0xB6, 0xD3, 0xE3, 0x01, 0x75, 0x85, 0x86, 0x28, 0x1D, 0xCD, 0x26] +const empty_message_keccak_256 = [u8(0xC5), 0xD2, 0x46, 0x01, 0x86, 0xF7, 0x23, 0x3C, 0x92, 0x7E, + 0x7D, 0xB2, 0xDC, 0xC7, 0x03, 0xC0, 0xE5, 0x00, 0xB6, 0x53, 0xCA, 0x82, 0x27, 0x3B, 0x7B, 0xFA, + 0xD8, 0x04, 0x5D, 0x85, 0xA4, 0x70] +const empty_message_keccak_512 = [u8(0x0E), 0xAB, 0x42, 0xDE, 0x4C, 0x3C, 0xEB, 0x92, 0x35, 0xFC, + 0x91, 0xAC, 0xFF, 0xE7, 0x46, 0xB2, 0x9C, 0x29, 0xA8, 0xC3, 0x66, 0xB7, 0xC6, 0x0E, 0x4E, 0x67, + 0xC4, 0x66, 0xF3, 0x6A, 0x43, 0x04, 0xC0, 0x0F, 0xA9, 0xCA, 0xF9, 0xD8, 0x79, 0x76, 0xBA, 0x46, + 0x9B, 0xCB, 0xE0, 0x67, 0x13, 0xB4, 0x35, 0xF0, 0x91, 0xEF, 0x27, 0x69, 0xFB, 0x16, 0x0C, 0xDA, + 0xB3, 0x3D, 0x36, 0x70, 0x68, 0x0E] fn test_0_length_hash() { input := []u8{} @@ -30,6 +38,12 @@ fn test_0_length_hash() { output_512 := sum512(input) assert output_512 == empty_message_sha3_512 + + output_keccak_256 := keccak256(input) + assert output_keccak_256 == empty_message_keccak_256 + + output_keccak_512 := keccak512(input) + assert output_keccak_512 == empty_message_keccak_512 } const input_200 = [u8(0xa3), 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, @@ -65,6 +79,17 @@ const test_200_message_sha3_512 = [u8(0xE7), 0x6D, 0xFA, 0xD2, 0x20, 0x84, 0xA8, 0x84, 0x73, 0x9A, 0x2D, 0xF4, 0x6B, 0xE5, 0x89, 0xC5, 0x1C, 0xA1, 0xA4, 0xA8, 0x41, 0x6D, 0xF6, 0x54, 0x5A, 0x1C, 0xE8, 0xBA, 0x00] +// The values are taken from the output of Go's sha3 library +const test_200_message_keccak_256 = [u8(0x3A), 0x57, 0x66, 0x6B, 0x04, 0x87, 0x77, 0xF2, 0xC9, + 0x53, 0xDC, 0x44, 0x56, 0xF4, 0x5A, 0x25, 0x88, 0xE1, 0xCB, 0x6F, 0x2D, 0xA7, 0x60, 0x12, 0x2D, + 0x53, 0x0A, 0xC2, 0xCE, 0x60, 0x7D, 0x4A] + +const test_200_message_keccak_512 = [u8(0xF4), 0xF8, 0x46, 0xD1, 0x40, 0x84, 0x75, 0x39, 0xF5, + 0x3C, 0x3F, 0x08, 0x2C, 0xC4, 0xE6, 0x81, 0x0E, 0x14, 0x3A, 0x5B, 0x4F, 0xC6, 0x2A, 0x20, 0x59, + 0x7B, 0x5D, 0x76, 0x04, 0x32, 0x46, 0xB8, 0x6B, 0xD7, 0x14, 0x9B, 0x90, 0x61, 0x40, 0xBB, 0x96, + 0x65, 0xA6, 0xCE, 0x83, 0xD9, 0x91, 0xF0, 0x32, 0xF2, 0x29, 0x1D, 0x2F, 0xAE, 0x80, 0xEE, 0xDF, + 0xC6, 0xF8, 0x45, 0xCC, 0x16, 0xD5, 0xAE] + fn test_200_length_hash() { output_224 := sum224(input_200) assert output_224 == test_200_message_sha3_224 @@ -77,6 +102,12 @@ fn test_200_length_hash() { output_512 := sum512(input_200) assert output_512 == test_200_message_sha3_512 + + output_keccak_256 := keccak256(input_200) + assert output_keccak_256 == test_200_message_keccak_256 + + output_keccak_512 := keccak512(input_200) + assert output_keccak_512 == test_200_message_keccak_512 } const empty_message_shake128 = [u8(0x7F), 0x9C, 0x2B, 0xA4, 0xE8, 0x8F, 0x82, 0x7D, 0x61, 0x60, diff --git a/vlib/crypto/sha3/usage_test.v b/vlib/crypto/sha3/usage_test.v new file mode 100644 index 0000000000..495affca4e --- /dev/null +++ b/vlib/crypto/sha3/usage_test.v @@ -0,0 +1,95 @@ +import crypto.sha3 + +fn test_api_call() { + input := 'abc'.bytes() + + // Digest generation + mut digest_224 := sha3.new_digest(sha3.rate_224, sha3.size_224)! + mut digest_256 := sha3.new_digest(sha3.rate_256, sha3.size_256)! + mut digest_384 := sha3.new_digest(sha3.rate_384, sha3.size_384)! + mut digest_512 := sha3.new_digest(sha3.rate_512, sha3.size_512)! + mut digest_keccak_224 := sha3.new_digest(sha3.rate_224, sha3.size_224, padding: .keccak)! + mut digest_keccak_256 := sha3.new_digest(sha3.rate_256, sha3.size_256, padding: .keccak)! + mut digest_keccak_384 := sha3.new_digest(sha3.rate_384, sha3.size_384, padding: .keccak)! + mut digest_keccak_512 := sha3.new_digest(sha3.rate_512, sha3.size_512, padding: .keccak)! + + // Result should be same as shortened version + assert digest_224 == sha3.new224()! + assert digest_256 == sha3.new256()! + assert digest_384 == sha3.new384()! + assert digest_512 == sha3.new512()! + assert digest_keccak_256 == sha3.new256keccak()! + assert digest_keccak_512 == sha3.new512keccak()! + + // Should not panic + digest_224.write(input)! + digest_256.write(input)! + digest_384.write(input)! + digest_512.write(input)! + digest_keccak_256.write(input)! + digest_keccak_512.write(input)! + + // Result should be same as shortened version + assert digest_224.checksum() == sha3.sum224(input) + assert digest_256.checksum() == sha3.sum256(input) + assert digest_384.checksum() == sha3.sum384(input) + assert digest_512.checksum() == sha3.sum512(input) + assert digest_keccak_256.checksum() == sha3.keccak256(input) + assert digest_keccak_512.checksum() == sha3.keccak512(input) + + for size in 1 .. 65 { + // Digest generation + mut digest_xof_128 := sha3.new_digest(sha3.xof_rate_128, size, padding: .xof)! + mut digest_xof_256 := sha3.new_digest(sha3.xof_rate_256, size, padding: .xof)! + + // Result should be same as shortened version + assert digest_xof_128 == sha3.new_xof_digest(sha3.xof_rate_128, size)! + assert digest_xof_256 == sha3.new_xof_digest(sha3.xof_rate_256, size)! + assert digest_xof_128 == sha3.new128xof(size)! + assert digest_xof_256 == sha3.new256xof(size)! + + // Should not panic + digest_xof_128.write(input)! + digest_xof_256.write(input)! + } + + // Should be AbsorptionRateError + if should_be_error := sha3.new_digest(sha3.rate_224, sha3.size_256) { + assert false, 'new_digest(sha3.rate_224, sha3.size_256) should be error' + } + if should_be_error := sha3.new_digest(sha3.rate_224, sha3.size_256, padding: .keccak) { + assert false, 'new_digest(sha3.rate_224, sha3.size_256, padding: .keccak) should be error' + } + if should_be_error := sha3.new_digest(0, sha3.size_256) { + assert false, 'new_digest(0, sha3.size_256) should be error' + } + if should_be_error := sha3.new_digest(0, sha3.size_256, padding: .keccak) { + assert false, 'new_digest(0, sha3.size_256, padding: .keccak) should be error' + } + + // Should be HashSizeError + if should_be_error := sha3.new_digest(sha3.rate_256, -1) { + assert false, 'new_digest(sha3.rate_256, -1) should be error' + } + if should_be_error := sha3.new_digest(sha3.rate_256, -1, padding: .keccak) { + assert false, 'new_digest(sha3.rate_256, -1, padding: .keccak) should be error' + } + if should_be_error := sha3.new_digest(sha3.rate_256, 100) { + assert false, 'new_digest(sha3.rate_256, -1) should be error' + } + if should_be_error := sha3.new_digest(sha3.rate_256, 100, padding: .keccak) { + assert false, 'new_digest(sha3.rate_256, -1, padding: .keccak) should be error' + } + + // Should be XOFSizeError + if should_be_error := sha3.new128xof(-1) { + assert false, 'new128xof(-1) should be error' + } + if should_be_error := sha3.new256xof(-1) { + assert false, 'new256xof(-1) should be error' + } + // Shoud be XOFRateError + if should_be_error := sha3.new_xof_digest(0, sha3.size_256) { + assert false, 'new_xof_digest(0, sha3.size_256) should be error' + } +}