From 1863b0db518138d835cba44af36bd5a34f22c16e Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sat, 9 Aug 2025 12:00:28 +1000 Subject: [PATCH 1/6] salsa20: add type param for key length (#432) --- salsa20/src/backends/soft.rs | 8 ++++---- salsa20/src/lib.rs | 38 ++++++++++++++++++++++-------------- salsa20/src/xsalsa.rs | 6 +++--- 3 files changed, 30 insertions(+), 22 deletions(-) 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..c03c44eb 100644 --- a/salsa20/src/lib.rs +++ b/salsa20/src/lib.rs @@ -78,7 +78,7 @@ pub use cipher; use cipher::{ Block, BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure, StreamCipherCore, StreamCipherCoreWrapper, StreamCipherSeekCore, - array::{Array, typenum::Unsigned}, + array::{Array, ArraySize, typenum::Unsigned}, consts::{U4, U6, U8, U10, U24, U32, U64}, }; use core::marker::PhantomData; @@ -93,18 +93,18 @@ 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>; /// 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; @@ -119,14 +119,16 @@ const STATE_WORDS: usize = 16; const CONSTANTS: [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,24 +137,29 @@ 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 +{ + fn new(key: &Key, iv: &Nonce) -> Self { let mut state = [0u32; STATE_WORDS]; state[0] = CONSTANTS[0]; @@ -179,11 +186,12 @@ impl KeyIvInit for SalsaCore { 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 +202,7 @@ impl StreamCipherCore for SalsaCore { } } -impl StreamCipherSeekCore for SalsaCore { +impl StreamCipherSeekCore for SalsaCore { type Counter = u64; #[inline(always)] diff --git a/salsa20/src/xsalsa.rs b/salsa20/src/xsalsa.rs index 9f84cef5..14b2e789 100644 --- a/salsa20/src/xsalsa.rs +++ b/salsa20/src/xsalsa.rs @@ -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,7 +89,7 @@ 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()) From cdd8f9db3dbad060fbf03df21f39b5529bc61e10 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sat, 9 Aug 2025 12:34:46 +1000 Subject: [PATCH 2/6] salsa20: add support for 16-byte keys, and first test vector (#432) --- salsa20/src/lib.rs | 87 ++++++++++++++++++++++++++++---- salsa20/src/xsalsa.rs | 10 ++-- salsa20/tests/data/ecrypt16.blb | Bin 0 -> 319 bytes salsa20/tests/ecrypt.rs | 29 ++++++++++- 4 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 salsa20/tests/data/ecrypt16.blb diff --git a/salsa20/src/lib.rs b/salsa20/src/lib.rs index c03c44eb..602d2971 100644 --- a/salsa20/src/lib.rs +++ b/salsa20/src/lib.rs @@ -79,7 +79,7 @@ use cipher::{ Block, BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure, StreamCipherCore, StreamCipherCoreWrapper, StreamCipherSeekCore, array::{Array, ArraySize, typenum::Unsigned}, - consts::{U4, U6, U8, U10, U24, U32, U64}, + consts::{U4, U6, U8, U10, U16, U24, U32, U64}, }; use core::marker::PhantomData; @@ -103,8 +103,19 @@ pub type Salsa12 = StreamCipherCoreWrapper>; /// (20 rounds; **recommended**) 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,8 +126,11 @@ 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 32-byte keys ("expand 32-byte k") +const CONSTANTS_32: [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]; /// The Salsa20 core function. pub struct SalsaCore { @@ -157,17 +171,70 @@ impl BlockSizeUser for SalsaCore { type BlockSize = U64; } -impl KeyIvInit for SalsaCore -{ +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]; + + cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + state = [ + state[0], state[5], state[10], state[15], + state[4], state[9], state[14], state[3], + state[8], state[13], state[2], state[7], + state[12], state[1], state[6], state[11], + ]; + } + } + + 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()); @@ -175,13 +242,13 @@ 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, diff --git a/salsa20/src/xsalsa.rs b/salsa20/src/xsalsa.rs index 14b2e789..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, @@ -96,22 +96,22 @@ pub fn hsalsa(key: &Key, input: &Array) -> Array@%DyQ5Q7dhFx;9W+W1BD!2|o@8q^CwR+!^F8n6#s6?Zn=;Sk-U!3mmeW=30%J}ZJ#T@U`ndRrF zf(>G1Xke%f?@X5466(V6@PX>&*k9Fl6D+Su--%d}S!>o9{c%Rj`}) zP3&Bu`yY;#R%Ue^$@0nvtQl6^(uWuXbkXM00&~k-=dRp!i2Pf4_(=VspOdE*{JOFv uA#LrMtT5LKV~(Btzl-*9D}3) literal 0 HcmV?d00001 diff --git a/salsa20/tests/ecrypt.rs b/salsa20/tests/ecrypt.rs index 48c87504..2f3c995b 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, expected); + } +} From 882701ec095954ef97f4d06e47732d88bf284ac2 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sat, 9 Aug 2025 13:02:38 +1000 Subject: [PATCH 3/6] salsa20: add other 16 byte test vectors --- salsa20/tests/data/ecrypt16.blb | Bin 319 -> 26158 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/salsa20/tests/data/ecrypt16.blb b/salsa20/tests/data/ecrypt16.blb index d5c7bf95154b5a74050ebb7042c6eb94cf297133..6f4b81ac741743b3cb07e11a38291fe0edc3d0e4 100644 GIT binary patch literal 26158 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 delta 80 zcmZ2?hH*cW5|cm!11jKvaT%Gx%*i`ho!BAl1C#&3*o+L5o!Ow`j0YxnusHz$iP#FP From 8a67d709e63baceb7ca92103c0c19ca7c966ff1b Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sat, 9 Aug 2025 13:12:43 +1000 Subject: [PATCH 4/6] fmt --- salsa20/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/salsa20/src/lib.rs b/salsa20/src/lib.rs index 602d2971..406a7bbc 100644 --- a/salsa20/src/lib.rs +++ b/salsa20/src/lib.rs @@ -126,12 +126,12 @@ pub type XNonce = Array; /// Number of 32-bit words in the Salsa20 state const STATE_WORDS: usize = 16; -/// State initialization constant for 32-byte keys ("expand 32-byte k") -const CONSTANTS_32: [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 { /// Internal state of the core function From 78a86e57481b61249e94ffc828ccbeb38087667f Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sat, 9 Aug 2025 13:25:23 +1000 Subject: [PATCH 5/6] salsa20: fix building with zeroize --- salsa20/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/salsa20/src/lib.rs b/salsa20/src/lib.rs index 406a7bbc..bd60e4b0 100644 --- a/salsa20/src/lib.rs +++ b/salsa20/src/lib.rs @@ -285,11 +285,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 {} From 147b5882c00c21f8d5ab25b8f33aa05318acd47f Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Mon, 8 Sep 2025 14:37:39 +1000 Subject: [PATCH 6/6] salsa20: fix more merge conflicts --- salsa20/src/lib.rs | 11 ----------- salsa20/tests/ecrypt.rs | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/salsa20/src/lib.rs b/salsa20/src/lib.rs index bd60e4b0..f8b5d07e 100644 --- a/salsa20/src/lib.rs +++ b/salsa20/src/lib.rs @@ -205,17 +205,6 @@ impl KeyIvInit for SalsaCore { state[15] = CONSTANTS_16[3]; - cfg_if! { - if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { - state = [ - state[0], state[5], state[10], state[15], - state[4], state[9], state[14], state[3], - state[8], state[13], state[2], state[7], - state[12], state[1], state[6], state[11], - ]; - } - } - Self { state, rounds: PhantomData, diff --git a/salsa20/tests/ecrypt.rs b/salsa20/tests/ecrypt.rs index 2f3c995b..9264b026 100644 --- a/salsa20/tests/ecrypt.rs +++ b/salsa20/tests/ecrypt.rs @@ -51,6 +51,6 @@ fn salsa20_ecrypt16() { let mut buf = [0u8; 64]; c.apply_keystream(&mut buf); - assert_eq!(buf, expected); + assert_eq!(buf, tv.expected); } }