From 690b149b4b7103a04b5f554df7515f9a61f7d67c Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Fri, 6 Feb 2026 15:55:04 +0100 Subject: [PATCH] uucore: Make Shake* algorithms optionally accept a length --- src/uu/cksum/benches/cksum_bench.rs | 4 +- src/uu/cksum/src/cksum.rs | 10 +++++ src/uucore/src/lib/features/checksum/mod.rs | 47 ++++++++++++--------- src/uucore/src/lib/features/sum.rs | 35 +++++++++++---- 4 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/uu/cksum/benches/cksum_bench.rs b/src/uu/cksum/benches/cksum_bench.rs index c316ec274..81f70bbb3 100644 --- a/src/uu/cksum/benches/cksum_bench.rs +++ b/src/uu/cksum/benches/cksum_bench.rs @@ -57,7 +57,7 @@ macro_rules! bench_shake_algorithm { let data = text_data::generate_by_size(100, 80); bencher.bench(|| { - let mut shake = Shake128::new(); + let mut shake = Shake128::with_output_bits(256); shake.hash_update(&data); // SHAKE algorithms can output any length, use 256 bits (32 bytes) for meaningful comparison @@ -76,7 +76,7 @@ macro_rules! bench_shake_algorithm { let data = text_data::generate_by_size(100, 80); bencher.bench(|| { - let mut shake = Shake256::new(); + let mut shake = Shake256::with_output_bits(512); shake.hash_update(&data); // SHAKE algorithms can output any length, use 256 bits (32 bytes) for meaningful comparison diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 5c78af2c6..51dd83d3a 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -100,6 +100,16 @@ fn maybe_sanitize_length( sanitize_sha2_sha3_length_str(algo, s_len).map(Some) } + // SHAKE128 and SHAKE256 algorithms optionally take a bit length. No + // validation is performed on this length, any value is valid. If the + // given length is not a multiple of 8, the last byte of the output + // will have its extra bits set to zero. + (Some(AlgoKind::Shake128 | AlgoKind::Shake256), Some(len)) => match len.parse::() { + Ok(0) => Ok(None), + Ok(l) => Ok(Some(l)), + Err(_) => Err(ChecksumError::InvalidLength(len.into()).into()), + }, + // For BLAKE2b, if a length is provided, validate it. (Some(AlgoKind::Blake2b), Some(len)) => calculate_blake2b_length_str(len), diff --git a/src/uucore/src/lib/features/checksum/mod.rs b/src/uucore/src/lib/features/checksum/mod.rs index 5cbcb0ae3..09e6d6344 100644 --- a/src/uucore/src/lib/features/checksum/mod.rs +++ b/src/uucore/src/lib/features/checksum/mod.rs @@ -114,6 +114,9 @@ impl AlgoKind { ALGORITHM_OPTIONS_SHA256 => Sha256, ALGORITHM_OPTIONS_SHA384 => Sha384, ALGORITHM_OPTIONS_SHA512 => Sha512, + + ALGORITHM_OPTIONS_SHAKE128 => Shake128, + ALGORITHM_OPTIONS_SHAKE256 => Shake256, _ => return Err(ChecksumError::UnknownAlgorithm(algo.as_ref().to_string()).into()), }) } @@ -247,8 +250,8 @@ pub enum SizedAlgoKind { Sha3(ShaLength), // Note: we store Blake2b's length as BYTES. Blake2b(Option), - Shake128(usize), - Shake256(usize), + Shake128(Option), + Shake256(Option), } impl SizedAlgoKind { @@ -280,8 +283,8 @@ impl SizedAlgoKind { (ak::Sha1, _) => Ok(Self::Sha1), (ak::Blake3, _) => Ok(Self::Blake3), - (ak::Shake128, Some(l)) => Ok(Self::Shake128(l)), - (ak::Shake256, Some(l)) => Ok(Self::Shake256(l)), + (ak::Shake128, l) => Ok(Self::Shake128(l)), + (ak::Shake256, l) => Ok(Self::Shake256(l)), (ak::Sha2, Some(l)) => Ok(Self::Sha2(ShaLength::try_from(l)?)), (ak::Sha3, Some(l)) => Ok(Self::Sha3(ShaLength::try_from(l)?)), (algo @ (ak::Sha2 | ak::Sha3), None) => { @@ -298,7 +301,6 @@ impl SizedAlgoKind { (ak::Sha256, None) => Ok(Self::Sha2(ShaLength::Len256)), (ak::Sha384, None) => Ok(Self::Sha2(ShaLength::Len384)), (ak::Sha512, None) => Ok(Self::Sha2(ShaLength::Len512)), - (_, None) => Err(ChecksumError::LengthRequired(kind.to_uppercase().into()).into()), } } @@ -341,27 +343,30 @@ impl SizedAlgoKind { Self::Blake2b(len_opt) => Box::new(Blake2b::with_output_bytes( len_opt.unwrap_or(Blake2b::DEFAULT_BYTE_SIZE), )), - Self::Shake128(_) => Box::new(Shake128::new()), - Self::Shake256(_) => Box::new(Shake256::new()), + Self::Shake128(len_opt) => Box::new(Shake128::with_output_bits( + len_opt.unwrap_or(Shake128::DEFAULT_BIT_SIZE), + )), + Self::Shake256(len_opt) => Box::new(Shake256::with_output_bits( + len_opt.unwrap_or(Shake256::DEFAULT_BIT_SIZE), + )), } } pub fn bitlen(&self) -> usize { - use SizedAlgoKind::*; match self { - Sysv => 512, - Bsd => 1024, - Crc => 256, - Crc32b => 32, - Md5 => 128, - Sm3 => 512, - Sha1 => 160, - Blake3 => 256, - Sha2(len) => len.as_usize(), - Sha3(len) => len.as_usize(), - Blake2b(len) => len.unwrap_or(512), - Shake128(len) => *len, - Shake256(len) => *len, + Self::Sysv => 512, + Self::Bsd => 1024, + Self::Crc => 256, + Self::Crc32b => 32, + Self::Md5 => 128, + Self::Sm3 => 512, + Self::Sha1 => 160, + Self::Blake3 => 256, + Self::Sha2(len) => len.as_usize(), + Self::Sha3(len) => len.as_usize(), + Self::Blake2b(len) => len.unwrap_or(Blake2b::DEFAULT_BYTE_SIZE * 8), + Self::Shake128(len) => len.unwrap_or(Shake128::DEFAULT_BIT_SIZE), + Self::Shake256(len) => len.unwrap_or(Shake256::DEFAULT_BIT_SIZE), } } pub fn is_legacy(&self) -> bool { diff --git a/src/uucore/src/lib/features/sum.rs b/src/uucore/src/lib/features/sum.rs index 8bce7acb6..cc4244362 100644 --- a/src/uucore/src/lib/features/sum.rs +++ b/src/uucore/src/lib/features/sum.rs @@ -377,27 +377,38 @@ macro_rules! impl_digest_common { // Implements the Digest trait for sha2 / sha3 algorithms with variable output macro_rules! impl_digest_shake { - ($algo_type: ty, $output_bits: literal) => { + ($algo_type: ty, $default_output_bits: literal) => { impl $algo_type { - pub fn new() -> Self { - Self(Default::default()) + pub const DEFAULT_BIT_SIZE: usize = $default_output_bits; + + pub fn with_output_bits(bits: usize) -> Self { + Self { + digest: Default::default(), + bit_size: bits, + } } } impl Digest for $algo_type { fn hash_update(&mut self, input: &[u8]) { - digest::Update::update(&mut self.0, input); + digest::Update::update(&mut self.digest, input); } fn hash_finalize(&mut self, out: &mut [u8]) { - digest::ExtendableOutputReset::finalize_xof_reset_into(&mut self.0, out); + digest::ExtendableOutputReset::finalize_xof_reset_into(&mut self.digest, out); + + // Remove the last bits if the requested length is not a multiple of 8. + let extra = self.output_bits() % 8; + if extra != 0 { + out[out.len() - 1] &= (1 << extra) - 1; + } } fn reset(&mut self) { - *self = Self::new(); + *self = Self::with_output_bits(self.bit_size); } fn output_bits(&self) -> usize { - $output_bits + self.bit_size } fn result(&mut self) -> DigestOutput { @@ -431,8 +442,14 @@ impl_digest_common!(Sha3_256, 256); impl_digest_common!(Sha3_384, 384); impl_digest_common!(Sha3_512, 512); -pub struct Shake128(sha3::Shake128); -pub struct Shake256(sha3::Shake256); +pub struct Shake128 { + digest: sha3::Shake128, + bit_size: usize, +} +pub struct Shake256 { + digest: sha3::Shake256, + bit_size: usize, +} impl_digest_shake!(Shake128, 256); impl_digest_shake!(Shake256, 512);