From 656df2e445ad7c8cafc172b87c63f1f0b0c7d419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 16 Jun 2025 15:52:07 +0300 Subject: [PATCH 1/2] cipher: add methods for writing keystream --- cipher/src/stream.rs | 20 +++++++++++++ cipher/src/stream/wrapper.rs | 56 +++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/cipher/src/stream.rs b/cipher/src/stream.rs index 520837db5..a1ba34347 100644 --- a/cipher/src/stream.rs +++ b/cipher/src/stream.rs @@ -104,6 +104,16 @@ pub trait StreamCipher { self.try_apply_keystream_inout(buf.into()) } + /// Write keystream to `buf`. + /// + /// If end of the keystream will be achieved with the given data length, + /// method will return [`StreamCipherError`] without modifying provided `data`. + #[inline] + fn try_write_keystream(&mut self, buf: &mut [u8]) -> Result<(), StreamCipherError> { + buf.fill(0); + self.try_apply_keystream(buf) + } + /// Apply keystream to `inout` data. /// /// It will XOR generated keystream with the data behind `in` pointer @@ -130,6 +140,16 @@ pub trait StreamCipher { self.try_apply_keystream(buf).unwrap(); } + /// Write keystream to `buf`. + /// + /// # Panics + /// If end of the keystream will be reached with the given data length, + /// method will panic without modifying the provided `data`. + #[inline] + fn write_keystream(&mut self, buf: &mut [u8]) { + self.try_write_keystream(buf).unwrap(); + } + /// Apply keystream to data buffer-to-buffer. /// /// It will XOR generated keystream with data from the `input` buffer diff --git a/cipher/src/stream/wrapper.rs b/cipher/src/stream/wrapper.rs index e438ef4a4..9960979ed 100644 --- a/cipher/src/stream/wrapper.rs +++ b/cipher/src/stream/wrapper.rs @@ -3,7 +3,9 @@ use super::{ StreamCipherSeekCore, errors::StreamCipherError, }; use core::fmt; -use crypto_common::{Iv, IvSizeUser, Key, KeyInit, KeyIvInit, KeySizeUser, typenum::Unsigned}; +use crypto_common::{ + Iv, IvSizeUser, Key, KeyInit, KeyIvInit, KeySizeUser, array::Array, typenum::Unsigned, +}; use inout::InOutBuf; #[cfg(feature = "zeroize")] use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -170,6 +172,58 @@ impl StreamCipher for StreamCipherCoreWrapper { Ok(()) } + + #[inline] + fn try_write_keystream(&mut self, mut data: &mut [u8]) -> Result<(), StreamCipherError> { + self.check_remaining(data.len())?; + + let pos = usize::from(self.get_pos()); + let rem = usize::from(self.remaining()); + let data_len = data.len(); + + if rem != 0 { + if data_len <= rem { + data.copy_from_slice(&self.buffer[pos..][..data_len]); + // SAFETY: we have checked that `data_len` is less or equal to length + // of remaining keystream data, thus `pos + data_len` can not be bigger + // than block size. Since `pos` is never zero, `pos + data_len` can not + // be zero. Thus `pos + data_len` satisfies the safety invariant required + // by `set_pos_unchecked`. + unsafe { + self.set_pos_unchecked(pos + data_len); + } + return Ok(()); + } + let (left, right) = data.split_at_mut(rem); + data = right; + left.copy_from_slice(&self.buffer[pos..]); + } + + let (blocks, tail) = Array::slice_as_chunks_mut(data); + self.core.write_keystream_blocks(blocks); + + let new_pos = if tail.is_empty() { + T::BlockSize::USIZE + } else { + // Note that we temporarily write a pseudo-random byte into + // the first byte of `self.buffer`. It may break the safety invariant, + // but after XORing keystream block with `tail`, we immediately + // overwrite the first byte with a correct value. + self.core.write_keystream_block(&mut self.buffer); + tail.copy_from_slice(&self.buffer[..tail.len()]); + tail.len() + }; + + // SAFETY: `into_chunks` always returns tail with size + // less than block size. If `tail.len()` is zero, we replace + // it with block size. Thus the invariant required by + // `set_pos_unchecked` is satisfied. + unsafe { + self.set_pos_unchecked(new_pos); + } + + Ok(()) + } } impl StreamCipherSeek for StreamCipherCoreWrapper { From a2f2728bcfaa88e7b02f0d724e6ff12fb85afeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 16 Jun 2025 16:02:50 +0300 Subject: [PATCH 2/2] tweak comment --- cipher/src/stream/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cipher/src/stream/wrapper.rs b/cipher/src/stream/wrapper.rs index 9960979ed..3dbc7e1b5 100644 --- a/cipher/src/stream/wrapper.rs +++ b/cipher/src/stream/wrapper.rs @@ -207,7 +207,7 @@ impl StreamCipher for StreamCipherCoreWrapper { } else { // Note that we temporarily write a pseudo-random byte into // the first byte of `self.buffer`. It may break the safety invariant, - // but after XORing keystream block with `tail`, we immediately + // but after writing keystream block with `tail`, we immediately // overwrite the first byte with a correct value. self.core.write_keystream_block(&mut self.buffer); tail.copy_from_slice(&self.buffer[..tail.len()]);