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