Skip to content

Commit d336b61

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

File tree

4 files changed

+107
-16
lines changed

4 files changed

+107
-16
lines changed

salsa20/src/lib.rs

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

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

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

111122
/// Nonce type used by all Salsa variants.
112123
pub type Nonce = Array<u8, U8>;
@@ -117,8 +128,11 @@ pub type XNonce = Array<u8, U24>;
117128
/// Number of 32-bit words in the Salsa20 state
118129
const STATE_WORDS: usize = 16;
119130

120-
/// State initialization constant ("expand 32-byte k")
121-
const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
131+
/// State initialization constant for 32-byte keys ("expand 32-byte k")
132+
const CONSTANTS_32: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
133+
134+
/// State initialization constant for 16-byte keys ("expand 16-byte k")
135+
const CONSTANTS_16: [u32; 4] = [0x6170_7865, 0x3120_646e, 0x7962_2d36, 0x6b20_6574];
122136

123137
/// The Salsa20 core function.
124138
pub struct SalsaCore<R: Unsigned, KeySize = U32> {
@@ -159,31 +173,84 @@ impl<R: Unsigned, KeySize> BlockSizeUser for SalsaCore<R, KeySize> {
159173
type BlockSize = U64;
160174
}
161175

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

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

172-
state[5] = CONSTANTS[1];
239+
state[5] = CONSTANTS_32[1];
173240

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

178245
state[8] = 0;
179246
state[9] = 0;
180-
state[10] = CONSTANTS[2];
247+
state[10] = CONSTANTS_32[2];
181248

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

186-
state[15] = CONSTANTS[3];
253+
state[15] = CONSTANTS_32[3];
187254

188255
cfg_if! {
189256
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {

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: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek, blobby};
2-
use salsa20::Salsa20;
2+
use salsa20::{Salsa20, Salsa20_16};
33

44
static DATA: &[u8] = include_bytes!("data/ecrypt.blb");
5+
static DATA_16: &[u8] = include_bytes!("data/ecrypt16.blb");
56

67
/// ECRYPT test vectors:
78
/// https://github.com/das-labor/legacy/blob/master/microcontroller-2/arm-crypto-lib/testvectors/salsa20-256.64-verified.test-vectors
@@ -24,3 +25,26 @@ fn salsa20_ecrypt() {
2425
assert_eq!(buf, expected);
2526
}
2627
}
28+
29+
/// ECRYPT test vectors:
30+
/// https://github.com/das-labor/legacy/blob/master/microcontroller-2/arm-crypto-lib/testvectors/salsa20-128.64-verified.test-vectors
31+
#[test]
32+
fn salsa20_ecrypt16() {
33+
let test_vectors = blobby::Blob4Iterator::new(DATA_16).unwrap();
34+
for test_vector in test_vectors {
35+
let [key, iv, pos, expected] = test_vector.unwrap();
36+
37+
println!("key length: {}", key.len());
38+
let key = key.try_into().unwrap();
39+
let iv = iv.try_into().unwrap();
40+
let pos = u32::from_be_bytes(pos.try_into().unwrap());
41+
42+
let mut c = Salsa20_16::new(key, iv);
43+
c.seek(pos);
44+
45+
let mut buf = [0u8; 64];
46+
c.apply_keystream(&mut buf);
47+
48+
assert_eq!(buf, expected);
49+
}
50+
}

0 commit comments

Comments
 (0)