Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to stream-cipher v0.7 #164

Merged
merged 9 commits into from
Aug 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions aes-ctr/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.5.0 (2020-08-14)
## 0.5.0 (2020-08-25)
### Changed
- Bump `stream-cipher` dependency to v0.6, implement the `FromBlockCipher` trait ([#161])
- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164])

[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161
[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164

## 0.4.0 (2020-06-08)
### Changed
Expand Down
6 changes: 3 additions & 3 deletions aes-ctr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ readme = "README.md"
edition = "2018"

[dependencies]
stream-cipher = "0.6"
stream-cipher = "0.7"

[target.'cfg(not(all(target_feature = "aes", target_feature = "sse2", target_feature = "ssse3", any(target_arch = "x86_64", target_arch = "x86"))))'.dependencies]
ctr = { version = "0.5", path = "../ctr" }
aes-soft = "0.5"

[target.'cfg(all(target_feature = "aes", target_feature = "sse2", target_feature = "ssse3", any(target_arch = "x86_64", target_arch = "x86")))'.dependencies]
aesni = "0.8"
aesni = "0.9"

[dev-dependencies]
stream-cipher = { version = "0.6", features = ["dev"] }
stream-cipher = { version = "0.7", features = ["dev"] }
4 changes: 2 additions & 2 deletions aes-ctr/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ use aes_ctr::{Aes128Ctr, Aes256Ctr};
use stream_cipher::{new_seek_test, new_sync_test};

new_sync_test!(aes128_ctr_core, Aes128Ctr, "aes128-ctr");
new_seek_test!(aes128_ctr_seek, Aes128Ctr, "aes128-ctr");
new_sync_test!(aes256_ctr_core, Aes256Ctr, "aes256-ctr");
new_seek_test!(aes256_ctr_seek, Aes256Ctr, "aes256-ctr");
new_seek_test!(aes128_ctr_seek, Aes128Ctr);
new_seek_test!(aes256_ctr_seek, Aes256Ctr);
5 changes: 3 additions & 2 deletions cfb-mode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.5.0 (2020-08-14)
## 0.5.0 (2020-08-25)
### Changed
- Bump `stream-cipher` dependency to v0.6, implement the `FromBlockCipher` trait ([#161])
- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164])

[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161
[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164

## 0.4.0 (2020-06-08)
### Changed
Expand Down
4 changes: 2 additions & 2 deletions cfb-mode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ readme = "README.md"
edition = "2018"

[dependencies]
stream-cipher = { version = "0.6", features = ["block-cipher"] }
stream-cipher = { version = "0.7", features = ["block-cipher"] }

[dev-dependencies]
aes = "0.5"
stream-cipher = { version = "0.6", features = ["dev"] }
stream-cipher = { version = "0.7", features = ["dev"] }
hex-literal = "0.2"
5 changes: 3 additions & 2 deletions cfb8/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.5.0 (2020-08-14)
## 0.5.0 (2020-08-25)
### Changed
- Bump `stream-cipher` dependency to v0.6, implement the `FromBlockCipher` trait ([#161])
- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164])

[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161
[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164

## 0.4.0 (2020-06-08)
### Changed
Expand Down
4 changes: 2 additions & 2 deletions cfb8/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ readme = "README.md"
edition = "2018"

[dependencies]
stream-cipher = { version = "0.6", features = ["block-cipher"] }
stream-cipher = { version = "0.7", features = ["block-cipher"] }

[dev-dependencies]
aes = "0.5"
stream-cipher = { version = "0.6", features = ["dev"] }
stream-cipher = { version = "0.7", features = ["dev"] }
hex-literal = "0.2"
5 changes: 3 additions & 2 deletions chacha20/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.5.0 (2020-08-14)
## 0.5.0 (2020-08-25)
### Changed
- Bump `stream-cipher` dependency to v0.6 ([#161])
- Bump `stream-cipher` dependency to v0.7 ([#161], [#164])

[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161
[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164

## 0.4.3 (2020-06-11)
### Changed
Expand Down
4 changes: 2 additions & 2 deletions chacha20/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ edition = "2018"

[dependencies]
rand_core = { version = "0.5", optional = true, default-features = false }
stream-cipher = { version = "0.6", optional = true }
stream-cipher = { version = "0.7", optional = true }
zeroize = { version = "1", optional = true, default-features = false }

[dev-dependencies]
stream-cipher = { version = "0.6", features = ["dev"] }
stream-cipher = { version = "0.7", features = ["dev"] }
hex-literal = "0.2"

[features]
Expand Down
118 changes: 52 additions & 66 deletions chacha20/src/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ use crate::{
BLOCK_SIZE, MAX_BLOCKS,
};
use core::{
cmp,
convert::TryInto,
fmt::{self, Debug},
};
use stream_cipher::consts::{U12, U32};
use stream_cipher::{LoopError, NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek};
use stream_cipher::{
consts::{U12, U32},
LoopError, NewStreamCipher, OverflowError, SeekNum, SyncStreamCipher, SyncStreamCipherSeek,
};

/// ChaCha8 stream cipher (reduced-round variant of ChaCha20 with 8 rounds)
pub type ChaCha8 = Cipher<R8>;
Expand Down Expand Up @@ -61,7 +62,7 @@ pub struct Cipher<R: Rounds> {
buffer: Buffer,

/// Position within buffer, or `None` if the buffer is not in use
buffer_pos: Option<u8>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC this was a performance optimization for the AEAD use case, which is always able to work with ChaCha blocks-at-a-time until the last block.

It'd be good to double check how/if this impacts the performance of chacha20poly1305.

Copy link
Member Author

@newpavlov newpavlov Aug 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current API will not cache buffer if fed with data multiple of block size, so performance should be the same (though ofc worth checking out just to be safe). Note the if pos != 0 { .. } and if !rem.is_empty() { .. } parts. The Option<u8> (which I think you've borrowed from the ctr crate) approach was redundant, since buffer_pos will never be equal to Some(0).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After quick and dirty tests (i.e. without fiddling with hardware settings) the chacha20poly1305 performance is approximately the same on my CPU, with 2.605 cpb for decryption and 8.337 cpb for encryption (without enabled target-features).

buffer_pos: u8,

/// Current counter value relative to the start of the keystream
counter: u64,
Expand Down Expand Up @@ -93,7 +94,7 @@ impl<R: Rounds> NewStreamCipher for Cipher<R> {
Self {
block,
buffer: [0u8; BUFFER_SIZE],
buffer_pos: None,
buffer_pos: 0,
counter: 0,
counter_offset,
}
Expand All @@ -103,98 +104,83 @@ impl<R: Rounds> NewStreamCipher for Cipher<R> {
impl<R: Rounds> SyncStreamCipher for Cipher<R> {
fn try_apply_keystream(&mut self, mut data: &mut [u8]) -> Result<(), LoopError> {
self.check_data_len(data)?;
let pos = self.buffer_pos as usize;

let mut counter = self.counter;
// xor with leftover bytes from the last call if any
if let Some(pos) = self.buffer_pos {
let pos = pos as usize;

if data.len() >= BUFFER_SIZE - pos {
let buf = &self.buffer[pos..];
let (r, l) = data.split_at_mut(buf.len());
data = l;
xor(r, buf);
self.buffer_pos = None;
} else {
let buf = &self.buffer[pos..pos.checked_add(data.len()).unwrap()];
xor(data, buf);
self.buffer_pos = Some(pos.checked_add(data.len()).unwrap() as u8);
if pos != 0 {
if data.len() < BUFFER_SIZE - pos {
let n = pos + data.len();
xor(data, &self.buffer[pos..n]);
self.buffer_pos = n as u8;
return Ok(());
} else {
let (l, r) = data.split_at_mut(BUFFER_SIZE - pos);
data = r;
xor(l, &self.buffer[pos..]);
counter = counter.checked_add(COUNTER_INCR).unwrap();
}
}

let mut counter = self.counter;

while data.len() >= BUFFER_SIZE {
let (l, r) = { data }.split_at_mut(BUFFER_SIZE);
data = r;

let mut chunks = data.chunks_exact_mut(BUFFER_SIZE);
for chunk in &mut chunks {
// TODO(tarcieri): double check this should be checked and not wrapping
let counter_with_offset = self.counter_offset.checked_add(counter).unwrap();
self.block.apply_keystream(counter_with_offset, l);

self.block.apply_keystream(counter_with_offset, chunk);
counter = counter.checked_add(COUNTER_INCR).unwrap();
}

if !data.is_empty() {
let rem = chunks.into_remainder();
self.buffer_pos = rem.len() as u8;
self.counter = counter;
if !rem.is_empty() {
self.generate_block(counter);
counter = counter.checked_add(COUNTER_INCR).unwrap();
let n = data.len();
xor(data, &self.buffer[..n]);
self.buffer_pos = Some(n as u8);
xor(rem, &self.buffer[..rem.len()]);
}

self.counter = counter;

Ok(())
}
}

impl<R: Rounds> SyncStreamCipherSeek for Cipher<R> {
fn current_pos(&self) -> u64 {
let bs = BLOCK_SIZE as u64;

if let Some(pos) = self.buffer_pos {
(self.counter.wrapping_sub(1) * bs)
.checked_add(u64::from(pos))
.unwrap()
fn try_current_pos<T: SeekNum>(&self) -> Result<T, OverflowError> {
// quick and dirty fix, until ctr-like parallel block processing will be added
let (counter, pos) = if self.buffer_pos < BLOCK_SIZE as u8 {
(self.counter, self.buffer_pos)
} else {
self.counter * bs
}
(
self.counter.checked_add(1).ok_or(OverflowError)?,
self.buffer_pos - BLOCK_SIZE as u8,
)
};
T::from_block_byte(counter, pos, BLOCK_SIZE as u8)
}

fn seek(&mut self, pos: u64) {
let bs = BLOCK_SIZE as u64;
self.counter = pos / bs;
let rem = pos % bs;

if rem == 0 {
self.buffer_pos = None;
} else {
fn try_seek<T: SeekNum>(&mut self, pos: T) -> Result<(), LoopError> {
let res = pos.to_block_byte(BLOCK_SIZE as u8)?;
self.counter = res.0;
self.buffer_pos = res.1;
if self.buffer_pos != 0 {
self.generate_block(self.counter);
self.counter = self.counter.checked_add(COUNTER_INCR).unwrap();
self.buffer_pos = Some(rem as u8);
}
Ok(())
}
}

impl<R: Rounds> Cipher<R> {
/// Check data length
fn check_data_len(&self, data: &[u8]) -> Result<(), LoopError> {
let dlen = data.len()
- self
.buffer_pos
.map(|pos| cmp::min(BUFFER_SIZE - pos as usize, data.len()))
.unwrap_or_default();

let data_blocks = dlen / BLOCK_SIZE + if data.len() % BLOCK_SIZE != 0 { 1 } else { 0 };

if let Some(new_counter) = self.counter.checked_add(data_blocks as u64) {
if new_counter <= MAX_BLOCKS as u64 {
return Ok(());
}
let leftover_bytes = BUFFER_SIZE - self.buffer_pos as usize;
if data.len() < leftover_bytes {
return Ok(());
}
let blocks = 1 + (data.len() - leftover_bytes) / BLOCK_SIZE;
let res = self.counter.checked_add(blocks as u64).ok_or(LoopError)?;
if res <= MAX_BLOCKS as u64 {
Ok(())
} else {
Err(LoopError)
}

Err(LoopError)
}

/// Generate a block, storing it in the internal buffer
Expand Down
14 changes: 8 additions & 6 deletions chacha20/src/legacy.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Legacy version of ChaCha20 with a 64-bit nonce

use crate::cipher::{ChaCha20, Key};
use stream_cipher::consts::{U32, U8};
use stream_cipher::{LoopError, NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek};
use stream_cipher::{
consts::{U32, U8},
LoopError, NewStreamCipher, OverflowError, SeekNum, SyncStreamCipher, SyncStreamCipherSeek,
};

/// Size of the nonce for the legacy ChaCha20 stream cipher
#[cfg_attr(docsrs, doc(cfg(feature = "legacy")))]
Expand Down Expand Up @@ -35,11 +37,11 @@ impl SyncStreamCipher for ChaCha20Legacy {
}

impl SyncStreamCipherSeek for ChaCha20Legacy {
fn current_pos(&self) -> u64 {
self.0.current_pos()
fn try_current_pos<T: SeekNum>(&self) -> Result<T, OverflowError> {
self.0.try_current_pos()
}

fn seek(&mut self, pos: u64) {
self.0.seek(pos);
fn try_seek<T: SeekNum>(&mut self, pos: T) -> Result<(), LoopError> {
self.0.try_seek(pos)
}
}
Loading