encryption/encryptor/
salsa20.rs1use cbc::cipher::{KeyIvInit, StreamCipher};
8use rand_core::{OsRng, RngCore};
9use salsa20::Salsa20;
10
11use crate::contract::{Identified, SymmetricCipher};
12use crate::error::Result;
13use crate::internal::etm;
14use crate::key::Key;
15
16const LABEL: &str = "Salsa20";
17const NONCE_LEN: usize = 8;
19
20#[derive(Debug)]
22pub struct Salsa20Encryptor {
23 key: Key<32>,
24}
25
26impl Salsa20Encryptor {
27 pub const IDENTIFIER: &'static str = "salsa20";
28
29 pub fn new(key: [u8; 32]) -> Self {
30 Self { key: Key::new(key) }
31 }
32
33 pub fn from_slice(key: &[u8]) -> Result<Self> {
34 Ok(Self {
35 key: Key::from_slice(key, LABEL)?,
36 })
37 }
38}
39
40impl Identified for Salsa20Encryptor {
41 fn identifier(&self) -> &str {
42 Self::IDENTIFIER
43 }
44}
45
46impl SymmetricCipher for Salsa20Encryptor {
47 fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
48 let mut nonce = [0u8; NONCE_LEN];
49 OsRng.fill_bytes(&mut nonce);
50
51 let mut buffer = plaintext.to_vec();
52 Salsa20::new_from_slices(self.key.as_bytes(), &nonce)
53 .expect("key and nonce lengths are fixed")
54 .apply_keystream(&mut buffer);
55
56 Ok(etm::pack(&nonce, &buffer, self.key.as_bytes()))
57 }
58
59 fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
60 let (nonce, body) =
61 etm::unpack_and_verify(ciphertext, NONCE_LEN, self.key.as_bytes(), LABEL)?;
62
63 let mut buffer = body.to_vec();
64 Salsa20::new_from_slices(self.key.as_bytes(), nonce)
65 .expect("key and nonce lengths were verified")
66 .apply_keystream(&mut buffer);
67 Ok(buffer)
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::error::Error;
75
76 fn unhex(s: &str) -> Vec<u8> {
78 (0..s.len() / 2)
79 .map(|i| u8::from_str_radix(&s[2 * i..2 * i + 2], 16).unwrap())
80 .collect()
81 }
82
83 #[test]
90 fn bare_keystream_matches_estream_vectors() {
91 let key = unhex("8000000000000000000000000000000000000000000000000000000000000000");
92 let nonce = unhex("0000000000000000");
93 let mut keystream = [0u8; 64];
94 Salsa20::new_from_slices(&key, &nonce)
95 .unwrap()
96 .apply_keystream(&mut keystream);
97 assert_eq!(
98 keystream.as_slice(),
99 unhex(concat!(
100 "e3be8fdd8beca2e3ea8ef9475b29a6e7",
101 "003951e1097a5c38d23b7a5fad9f6844",
102 "b22c97559e2723c7cbbd3fe4fc8d9a07",
103 "44652a83e72a9c461876af4d7ef1a117"
104 ))
105 .as_slice()
106 );
107
108 let key = unhex("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
109 let nonce = unhex("0301040105090206");
110 let mut keystream = [0u8; 256];
111 Salsa20::new_from_slices(&key, &nonce)
112 .unwrap()
113 .apply_keystream(&mut keystream);
114 assert_eq!(
115 keystream.as_slice(),
116 unhex(concat!(
117 "6ebcbdbf76fccc64ab05542bee8a67cb",
118 "c28fa2e141fbefbb3a2f9b221909c8d7",
119 "d4295258cb539770dd24d7ac3443769f",
120 "fa27a50e60644264dc8b6b612683372e",
121 "085d0a12bf240b189ce2b78289862b56",
122 "fdc9fcffc33bef9325a2e81b98fb3fb9",
123 "aa04cf434615ceffeb985c1cb08d8440",
124 "e90b1d56ddeaea16d9e15affff1f698c",
125 "483c7a466af1fe062574adfd2b06a62b",
126 "4d98440719ea776385c470349a7ed696",
127 "9583463ed5d26b8fefccb205da0f5bfa",
128 "98c77812fe756b09eacc282aa42f4baf",
129 "a79633189046e2b20f35b3e0e54aa3b9",
130 "29e23c0f47dc7bcd4f928b2a9764be7d",
131 "4b8a50f980a50b35ad8087375e0c556e",
132 "cbe6a7161e8653ce9391e1e6710ed4f1"
133 ))
134 .as_slice()
135 );
136 }
137
138 #[test]
139 fn roundtrip_including_multi_block_and_empty() {
140 let encryptor = Salsa20Encryptor::new([3u8; 32]);
141 for plaintext in [b"".as_slice(), b"a", b"abc", b"abcd", &[0xffu8; 200]] {
143 let blob = encryptor.encrypt(plaintext).unwrap();
144 assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
145 }
146 }
147
148 #[test]
149 fn ciphertext_differs_from_plaintext_and_tamper_fails() {
150 let encryptor = Salsa20Encryptor::new([3u8; 32]);
151 let blob = encryptor.encrypt(b"0123456789").unwrap();
152 assert_ne!(&blob[42..], b"0123456789");
154
155 let mut tampered = blob.clone();
156 let last = tampered.len() - 1;
157 tampered[last] ^= 0x01;
158 assert_eq!(
159 encryptor.decrypt(&tampered).unwrap_err(),
160 Error::MacVerificationFailed { label: LABEL }
161 );
162 }
163
164 #[test]
165 fn wrong_key_fails_mac() {
166 let blob = Salsa20Encryptor::new([3u8; 32]).encrypt(b"secret").unwrap();
167 let other = Salsa20Encryptor::new([4u8; 32]);
168 assert_eq!(
169 other.decrypt(&blob).unwrap_err(),
170 Error::MacVerificationFailed { label: LABEL }
171 );
172 }
173
174 #[test]
175 fn nonce_is_fresh_per_encryption() {
176 let encryptor = Salsa20Encryptor::new([3u8; 32]);
177 let first = encryptor.encrypt(b"same plaintext").unwrap();
178 let second = encryptor.encrypt(b"same plaintext").unwrap();
179 assert_ne!(first, second);
180 }
181
182 #[test]
183 fn invalid_prefix_and_too_short_are_rejected() {
184 let encryptor = Salsa20Encryptor::new([3u8; 32]);
185 assert_eq!(
186 encryptor.decrypt(b"v9 not a blob").unwrap_err(),
187 Error::InvalidPrefix { label: LABEL }
188 );
189 assert_eq!(
190 encryptor.decrypt(b"v1 short").unwrap_err(),
191 Error::TooShort { label: LABEL }
192 );
193 }
194
195 #[test]
196 fn key_length_is_checked() {
197 assert_eq!(
198 Salsa20Encryptor::from_slice(&[0u8; 16]).unwrap_err(),
199 Error::InvalidKeyLength {
200 label: LABEL,
201 expected: 32,
202 got: 16
203 }
204 );
205 }
206}