Skip to content

Commit cdd8f9d

Browse files
committed
salsa20: add support for 16-byte keys, and first test vector (#432)
1 parent 1863b0d commit cdd8f9d

File tree

4 files changed

+110
-16
lines changed

4 files changed

+110
-16
lines changed

salsa20/src/lib.rs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ use cipher::{
7979
Block, BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure,
8080
StreamCipherCore, StreamCipherCoreWrapper, StreamCipherSeekCore,
8181
array::{Array, ArraySize, typenum::Unsigned},
82-
consts::{U4, U6, U8, U10, U24, U32, U64},
82+
consts::{U4, U6, U8, U10, U16, U24, U32, U64},
8383
};
8484
use core::marker::PhantomData;
8585

@@ -103,8 +103,19 @@ pub type Salsa12 = StreamCipherCoreWrapper<SalsaCore<U6, U32>>;
103103
/// (20 rounds; **recommended**)
104104
pub type Salsa20 = StreamCipherCoreWrapper<SalsaCore<U10, U32>>;
105105

106+
/// Salsa20/20 stream cipher, using 16-byte keys (*not recommended*)
107+
///
108+
/// # ⚠️ Security warning
109+
///
110+
/// Using Salsa20 with keys shorter than 32 bytes is
111+
/// [**explicitly discouraged** by its creator][0]. It is included for
112+
/// compatibility with systems that use these weaker keys.
113+
///
114+
/// [0]: https://cr.yp.to/snuffle/keysizes.pdf
115+
pub type Salsa20_16 = StreamCipherCoreWrapper<SalsaCore<U10, U16>>;
116+
106117
/// Key type used by all Salsa variants and [`XSalsa20`].
107-
pub type Key<KeySize> = Array<u8, KeySize>;
118+
pub type Key<KeySize = U32> = Array<u8, KeySize>;
108119

109120
/// Nonce type used by all Salsa variants.
110121
pub type Nonce = Array<u8, U8>;
@@ -115,8 +126,11 @@ pub type XNonce = Array<u8, U24>;
115126
/// Number of 32-bit words in the Salsa20 state
116127
const STATE_WORDS: usize = 16;
117128

118-
/// State initialization constant ("expand 32-byte k")
119-
const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
129+
/// State initialization constant for 32-byte keys ("expand 32-byte k")
130+
const CONSTANTS_32: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
131+
132+
/// State initialization constant for 16-byte keys ("expand 16-byte k")
133+
const CONSTANTS_16: [u32; 4] = [0x6170_7865, 0x3120_646e, 0x7962_2d36, 0x6b20_6574];
120134

121135
/// The Salsa20 core function.
122136
pub struct SalsaCore<R: Unsigned, KeySize = U32> {
@@ -157,31 +171,84 @@ impl<R: Unsigned, KeySize> BlockSizeUser for SalsaCore<R, KeySize> {
157171
type BlockSize = U64;
158172
}
159173

160-
impl<R: Unsigned> KeyIvInit for SalsaCore<R, U32>
161-
{
174+
impl<R: Unsigned> KeyIvInit for SalsaCore<R, U16> {
175+
/// Create a new Salsa core using a _weaker_ 16-byte key.
176+
///
177+
/// # ⚠️ Security warning
178+
///
179+
/// Using Salsa20 with keys shorter than 32 bytes is
180+
/// [**explicitly discouraged** by its creator][0]. It is included for
181+
/// compatibility with systems that use these weaker keys.
182+
///
183+
/// [0]: https://cr.yp.to/snuffle/keysizes.pdf
184+
fn new(key: &Key<U16>, iv: &Nonce) -> Self {
185+
let mut state = [0u32; STATE_WORDS];
186+
state[0] = CONSTANTS_16[0];
187+
188+
for (i, chunk) in key.chunks(4).enumerate() {
189+
state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
190+
}
191+
192+
state[5] = CONSTANTS_16[1];
193+
194+
for (i, chunk) in iv.chunks(4).enumerate() {
195+
state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
196+
}
197+
198+
state[8] = 0;
199+
state[9] = 0;
200+
state[10] = CONSTANTS_16[2];
201+
202+
for (i, chunk) in key.chunks(4).enumerate() {
203+
state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
204+
}
205+
206+
state[15] = CONSTANTS_16[3];
207+
208+
cfg_if! {
209+
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
210+
state = [
211+
state[0], state[5], state[10], state[15],
212+
state[4], state[9], state[14], state[3],
213+
state[8], state[13], state[2], state[7],
214+
state[12], state[1], state[6], state[11],
215+
];
216+
}
217+
}
218+
219+
Self {
220+
state,
221+
rounds: PhantomData,
222+
key_size: PhantomData,
223+
}
224+
}
225+
}
226+
227+
impl<R: Unsigned> KeyIvInit for SalsaCore<R, U32> {
228+
/// Create a new Salsa core using a 32-byte key.
162229
fn new(key: &Key<U32>, iv: &Nonce) -> Self {
163230
let mut state = [0u32; STATE_WORDS];
164-
state[0] = CONSTANTS[0];
231+
state[0] = CONSTANTS_32[0];
165232

166233
for (i, chunk) in key[..16].chunks(4).enumerate() {
167234
state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
168235
}
169236

170-
state[5] = CONSTANTS[1];
237+
state[5] = CONSTANTS_32[1];
171238

172239
for (i, chunk) in iv.chunks(4).enumerate() {
173240
state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
174241
}
175242

176243
state[8] = 0;
177244
state[9] = 0;
178-
state[10] = CONSTANTS[2];
245+
state[10] = CONSTANTS_32[2];
179246

180247
for (i, chunk) in key[16..].chunks(4).enumerate() {
181248
state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
182249
}
183250

184-
state[15] = CONSTANTS[3];
251+
state[15] = CONSTANTS_32[3];
185252

186253
Self {
187254
state,

salsa20/src/xsalsa.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! XSalsa20 is an extended nonce variant of Salsa20
22
3-
use super::{CONSTANTS, Key, Nonce, SalsaCore, Unsigned, XNonce};
3+
use super::{CONSTANTS_32, Key, Nonce, SalsaCore, Unsigned, XNonce};
44
use cipher::{
55
BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure, StreamCipherCore,
66
StreamCipherCoreWrapper, StreamCipherSeekCore,
@@ -96,22 +96,22 @@ pub fn hsalsa<R: Unsigned>(key: &Key<U32>, input: &Array<u8, U16>) -> Array<u8,
9696
}
9797

9898
let mut state = [0u32; 16];
99-
state[0] = CONSTANTS[0];
99+
state[0] = CONSTANTS_32[0];
100100
state[1..5]
101101
.iter_mut()
102102
.zip(key[0..16].chunks_exact(4))
103103
.for_each(|(v, chunk)| *v = to_u32(chunk));
104-
state[5] = CONSTANTS[1];
104+
state[5] = CONSTANTS_32[1];
105105
state[6..10]
106106
.iter_mut()
107107
.zip(input.chunks_exact(4))
108108
.for_each(|(v, chunk)| *v = to_u32(chunk));
109-
state[10] = CONSTANTS[2];
109+
state[10] = CONSTANTS_32[2];
110110
state[11..15]
111111
.iter_mut()
112112
.zip(key[16..].chunks_exact(4))
113113
.for_each(|(v, chunk)| *v = to_u32(chunk));
114-
state[15] = CONSTANTS[3];
114+
state[15] = CONSTANTS_32[3];
115115

116116
// 20 rounds consisting of 10 column rounds and 10 diagonal rounds
117117
for _ in 0..R::USIZE {

salsa20/tests/data/ecrypt16.blb

319 Bytes
Binary file not shown.

salsa20/tests/ecrypt.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek, blobby};
2-
use salsa20::Salsa20;
2+
use salsa20::{Salsa20, Salsa20_16};
33

44
/// ECRYPT test vectors:
55
/// 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() {
2727
assert_eq!(buf, tv.expected);
2828
}
2929
}
30+
31+
/// ECRYPT test vectors:
32+
/// https://github.com/das-labor/legacy/blob/master/microcontroller-2/arm-crypto-lib/testvectors/salsa20-128.64-verified.test-vectors
33+
#[test]
34+
fn salsa20_ecrypt16() {
35+
blobby::parse_into_structs!(
36+
include_bytes!("data/ecrypt16.blb");
37+
#[define_struct]
38+
static TEST_VECTORS: &[
39+
TestVector { key, iv, pos, expected }
40+
];
41+
);
42+
43+
for tv in TEST_VECTORS {
44+
let key = tv.key.try_into().unwrap();
45+
let iv = tv.iv.try_into().unwrap();
46+
let pos = u32::from_be_bytes(tv.pos.try_into().unwrap());
47+
48+
let mut c = Salsa20_16::new(key, iv);
49+
c.seek(pos);
50+
51+
let mut buf = [0u8; 64];
52+
c.apply_keystream(&mut buf);
53+
54+
assert_eq!(buf, expected);
55+
}
56+
}

0 commit comments

Comments
 (0)