1#![no_std]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
7)]
8#![forbid(unsafe_code)]
9#![warn(
10 clippy::arithmetic_side_effects,
11 clippy::panic,
12 clippy::panic_in_result_fn,
13 clippy::unwrap_used,
14 missing_docs,
15 rust_2018_idioms,
16 unused_lifetimes,
17 unused_qualifications
18)]
19
20#[cfg(feature = "std")]
21extern crate std;
22
23mod error;
24
25#[cfg(feature = "chacha20poly1305")]
26mod chacha20poly1305;
27
28pub use crate::error::{Error, Result};
29
30use core::{fmt, str};
31use encoding::{Label, LabelError};
32
33#[cfg(feature = "aes-ctr")]
34use cipher::StreamCipherCore;
35
36#[cfg(feature = "aes-gcm")]
37use aes_gcm::{aead::AeadInPlace, Aes128Gcm, Aes256Gcm};
38
39#[cfg(feature = "chacha20poly1305")]
40use crate::chacha20poly1305::ChaCha20Poly1305;
41
42#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
43use aes::{Aes128, Aes192, Aes256};
44
45#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
46use {
47 cbc::{Decryptor, Encryptor},
48 cipher::{block_padding::NoPadding, BlockCipher, BlockDecryptMut, BlockEncryptMut},
49};
50
51#[cfg(any(feature = "aes-cbc", feature = "aes-gcm", feature = "tdes"))]
52use cipher::KeyInit;
53
54#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
55use cipher::KeyIvInit;
56
57#[cfg(feature = "tdes")]
58use des::TdesEde3;
59
60const AES128_CBC: &str = "aes128-cbc";
62
63const AES192_CBC: &str = "aes192-cbc";
65
66const AES256_CBC: &str = "aes256-cbc";
68
69const AES128_CTR: &str = "aes128-ctr";
71
72const AES192_CTR: &str = "aes192-ctr";
74
75const AES256_CTR: &str = "aes256-ctr";
77
78const AES128_GCM: &str = "aes128-gcm@openssh.com";
80
81const AES256_GCM: &str = "aes256-gcm@openssh.com";
83
84const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com";
86
87const TDES_CBC: &str = "3des-cbc";
89
90pub type Nonce = [u8; 12];
95
96pub type Tag = [u8; 16];
101
102#[cfg(feature = "aes-ctr")]
104type Ctr128BE<Cipher> = ctr::CtrCore<Cipher, ctr::flavors::Ctr128BE>;
105
106#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
108#[non_exhaustive]
109pub enum Cipher {
110 None,
112
113 Aes128Cbc,
115
116 Aes192Cbc,
118
119 Aes256Cbc,
121
122 Aes128Ctr,
124
125 Aes192Ctr,
127
128 Aes256Ctr,
130
131 Aes128Gcm,
133
134 Aes256Gcm,
136
137 ChaCha20Poly1305,
139
140 TDesCbc,
142}
143
144impl Cipher {
145 pub fn new(ciphername: &str) -> core::result::Result<Self, LabelError> {
150 ciphername.parse()
151 }
152
153 pub fn as_str(self) -> &'static str {
155 match self {
156 Self::None => "none",
157 Self::Aes128Cbc => AES128_CBC,
158 Self::Aes192Cbc => AES192_CBC,
159 Self::Aes256Cbc => AES256_CBC,
160 Self::Aes128Ctr => AES128_CTR,
161 Self::Aes192Ctr => AES192_CTR,
162 Self::Aes256Ctr => AES256_CTR,
163 Self::Aes128Gcm => AES128_GCM,
164 Self::Aes256Gcm => AES256_GCM,
165 Self::ChaCha20Poly1305 => CHACHA20_POLY1305,
166 Self::TDesCbc => TDES_CBC,
167 }
168 }
169
170 pub fn key_and_iv_size(self) -> Option<(usize, usize)> {
172 match self {
173 Self::None => None,
174 Self::Aes128Cbc => Some((16, 16)),
175 Self::Aes192Cbc => Some((24, 16)),
176 Self::Aes256Cbc => Some((32, 16)),
177 Self::Aes128Ctr => Some((16, 16)),
178 Self::Aes192Ctr => Some((24, 16)),
179 Self::Aes256Ctr => Some((32, 16)),
180 Self::Aes128Gcm => Some((16, 12)),
181 Self::Aes256Gcm => Some((32, 12)),
182 Self::ChaCha20Poly1305 => Some((64, 0)),
183 Self::TDesCbc => Some((24, 8)),
184 }
185 }
186
187 pub fn block_size(self) -> usize {
189 match self {
190 Self::None | Self::ChaCha20Poly1305 | Self::TDesCbc => 8,
191 Self::Aes128Cbc
192 | Self::Aes192Cbc
193 | Self::Aes256Cbc
194 | Self::Aes128Ctr
195 | Self::Aes192Ctr
196 | Self::Aes256Ctr
197 | Self::Aes128Gcm
198 | Self::Aes256Gcm => 16,
199 }
200 }
201
202 #[allow(clippy::arithmetic_side_effects)]
205 pub fn padding_len(self, input_size: usize) -> usize {
206 match input_size % self.block_size() {
207 0 => 0,
208 input_rem => self.block_size() - input_rem,
209 }
210 }
211
212 pub fn has_tag(self) -> bool {
214 matches!(
215 self,
216 Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305
217 )
218 }
219
220 pub fn is_none(self) -> bool {
222 self == Self::None
223 }
224
225 pub fn is_some(self) -> bool {
227 !self.is_none()
228 }
229
230 pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option<Tag>) -> Result<()> {
232 match self {
233 #[cfg(feature = "aes-cbc")]
234 Self::Aes128Cbc => {
235 if tag.is_some() {
236 return Err(Error::TagSize);
237 }
238 cbc_decrypt::<Aes128>(key, iv, buffer)
239 }
240 #[cfg(feature = "aes-cbc")]
241 Self::Aes192Cbc => {
242 if tag.is_some() {
243 return Err(Error::TagSize);
244 }
245 cbc_decrypt::<Aes192>(key, iv, buffer)
246 }
247 #[cfg(feature = "aes-cbc")]
248 Self::Aes256Cbc => {
249 if tag.is_some() {
250 return Err(Error::TagSize);
251 }
252 cbc_decrypt::<Aes256>(key, iv, buffer)
253 }
254 #[cfg(feature = "aes-ctr")]
255 Self::Aes128Ctr | Self::Aes192Ctr | Self::Aes256Ctr => {
256 if tag.is_some() {
257 return Err(Error::TagSize);
258 }
259
260 self.encrypt(key, iv, buffer)?;
262 Ok(())
263 }
264 #[cfg(feature = "aes-gcm")]
265 Self::Aes128Gcm => {
266 let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
267 let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
268 let tag = tag.ok_or(Error::TagSize)?;
269 cipher
270 .decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into())
271 .map_err(|_| Error::Crypto)?;
272
273 Ok(())
274 }
275 #[cfg(feature = "aes-gcm")]
276 Self::Aes256Gcm => {
277 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
278 let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
279 let tag = tag.ok_or(Error::TagSize)?;
280 cipher
281 .decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into())
282 .map_err(|_| Error::Crypto)?;
283
284 Ok(())
285 }
286 #[cfg(feature = "chacha20poly1305")]
287 Self::ChaCha20Poly1305 => {
288 let tag = tag.ok_or(Error::TagSize)?;
289 ChaCha20Poly1305::new(key, iv)?.decrypt(buffer, tag)
290 }
291 #[cfg(feature = "tdes")]
292 Self::TDesCbc => {
293 if tag.is_some() {
294 return Err(Error::TagSize);
295 }
296 cbc_decrypt::<TdesEde3>(key, iv, buffer)
297 }
298 _ => {
299 let (_, _, _, _) = (key, iv, buffer, tag);
301 Err(self.unsupported())
302 }
303 }
304 }
305
306 pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<Option<Tag>> {
308 match self {
309 #[cfg(feature = "aes-cbc")]
310 Self::Aes128Cbc => {
311 cbc_encrypt::<Aes128>(key, iv, buffer)?;
312 Ok(None)
313 }
314 #[cfg(feature = "aes-cbc")]
315 Self::Aes192Cbc => {
316 cbc_encrypt::<Aes192>(key, iv, buffer)?;
317 Ok(None)
318 }
319 #[cfg(feature = "aes-cbc")]
320 Self::Aes256Cbc => {
321 cbc_encrypt::<Aes256>(key, iv, buffer)?;
322 Ok(None)
323 }
324 #[cfg(feature = "aes-ctr")]
325 Self::Aes128Ctr => {
326 ctr_encrypt::<Ctr128BE<Aes128>>(key, iv, buffer)?;
327 Ok(None)
328 }
329 #[cfg(feature = "aes-ctr")]
330 Self::Aes192Ctr => {
331 ctr_encrypt::<Ctr128BE<Aes192>>(key, iv, buffer)?;
332 Ok(None)
333 }
334 #[cfg(feature = "aes-ctr")]
335 Self::Aes256Ctr => {
336 ctr_encrypt::<Ctr128BE<Aes256>>(key, iv, buffer)?;
337 Ok(None)
338 }
339 #[cfg(feature = "aes-gcm")]
340 Self::Aes128Gcm => {
341 let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
342 let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
343 let tag = cipher
344 .encrypt_in_place_detached(&nonce.into(), &[], buffer)
345 .map_err(|_| Error::Crypto)?;
346
347 Ok(Some(tag.into()))
348 }
349 #[cfg(feature = "aes-gcm")]
350 Self::Aes256Gcm => {
351 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
352 let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
353 let tag = cipher
354 .encrypt_in_place_detached(&nonce.into(), &[], buffer)
355 .map_err(|_| Error::Crypto)?;
356
357 Ok(Some(tag.into()))
358 }
359 #[cfg(feature = "chacha20poly1305")]
360 Self::ChaCha20Poly1305 => {
361 let tag = ChaCha20Poly1305::new(key, iv)?.encrypt(buffer);
362 Ok(Some(tag))
363 }
364 #[cfg(feature = "tdes")]
365 Self::TDesCbc => {
366 cbc_encrypt::<TdesEde3>(key, iv, buffer)?;
367 Ok(None)
368 }
369 _ => {
370 let (_, _, _) = (key, iv, buffer);
372 Err(self.unsupported())
373 }
374 }
375 }
376
377 fn unsupported(self) -> Error {
379 Error::UnsupportedCipher(self)
380 }
381}
382
383impl AsRef<str> for Cipher {
384 fn as_ref(&self) -> &str {
385 self.as_str()
386 }
387}
388
389impl Label for Cipher {}
390
391impl fmt::Display for Cipher {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 f.write_str(self.as_str())
394 }
395}
396
397impl str::FromStr for Cipher {
398 type Err = LabelError;
399
400 fn from_str(ciphername: &str) -> core::result::Result<Self, LabelError> {
401 match ciphername {
402 "none" => Ok(Self::None),
403 AES128_CBC => Ok(Self::Aes128Cbc),
404 AES192_CBC => Ok(Self::Aes192Cbc),
405 AES256_CBC => Ok(Self::Aes256Cbc),
406 AES128_CTR => Ok(Self::Aes128Ctr),
407 AES192_CTR => Ok(Self::Aes192Ctr),
408 AES256_CTR => Ok(Self::Aes256Ctr),
409 AES128_GCM => Ok(Self::Aes128Gcm),
410 AES256_GCM => Ok(Self::Aes256Gcm),
411 CHACHA20_POLY1305 => Ok(Self::ChaCha20Poly1305),
412 TDES_CBC => Ok(Self::TDesCbc),
413 _ => Err(LabelError::new(ciphername)),
414 }
415 }
416}
417
418#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
419fn cbc_encrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
420where
421 C: BlockEncryptMut + BlockCipher + KeyInit,
422{
423 let cipher = Encryptor::<C>::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
424
425 cipher
427 .encrypt_padded_mut::<NoPadding>(buffer, buffer.len())
428 .map_err(|_| Error::Crypto)?;
429
430 Ok(())
431}
432
433#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
434fn cbc_decrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
435where
436 C: BlockDecryptMut + BlockCipher + KeyInit,
437{
438 let cipher = Decryptor::<C>::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
439
440 cipher
442 .decrypt_padded_mut::<NoPadding>(buffer)
443 .map_err(|_| Error::Crypto)?;
444
445 Ok(())
446}
447
448#[cfg(feature = "aes-ctr")]
449fn ctr_encrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
450where
451 C: StreamCipherCore + KeyIvInit,
452{
453 let cipher = C::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
454
455 cipher
456 .try_apply_keystream_partial(buffer.into())
457 .map_err(|_| Error::Crypto)?;
458
459 Ok(())
460}