diff --git a/salsa20/src/backends/soft.rs b/salsa20/src/backends/soft.rs index caf2693f..b7593071 100644 --- a/salsa20/src/backends/soft.rs +++ b/salsa20/src/backends/soft.rs @@ -7,17 +7,17 @@ use cipher::{ consts::{U1, U64}, }; -pub(crate) struct Backend<'a, R: Unsigned>(pub(crate) &'a mut SalsaCore); +pub(crate) struct Backend<'a, R: Unsigned, KeySize>(pub(crate) &'a mut SalsaCore); -impl BlockSizeUser for Backend<'_, R> { +impl BlockSizeUser for Backend<'_, R, KeySize> { type BlockSize = U64; } -impl ParBlocksSizeUser for Backend<'_, R> { +impl ParBlocksSizeUser for Backend<'_, R, KeySize> { type ParBlocksSize = U1; } -impl StreamCipherBackend for Backend<'_, R> { +impl StreamCipherBackend for Backend<'_, R, KeySize> { #[inline(always)] fn gen_ks_block(&mut self, block: &mut Block) { let res = run_rounds::(&self.0.state); diff --git a/salsa20/src/lib.rs b/salsa20/src/lib.rs index c75246f0..f8b5d07e 100644 --- a/salsa20/src/lib.rs +++ b/salsa20/src/lib.rs @@ -78,8 +78,8 @@ pub use cipher; use cipher::{ Block, BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure, StreamCipherCore, StreamCipherCoreWrapper, StreamCipherSeekCore, - array::{Array, typenum::Unsigned}, - consts::{U4, U6, U8, U10, U24, U32, U64}, + array::{Array, ArraySize, typenum::Unsigned}, + consts::{U4, U6, U8, U10, U16, U24, U32, U64}, }; use core::marker::PhantomData; @@ -93,18 +93,29 @@ pub use xsalsa::{XSalsa8, XSalsa12, XSalsa20, XSalsaCore, hsalsa}; /// Salsa20/8 stream cipher /// (reduced-round variant of Salsa20 with 8 rounds, *not recommended*) -pub type Salsa8 = StreamCipherCoreWrapper>; +pub type Salsa8 = StreamCipherCoreWrapper>; /// Salsa20/12 stream cipher /// (reduced-round variant of Salsa20 with 12 rounds, *not recommended*) -pub type Salsa12 = StreamCipherCoreWrapper>; +pub type Salsa12 = StreamCipherCoreWrapper>; /// Salsa20/20 stream cipher /// (20 rounds; **recommended**) -pub type Salsa20 = StreamCipherCoreWrapper>; +pub type Salsa20 = StreamCipherCoreWrapper>; + +/// Salsa20/20 stream cipher, using 16-byte keys (*not recommended*) +/// +/// # ⚠️ Security warning +/// +/// Using Salsa20 with keys shorter than 32 bytes is +/// [**explicitly discouraged** by its creator][0]. It is included for +/// compatibility with systems that use these weaker keys. +/// +/// [0]: https://cr.yp.to/snuffle/keysizes.pdf +pub type Salsa20_16 = StreamCipherCoreWrapper>; /// Key type used by all Salsa variants and [`XSalsa20`]. -pub type Key = Array; +pub type Key = Array; /// Nonce type used by all Salsa variants. pub type Nonce = Array; @@ -115,18 +126,23 @@ pub type XNonce = Array; /// Number of 32-bit words in the Salsa20 state const STATE_WORDS: usize = 16; -/// State initialization constant ("expand 32-byte k") -const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]; +/// State initialization constant for 16-byte keys ("expand 16-byte k") +const CONSTANTS_16: [u32; 4] = [0x6170_7865, 0x3120_646e, 0x7962_2d36, 0x6b20_6574]; + +/// State initialization constant for 32-byte keys ("expand 32-byte k") +const CONSTANTS_32: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]; /// The Salsa20 core function. -pub struct SalsaCore { +pub struct SalsaCore { /// Internal state of the core function state: [u32; STATE_WORDS], /// Number of rounds to perform rounds: PhantomData, + /// Key size + key_size: PhantomData, } -impl SalsaCore { +impl SalsaCore { /// Create new Salsa core from raw state. /// /// This method is mainly intended for the `scrypt` crate. @@ -135,32 +151,79 @@ impl SalsaCore { Self { state, rounds: PhantomData, + key_size: PhantomData, } } } -impl KeySizeUser for SalsaCore { - type KeySize = U32; +impl KeySizeUser for SalsaCore +where + KeySize: ArraySize, +{ + type KeySize = KeySize; } -impl IvSizeUser for SalsaCore { +impl IvSizeUser for SalsaCore { type IvSize = U8; } -impl BlockSizeUser for SalsaCore { +impl BlockSizeUser for SalsaCore { type BlockSize = U64; } -impl KeyIvInit for SalsaCore { - fn new(key: &Key, iv: &Nonce) -> Self { +impl KeyIvInit for SalsaCore { + /// Create a new Salsa core using a _weaker_ 16-byte key. + /// + /// # ⚠️ Security warning + /// + /// Using Salsa20 with keys shorter than 32 bytes is + /// [**explicitly discouraged** by its creator][0]. It is included for + /// compatibility with systems that use these weaker keys. + /// + /// [0]: https://cr.yp.to/snuffle/keysizes.pdf + fn new(key: &Key, iv: &Nonce) -> Self { + let mut state = [0u32; STATE_WORDS]; + state[0] = CONSTANTS_16[0]; + + for (i, chunk) in key.chunks(4).enumerate() { + state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + state[5] = CONSTANTS_16[1]; + + for (i, chunk) in iv.chunks(4).enumerate() { + state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + state[8] = 0; + state[9] = 0; + state[10] = CONSTANTS_16[2]; + + for (i, chunk) in key.chunks(4).enumerate() { + state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + state[15] = CONSTANTS_16[3]; + + Self { + state, + rounds: PhantomData, + key_size: PhantomData, + } + } +} + +impl KeyIvInit for SalsaCore { + /// Create a new Salsa core using a 32-byte key. + fn new(key: &Key, iv: &Nonce) -> Self { let mut state = [0u32; STATE_WORDS]; - state[0] = CONSTANTS[0]; + state[0] = CONSTANTS_32[0]; for (i, chunk) in key[..16].chunks(4).enumerate() { state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); } - state[5] = CONSTANTS[1]; + state[5] = CONSTANTS_32[1]; for (i, chunk) in iv.chunks(4).enumerate() { state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); @@ -168,22 +231,23 @@ impl KeyIvInit for SalsaCore { state[8] = 0; state[9] = 0; - state[10] = CONSTANTS[2]; + state[10] = CONSTANTS_32[2]; for (i, chunk) in key[16..].chunks(4).enumerate() { state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); } - state[15] = CONSTANTS[3]; + state[15] = CONSTANTS_32[3]; Self { state, rounds: PhantomData, + key_size: PhantomData, } } } -impl StreamCipherCore for SalsaCore { +impl StreamCipherCore for SalsaCore { #[inline(always)] fn remaining_blocks(&self) -> Option { let rem = u64::MAX - self.get_block_pos(); @@ -194,7 +258,7 @@ impl StreamCipherCore for SalsaCore { } } -impl StreamCipherSeekCore for SalsaCore { +impl StreamCipherSeekCore for SalsaCore { type Counter = u64; #[inline(always)] @@ -210,11 +274,11 @@ impl StreamCipherSeekCore for SalsaCore { } #[cfg(feature = "zeroize")] -impl Drop for SalsaCore { +impl Drop for SalsaCore { fn drop(&mut self) { self.state.zeroize(); } } #[cfg(feature = "zeroize")] -impl ZeroizeOnDrop for SalsaCore {} +impl ZeroizeOnDrop for SalsaCore {} diff --git a/salsa20/src/xsalsa.rs b/salsa20/src/xsalsa.rs index 9f84cef5..a43f2eb5 100644 --- a/salsa20/src/xsalsa.rs +++ b/salsa20/src/xsalsa.rs @@ -1,6 +1,6 @@ //! XSalsa20 is an extended nonce variant of Salsa20 -use super::{CONSTANTS, Key, Nonce, SalsaCore, Unsigned, XNonce}; +use super::{CONSTANTS_32, Key, Nonce, SalsaCore, Unsigned, XNonce}; use cipher::{ BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure, StreamCipherCore, StreamCipherCoreWrapper, StreamCipherSeekCore, @@ -25,7 +25,7 @@ pub type XSalsa12 = StreamCipherCoreWrapper>; pub type XSalsa8 = StreamCipherCoreWrapper>; /// The XSalsa core function. -pub struct XSalsaCore(SalsaCore); +pub struct XSalsaCore(SalsaCore); impl KeySizeUser for XSalsaCore { type KeySize = U32; @@ -41,7 +41,7 @@ impl BlockSizeUser for XSalsaCore { impl KeyIvInit for XSalsaCore { #[inline] - fn new(key: &Key, iv: &XNonce) -> Self { + fn new(key: &Key, iv: &XNonce) -> Self { let subkey = hsalsa::(key, iv[..16].try_into().unwrap()); let mut padded_iv = Nonce::default(); padded_iv.copy_from_slice(&iv[16..]); @@ -89,29 +89,29 @@ impl ZeroizeOnDrop for XSalsaCore {} /// - Nonce (`u32` x 4) /// /// It produces 256-bits of output suitable for use as a Salsa20 key -pub fn hsalsa(key: &Key, input: &Array) -> Array { +pub fn hsalsa(key: &Key, input: &Array) -> Array { #[inline(always)] fn to_u32(chunk: &[u8]) -> u32 { u32::from_le_bytes(chunk.try_into().unwrap()) } let mut state = [0u32; 16]; - state[0] = CONSTANTS[0]; + state[0] = CONSTANTS_32[0]; state[1..5] .iter_mut() .zip(key[0..16].chunks_exact(4)) .for_each(|(v, chunk)| *v = to_u32(chunk)); - state[5] = CONSTANTS[1]; + state[5] = CONSTANTS_32[1]; state[6..10] .iter_mut() .zip(input.chunks_exact(4)) .for_each(|(v, chunk)| *v = to_u32(chunk)); - state[10] = CONSTANTS[2]; + state[10] = CONSTANTS_32[2]; state[11..15] .iter_mut() .zip(key[16..].chunks_exact(4)) .for_each(|(v, chunk)| *v = to_u32(chunk)); - state[15] = CONSTANTS[3]; + state[15] = CONSTANTS_32[3]; // 20 rounds consisting of 10 column rounds and 10 diagonal rounds for _ in 0..R::USIZE { diff --git a/salsa20/tests/data/ecrypt16.blb b/salsa20/tests/data/ecrypt16.blb new file mode 100644 index 00000000..6f4b81ac Binary files /dev/null and b/salsa20/tests/data/ecrypt16.blb differ diff --git a/salsa20/tests/ecrypt.rs b/salsa20/tests/ecrypt.rs index 48c87504..9264b026 100644 --- a/salsa20/tests/ecrypt.rs +++ b/salsa20/tests/ecrypt.rs @@ -1,5 +1,5 @@ use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek, blobby}; -use salsa20::Salsa20; +use salsa20::{Salsa20, Salsa20_16}; /// ECRYPT test vectors: /// https://github.com/das-labor/legacy/blob/master/microcontroller-2/arm-crypto-lib/testvectors/salsa20-256.64-verified.test-vectors @@ -27,3 +27,30 @@ fn salsa20_ecrypt() { assert_eq!(buf, tv.expected); } } + +/// ECRYPT test vectors: +/// https://github.com/das-labor/legacy/blob/master/microcontroller-2/arm-crypto-lib/testvectors/salsa20-128.64-verified.test-vectors +#[test] +fn salsa20_ecrypt16() { + blobby::parse_into_structs!( + include_bytes!("data/ecrypt16.blb"); + #[define_struct] + static TEST_VECTORS: &[ + TestVector { key, iv, pos, expected } + ]; + ); + + for tv in TEST_VECTORS { + let key = tv.key.try_into().unwrap(); + let iv = tv.iv.try_into().unwrap(); + let pos = u32::from_be_bytes(tv.pos.try_into().unwrap()); + + let mut c = Salsa20_16::new(key, iv); + c.seek(pos); + + let mut buf = [0u8; 64]; + c.apply_keystream(&mut buf); + + assert_eq!(buf, tv.expected); + } +}