Skip to content
Open
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
8 changes: 4 additions & 4 deletions salsa20/src/backends/soft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ use cipher::{
consts::{U1, U64},
};

pub(crate) struct Backend<'a, R: Unsigned>(pub(crate) &'a mut SalsaCore<R>);
pub(crate) struct Backend<'a, R: Unsigned, KeySize>(pub(crate) &'a mut SalsaCore<R, KeySize>);

impl<R: Unsigned> BlockSizeUser for Backend<'_, R> {
impl<R: Unsigned, KeySize> BlockSizeUser for Backend<'_, R, KeySize> {
type BlockSize = U64;
}

impl<R: Unsigned> ParBlocksSizeUser for Backend<'_, R> {
impl<R: Unsigned, KeySize> ParBlocksSizeUser for Backend<'_, R, KeySize> {
type ParBlocksSize = U1;
}

impl<R: Unsigned> StreamCipherBackend for Backend<'_, R> {
impl<R: Unsigned, KeySize> StreamCipherBackend for Backend<'_, R, KeySize> {
#[inline(always)]
fn gen_ks_block(&mut self, block: &mut Block<Self>) {
let res = run_rounds::<R>(&self.0.state);
Expand Down
5 changes: 3 additions & 2 deletions salsa20/src/backends/sse2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use core::arch::x86_64::*;

#[inline]
#[target_feature(enable = "sse2")]
pub(crate) unsafe fn inner<R, F>(state: &mut [u32; STATE_WORDS], f: F)
pub(crate) unsafe fn inner<R, F, KeySize>(state: &mut [u32; STATE_WORDS], f: F)
where
R: Unsigned,
F: StreamCipherClosure<BlockSize = U64>,
Expand All @@ -37,9 +37,10 @@ where
f.call(&mut backend);
state[8] = _mm_cvtsi128_si32(backend.v[2]) as u32;
} else {
f.call(&mut SoftBackend(&mut SalsaCore::<R> {
f.call(&mut SoftBackend(&mut SalsaCore::<R, KeySize> {
state: *state,
rounds: PhantomData,
key_size: PhantomData,
}));
}
}
Expand Down
125 changes: 100 additions & 25 deletions salsa20/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,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;

Expand All @@ -95,18 +95,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<SalsaCore<U4>>;
pub type Salsa8 = StreamCipherCoreWrapper<SalsaCore<U4, U32>>;

/// Salsa20/12 stream cipher
/// (reduced-round variant of Salsa20 with 12 rounds, *not recommended*)
pub type Salsa12 = StreamCipherCoreWrapper<SalsaCore<U6>>;
pub type Salsa12 = StreamCipherCoreWrapper<SalsaCore<U6, U32>>;

/// Salsa20/20 stream cipher
/// (20 rounds; **recommended**)
pub type Salsa20 = StreamCipherCoreWrapper<SalsaCore<U10>>;
pub type Salsa20 = StreamCipherCoreWrapper<SalsaCore<U10, U32>>;

/// 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<SalsaCore<U10, U16>>;

/// Key type used by all Salsa variants and [`XSalsa20`].
pub type Key = Array<u8, U32>;
pub type Key<KeySize = U32> = Array<u8, KeySize>;

/// Nonce type used by all Salsa variants.
pub type Nonce = Array<u8, U8>;
Expand All @@ -117,18 +128,23 @@ pub type XNonce = Array<u8, U24>;
/// 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<R: Unsigned> {
pub struct SalsaCore<R: Unsigned, KeySize = U32> {
/// Internal state of the core function
state: [u32; STATE_WORDS],
/// Number of rounds to perform
rounds: PhantomData<R>,
/// Key size
key_size: PhantomData<KeySize>,
}

impl<R: Unsigned> SalsaCore<R> {
impl<R: Unsigned, KeySize> SalsaCore<R, KeySize> {
/// Create new Salsa core from raw state.
///
/// This method is mainly intended for the `scrypt` crate.
Expand All @@ -137,46 +153,104 @@ impl<R: Unsigned> SalsaCore<R> {
Self {
state,
rounds: PhantomData,
key_size: PhantomData,
}
}
}

impl<R: Unsigned> KeySizeUser for SalsaCore<R> {
type KeySize = U32;
impl<R: Unsigned, KeySize> KeySizeUser for SalsaCore<R, KeySize>
where
KeySize: ArraySize,
{
type KeySize = KeySize;
}

impl<R: Unsigned> IvSizeUser for SalsaCore<R> {
impl<R: Unsigned, KeySize> IvSizeUser for SalsaCore<R, KeySize> {
type IvSize = U8;
}

impl<R: Unsigned> BlockSizeUser for SalsaCore<R> {
impl<R: Unsigned, KeySize> BlockSizeUser for SalsaCore<R, KeySize> {
type BlockSize = U64;
}

impl<R: Unsigned> KeyIvInit for SalsaCore<R> {
fn new(key: &Key, iv: &Nonce) -> Self {
impl<R: Unsigned> KeyIvInit for SalsaCore<R, U16> {
/// 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<U16>, 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<R: Unsigned> KeyIvInit for SalsaCore<R, U32> {
/// Create a new Salsa core using a 32-byte key.
fn new(key: &Key<U32>, 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());
}

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];

cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
Expand All @@ -192,11 +266,12 @@ impl<R: Unsigned> KeyIvInit for SalsaCore<R> {
Self {
state,
rounds: PhantomData,
key_size: PhantomData,
}
}
}

impl<R: Unsigned> StreamCipherCore for SalsaCore<R> {
impl<R: Unsigned, KeySize> StreamCipherCore for SalsaCore<R, KeySize> {
#[inline(always)]
fn remaining_blocks(&self) -> Option<usize> {
let rem = u64::MAX - self.get_block_pos();
Expand All @@ -206,7 +281,7 @@ impl<R: Unsigned> StreamCipherCore for SalsaCore<R> {
cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
unsafe {
backends::sse2::inner::<R, _>(&mut self.state, f);
backends::sse2::inner::<R, _, KeySize>(&mut self.state, f);
}
} else {
f.call(&mut backends::soft::Backend(self));
Expand All @@ -215,7 +290,7 @@ impl<R: Unsigned> StreamCipherCore for SalsaCore<R> {
}
}

impl<R: Unsigned> StreamCipherSeekCore for SalsaCore<R> {
impl<R: Unsigned, KeySize> StreamCipherSeekCore for SalsaCore<R, KeySize> {
type Counter = u64;

#[inline(always)]
Expand Down Expand Up @@ -246,11 +321,11 @@ impl<R: Unsigned> StreamCipherSeekCore for SalsaCore<R> {
}

#[cfg(feature = "zeroize")]
impl<R: Unsigned> Drop for SalsaCore<R> {
impl<R: Unsigned, KeySize> Drop for SalsaCore<R, KeySize> {
fn drop(&mut self) {
self.state.zeroize();
}
}

#[cfg(feature = "zeroize")]
impl<R: Unsigned> ZeroizeOnDrop for SalsaCore<R> {}
impl<R: Unsigned, KeySize> ZeroizeOnDrop for SalsaCore<R, KeySize> {}
16 changes: 8 additions & 8 deletions salsa20/src/xsalsa.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -25,7 +25,7 @@ pub type XSalsa12 = StreamCipherCoreWrapper<XSalsaCore<U6>>;
pub type XSalsa8 = StreamCipherCoreWrapper<XSalsaCore<U4>>;

/// The XSalsa core function.
pub struct XSalsaCore<R: Unsigned>(SalsaCore<R>);
pub struct XSalsaCore<R: Unsigned>(SalsaCore<R, U32>);

impl<R: Unsigned> KeySizeUser for XSalsaCore<R> {
type KeySize = U32;
Expand All @@ -41,7 +41,7 @@ impl<R: Unsigned> BlockSizeUser for XSalsaCore<R> {

impl<R: Unsigned> KeyIvInit for XSalsaCore<R> {
#[inline]
fn new(key: &Key, iv: &XNonce) -> Self {
fn new(key: &Key<U32>, iv: &XNonce) -> Self {
let subkey = hsalsa::<R>(key, iv[..16].try_into().unwrap());
let mut padded_iv = Nonce::default();
padded_iv.copy_from_slice(&iv[16..]);
Expand Down Expand Up @@ -89,29 +89,29 @@ impl<R: Unsigned> ZeroizeOnDrop for XSalsaCore<R> {}
/// - Nonce (`u32` x 4)
///
/// It produces 256-bits of output suitable for use as a Salsa20 key
pub fn hsalsa<R: Unsigned>(key: &Key, input: &Array<u8, U16>) -> Array<u8, U32> {
pub fn hsalsa<R: Unsigned>(key: &Key<U32>, input: &Array<u8, U16>) -> Array<u8, U32> {
#[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 {
Expand Down
Binary file added salsa20/tests/data/ecrypt16.blb
Binary file not shown.
26 changes: 25 additions & 1 deletion salsa20/tests/ecrypt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek, blobby};
use salsa20::Salsa20;
use salsa20::{Salsa20, Salsa20_16};

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

/// ECRYPT test vectors:
/// https://github.com/das-labor/legacy/blob/master/microcontroller-2/arm-crypto-lib/testvectors/salsa20-256.64-verified.test-vectors
Expand All @@ -24,3 +25,26 @@ fn salsa20_ecrypt() {
assert_eq!(buf, 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() {
let test_vectors = blobby::Blob4Iterator::new(DATA_16).unwrap();
for test_vector in test_vectors {
let [key, iv, pos, expected] = test_vector.unwrap();

println!("key length: {}", key.len());
let key = key.try_into().unwrap();
let iv = iv.try_into().unwrap();
let pos = u32::from_be_bytes(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);
}
}