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::alloc_instead_of_core,
11 clippy::arithmetic_side_effects,
12 clippy::mod_module_files,
13 clippy::panic,
14 clippy::panic_in_result_fn,
15 clippy::std_instead_of_alloc,
16 clippy::std_instead_of_core,
17 clippy::unwrap_used,
18 missing_docs,
19 rust_2018_idioms,
20 unused_lifetimes,
21 unused_qualifications
22)]
23
24mod error;
25
26#[cfg(feature = "chacha20poly1305")]
27mod chacha20poly1305;
28#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
29mod decryptor;
30#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
31mod encryptor;
32
33pub use crate::error::{Error, Result};
34pub use cipher;
35
36#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
37pub use crate::{decryptor::Decryptor, encryptor::Encryptor};
38
39#[cfg(feature = "chacha20poly1305")]
40pub use crate::chacha20poly1305::{ChaCha20, ChaCha20Poly1305, ChaChaKey, ChaChaNonce};
41
42use cipher::array::{Array, typenum::U16};
43use core::{fmt, str};
44use encoding::{Label, LabelError};
45
46#[cfg(feature = "aes-gcm")]
47use {
48 aead::array::typenum::U12,
49 aes_gcm::{Aes128Gcm, Aes256Gcm},
50};
51
52#[cfg(any(feature = "aes-gcm", feature = "chacha20poly1305"))]
53use aead::{AeadInOut, KeyInit};
54
55const AES128_CBC: &str = "aes128-cbc";
57
58const AES192_CBC: &str = "aes192-cbc";
60
61const AES256_CBC: &str = "aes256-cbc";
63
64const AES128_CTR: &str = "aes128-ctr";
66
67const AES192_CTR: &str = "aes192-ctr";
69
70const AES256_CTR: &str = "aes256-ctr";
72
73const AES128_GCM: &str = "aes128-gcm@openssh.com";
75
76const AES256_GCM: &str = "aes256-gcm@openssh.com";
78
79const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com";
81
82const TDES_CBC: &str = "3des-cbc";
84
85#[cfg(feature = "aes-gcm")]
87pub type AesGcmNonce = Array<u8, U12>;
88
89pub type Tag = Array<u8, U16>;
94
95#[cfg(feature = "aes-ctr")]
97type Ctr128BE<Cipher> = ctr::CtrCore<Cipher, ctr::flavors::Ctr128BE>;
98
99#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
101#[non_exhaustive]
102pub enum Cipher {
103 None,
105
106 Aes128Cbc,
108
109 Aes192Cbc,
111
112 Aes256Cbc,
114
115 Aes128Ctr,
117
118 Aes192Ctr,
120
121 Aes256Ctr,
123
124 Aes128Gcm,
126
127 Aes256Gcm,
129
130 ChaCha20Poly1305,
132
133 TDesCbc,
135}
136
137impl Cipher {
138 pub fn new(ciphername: &str) -> core::result::Result<Self, LabelError> {
143 ciphername.parse()
144 }
145
146 pub fn as_str(self) -> &'static str {
148 match self {
149 Self::None => "none",
150 Self::Aes128Cbc => AES128_CBC,
151 Self::Aes192Cbc => AES192_CBC,
152 Self::Aes256Cbc => AES256_CBC,
153 Self::Aes128Ctr => AES128_CTR,
154 Self::Aes192Ctr => AES192_CTR,
155 Self::Aes256Ctr => AES256_CTR,
156 Self::Aes128Gcm => AES128_GCM,
157 Self::Aes256Gcm => AES256_GCM,
158 Self::ChaCha20Poly1305 => CHACHA20_POLY1305,
159 Self::TDesCbc => TDES_CBC,
160 }
161 }
162
163 pub fn key_and_iv_size(self) -> Option<(usize, usize)> {
165 match self {
166 Self::None => None,
167 Self::Aes128Cbc => Some((16, 16)),
168 Self::Aes192Cbc => Some((24, 16)),
169 Self::Aes256Cbc => Some((32, 16)),
170 Self::Aes128Ctr => Some((16, 16)),
171 Self::Aes192Ctr => Some((24, 16)),
172 Self::Aes256Ctr => Some((32, 16)),
173 Self::Aes128Gcm => Some((16, 12)),
174 Self::Aes256Gcm => Some((32, 12)),
175 Self::ChaCha20Poly1305 => Some((32, 8)),
176 Self::TDesCbc => Some((24, 8)),
177 }
178 }
179
180 pub fn block_size(self) -> usize {
182 match self {
183 Self::None | Self::ChaCha20Poly1305 | Self::TDesCbc => 8,
184 Self::Aes128Cbc
185 | Self::Aes192Cbc
186 | Self::Aes256Cbc
187 | Self::Aes128Ctr
188 | Self::Aes192Ctr
189 | Self::Aes256Ctr
190 | Self::Aes128Gcm
191 | Self::Aes256Gcm => 16,
192 }
193 }
194
195 #[allow(clippy::arithmetic_side_effects)]
198 pub fn padding_len(self, input_size: usize) -> usize {
199 match input_size % self.block_size() {
200 0 => 0,
201 input_rem => self.block_size() - input_rem,
202 }
203 }
204
205 pub fn has_tag(self) -> bool {
207 matches!(
208 self,
209 Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305
210 )
211 }
212
213 pub fn is_none(self) -> bool {
215 self == Self::None
216 }
217
218 pub fn is_some(self) -> bool {
220 !self.is_none()
221 }
222
223 #[cfg_attr(
228 not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")),
229 allow(unused_variables)
230 )]
231 pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option<Tag>) -> Result<()> {
232 match self {
233 #[cfg(feature = "aes-gcm")]
234 Self::Aes128Gcm => {
235 let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
236 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
237 let tag = tag.ok_or(Error::TagSize)?;
238 cipher
239 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
240 .map_err(|_| Error::Crypto)?;
241
242 Ok(())
243 }
244 #[cfg(feature = "aes-gcm")]
245 Self::Aes256Gcm => {
246 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
247 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
248 let tag = tag.ok_or(Error::TagSize)?;
249 cipher
250 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
251 .map_err(|_| Error::Crypto)?;
252
253 Ok(())
254 }
255 #[cfg(feature = "chacha20poly1305")]
256 Self::ChaCha20Poly1305 => {
257 let key = key.try_into().map_err(|_| Error::KeySize)?;
258 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
259 let tag = tag.ok_or(Error::TagSize)?;
260 ChaCha20Poly1305::new(key)
261 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
262 .map_err(|_| Error::Crypto)
263 }
264 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
266 _ => {
267 if tag.is_some() {
269 return Err(Error::Crypto);
270 }
271
272 self.decryptor(key, iv)?.decrypt(buffer)
273 }
274 #[cfg(not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")))]
275 _ => Err(self.unsupported()),
276 }
277 }
278
279 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
284 pub fn decryptor(self, key: &[u8], iv: &[u8]) -> Result<Decryptor> {
285 Decryptor::new(self, key, iv)
286 }
287
288 #[cfg_attr(
293 not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")),
294 allow(unused_variables)
295 )]
296 pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<Option<Tag>> {
297 match self {
298 #[cfg(feature = "aes-gcm")]
299 Self::Aes128Gcm => {
300 let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
301 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
302 let tag = cipher
303 .encrypt_inout_detached(nonce, &[], buffer.into())
304 .map_err(|_| Error::Crypto)?;
305
306 Ok(Some(tag))
307 }
308 #[cfg(feature = "aes-gcm")]
309 Self::Aes256Gcm => {
310 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
311 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
312 let tag = cipher
313 .encrypt_inout_detached(nonce, &[], buffer.into())
314 .map_err(|_| Error::Crypto)?;
315
316 Ok(Some(tag))
317 }
318 #[cfg(feature = "chacha20poly1305")]
319 Self::ChaCha20Poly1305 => {
320 let key = key.try_into().map_err(|_| Error::KeySize)?;
321 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
322 let tag = ChaCha20Poly1305::new(key)
323 .encrypt_inout_detached(nonce, &[], buffer.into())
324 .map_err(|_| Error::Crypto)?;
325 Ok(Some(tag))
326 }
327 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
329 _ => {
330 self.encryptor(key, iv)?.encrypt(buffer)?;
331 Ok(None)
332 }
333 #[cfg(not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")))]
334 _ => Err(self.unsupported()),
335 }
336 }
337
338 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
343 pub fn encryptor(self, key: &[u8], iv: &[u8]) -> Result<Encryptor> {
344 Encryptor::new(self, key, iv)
345 }
346
347 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
349 fn check_key_and_iv(self, key: &[u8], iv: &[u8]) -> Result<()> {
350 let (key_size, iv_size) = self
351 .key_and_iv_size()
352 .ok_or(Error::UnsupportedCipher(self))?;
353
354 if key.len() != key_size {
355 return Err(Error::KeySize);
356 }
357
358 if iv.len() != iv_size {
359 return Err(Error::IvSize);
360 }
361
362 Ok(())
363 }
364
365 fn unsupported(self) -> Error {
367 Error::UnsupportedCipher(self)
368 }
369}
370
371impl AsRef<str> for Cipher {
372 fn as_ref(&self) -> &str {
373 self.as_str()
374 }
375}
376
377impl Label for Cipher {}
378
379impl fmt::Display for Cipher {
380 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381 f.write_str(self.as_str())
382 }
383}
384
385impl str::FromStr for Cipher {
386 type Err = LabelError;
387
388 fn from_str(ciphername: &str) -> core::result::Result<Self, LabelError> {
389 match ciphername {
390 "none" => Ok(Self::None),
391 AES128_CBC => Ok(Self::Aes128Cbc),
392 AES192_CBC => Ok(Self::Aes192Cbc),
393 AES256_CBC => Ok(Self::Aes256Cbc),
394 AES128_CTR => Ok(Self::Aes128Ctr),
395 AES192_CTR => Ok(Self::Aes192Ctr),
396 AES256_CTR => Ok(Self::Aes256Ctr),
397 AES128_GCM => Ok(Self::Aes128Gcm),
398 AES256_GCM => Ok(Self::Aes256Gcm),
399 CHACHA20_POLY1305 => Ok(Self::ChaCha20Poly1305),
400 TDES_CBC => Ok(Self::TDesCbc),
401 _ => Err(LabelError::new(ciphername)),
402 }
403 }
404}