1#![no_std]
2#![cfg_attr(docsrs, feature(doc_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
9#[cfg(any(feature = "aes", feature = "tdes"))]
10pub mod block_cipher;
11
12#[cfg(feature = "chacha20poly1305")]
13mod chacha20poly1305;
14mod error;
15
16pub use crate::error::{Error, Result};
17pub use cipher;
18
19#[cfg(feature = "chacha20poly1305")]
20pub use crate::chacha20poly1305::{ChaCha20, ChaCha20Poly1305, ChaChaKey, ChaChaNonce};
21
22use cipher::array::{Array, typenum::U16};
23use core::{fmt, str};
24use encoding::{Label, LabelError};
25
26#[cfg(feature = "aes")]
27use self::block_cipher::Aes;
28#[cfg(feature = "tdes")]
29use self::block_cipher::Tdes;
30#[cfg(any(feature = "aes", feature = "chacha20poly1305"))]
31use ::aead::{AeadInOut, KeyInit};
32#[cfg(any(feature = "aes", feature = "tdes"))]
33use {
34 self::block_cipher::{BlockMode, sealed::BlockCipher},
35 ::cipher::{Block, BlockModeDecrypt, BlockModeEncrypt},
36};
37#[cfg(feature = "aes")]
38use {
39 aead::array::typenum::U12,
40 aes_gcm::{Aes128Gcm, Aes256Gcm},
41};
42
43const AES128_CBC: &str = "aes128-cbc";
45const AES192_CBC: &str = "aes192-cbc";
47const AES256_CBC: &str = "aes256-cbc";
49
50const AES128_CTR: &str = "aes128-ctr";
52const AES192_CTR: &str = "aes192-ctr";
54const AES256_CTR: &str = "aes256-ctr";
56
57const AES128_GCM: &str = "aes128-gcm@openssh.com";
59const AES256_GCM: &str = "aes256-gcm@openssh.com";
61
62const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com";
64
65const TDES_CBC: &str = "3des-cbc";
67
68#[cfg(feature = "aes")]
70pub type AesGcmNonce = Array<u8, U12>;
71
72pub type Tag = Array<u8, U16>;
77
78#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
113#[non_exhaustive]
114pub enum Cipher {
115 None,
117
118 Aes128Cbc,
120
121 Aes192Cbc,
123
124 Aes256Cbc,
126
127 Aes128Ctr,
129
130 Aes192Ctr,
132
133 Aes256Ctr,
135
136 Aes128Gcm,
138
139 Aes256Gcm,
141
142 ChaCha20Poly1305,
144
145 TdesCbc,
147}
148
149impl Cipher {
150 pub fn new(ciphername: &str) -> core::result::Result<Self, LabelError> {
167 ciphername.parse()
168 }
169
170 #[must_use]
172 pub fn as_str(self) -> &'static str {
173 match self {
174 Self::None => "none",
175 Self::Aes128Cbc => AES128_CBC,
176 Self::Aes192Cbc => AES192_CBC,
177 Self::Aes256Cbc => AES256_CBC,
178 Self::Aes128Ctr => AES128_CTR,
179 Self::Aes192Ctr => AES192_CTR,
180 Self::Aes256Ctr => AES256_CTR,
181 Self::Aes128Gcm => AES128_GCM,
182 Self::Aes256Gcm => AES256_GCM,
183 Self::ChaCha20Poly1305 => CHACHA20_POLY1305,
184 Self::TdesCbc => TDES_CBC,
185 }
186 }
187
188 #[must_use]
190 pub fn key_and_iv_size(self) -> Option<(usize, usize)> {
191 match self {
192 Self::None => None,
193 Self::Aes128Cbc => Some((16, 16)),
194 Self::Aes192Cbc => Some((24, 16)),
195 Self::Aes256Cbc => Some((32, 16)),
196 Self::Aes128Ctr => Some((16, 16)),
197 Self::Aes192Ctr => Some((24, 16)),
198 Self::Aes256Ctr => Some((32, 16)),
199 Self::Aes128Gcm => Some((16, 12)),
200 Self::Aes256Gcm => Some((32, 12)),
201 Self::ChaCha20Poly1305 => Some((32, 8)),
202 Self::TdesCbc => Some((24, 8)),
203 }
204 }
205
206 #[must_use]
208 pub fn block_size(self) -> usize {
209 match self {
210 Self::None | Self::ChaCha20Poly1305 | Self::TdesCbc => 8,
211 Self::Aes128Cbc
212 | Self::Aes192Cbc
213 | Self::Aes256Cbc
214 | Self::Aes128Ctr
215 | Self::Aes192Ctr
216 | Self::Aes256Ctr
217 | Self::Aes128Gcm
218 | Self::Aes256Gcm => 16,
219 }
220 }
221
222 #[allow(clippy::arithmetic_side_effects)]
225 #[must_use]
226 pub fn padding_len(self, input_size: usize) -> usize {
227 #[allow(
228 clippy::integer_division_remainder_used,
229 reason = "input_size is non-secret"
230 )]
231 match input_size % self.block_size() {
232 0 => 0,
233 input_rem => self.block_size() - input_rem,
234 }
235 }
236
237 #[must_use]
239 pub fn has_tag(self) -> bool {
240 matches!(
241 self,
242 Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305
243 )
244 }
245
246 #[must_use]
248 pub fn is_none(self) -> bool {
249 self == Self::None
250 }
251
252 #[must_use]
254 pub fn is_some(self) -> bool {
255 !self.is_none()
256 }
257
258 #[cfg_attr(not(any(feature = "aes", feature = "tdes")), allow(unused_variables))]
264 pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option<Tag>) -> Result<()> {
265 match self {
266 #[cfg(feature = "aes")]
267 Self::Aes128Gcm => {
268 let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
269 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
270 let tag = tag.ok_or(Error::TagSize)?;
271 cipher
272 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
273 .map_err(|_| Error::Crypto)?;
274
275 Ok(())
276 }
277 #[cfg(feature = "aes")]
278 Self::Aes256Gcm => {
279 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
280 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
281 let tag = tag.ok_or(Error::TagSize)?;
282 cipher
283 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
284 .map_err(|_| Error::Crypto)?;
285
286 Ok(())
287 }
288 #[cfg(feature = "chacha20poly1305")]
289 Self::ChaCha20Poly1305 => {
290 let key = key.try_into().map_err(|_| Error::KeySize)?;
291 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
292 let tag = tag.ok_or(Error::TagSize)?;
293 ChaCha20Poly1305::new(key)
294 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
295 .map_err(|_| Error::Crypto)
296 }
297 #[cfg(feature = "aes")]
298 Self::Aes128Cbc
299 | Self::Aes192Cbc
300 | Self::Aes256Cbc
301 | Self::Aes128Ctr
302 | Self::Aes192Ctr
303 | Self::Aes256Ctr => {
304 if tag.is_some() {
306 return Err(Error::Crypto);
307 }
308 self.decrypt_with_block_cipher::<Aes>(key, iv, buffer)
309 }
310 #[cfg(feature = "tdes")]
311 Self::TdesCbc => {
312 if tag.is_some() {
314 return Err(Error::Crypto);
315 }
316 self.decrypt_with_block_cipher::<Tdes>(key, iv, buffer)
317 }
318 _ => Err(Error::UnsupportedCipher(self)),
319 }
320 }
321
322 #[cfg(any(feature = "aes", feature = "tdes"))]
329 fn decrypt_with_block_cipher<C: BlockCipher>(
330 self,
331 key: &[u8],
332 iv: &[u8],
333 buffer: &mut [u8],
334 ) -> Result<()> {
335 let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer);
336
337 if !remaining.is_empty() {
338 return Err(Error::Length);
339 }
340
341 self.decryptor::<C>(key, iv)?.decrypt_blocks(blocks);
342 Ok(())
343 }
344
345 #[cfg(any(feature = "aes", feature = "tdes"))]
353 pub fn decryptor<C>(self, key: &[u8], iv: &[u8]) -> Result<block_cipher::Decryptor<C>>
354 where
355 C: BlockCipher,
356 {
357 block_cipher::Decryptor::new(self, key, iv)
358 }
359
360 #[cfg_attr(not(any(feature = "aes", feature = "tdes")), allow(unused_variables))]
366 pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<Option<Tag>> {
367 match self {
368 #[cfg(feature = "aes")]
369 Self::Aes128Gcm => {
370 let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
371 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
372 let tag = cipher
373 .encrypt_inout_detached(nonce, &[], buffer.into())
374 .map_err(|_| Error::Crypto)?;
375
376 Ok(Some(tag))
377 }
378 #[cfg(feature = "aes")]
379 Self::Aes256Gcm => {
380 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
381 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
382 let tag = cipher
383 .encrypt_inout_detached(nonce, &[], buffer.into())
384 .map_err(|_| Error::Crypto)?;
385
386 Ok(Some(tag))
387 }
388 #[cfg(feature = "chacha20poly1305")]
389 Self::ChaCha20Poly1305 => {
390 let key = key.try_into().map_err(|_| Error::KeySize)?;
391 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
392 let tag = ChaCha20Poly1305::new(key)
393 .encrypt_inout_detached(nonce, &[], buffer.into())
394 .map_err(|_| Error::Crypto)?;
395 Ok(Some(tag))
396 }
397 #[cfg(feature = "aes")]
398 Self::Aes128Cbc
399 | Self::Aes192Cbc
400 | Self::Aes256Cbc
401 | Self::Aes128Ctr
402 | Self::Aes192Ctr
403 | Self::Aes256Ctr => {
404 self.encrypt_with_block_cipher::<Aes>(key, iv, buffer)?;
405 Ok(None)
406 }
407 #[cfg(feature = "tdes")]
408 Self::TdesCbc => {
409 self.encrypt_with_block_cipher::<Tdes>(key, iv, buffer)?;
410 Ok(None)
411 }
412 _ => Err(Error::UnsupportedCipher(self)),
413 }
414 }
415
416 #[cfg(any(feature = "aes", feature = "tdes"))]
423 fn encrypt_with_block_cipher<C: BlockCipher>(
424 self,
425 key: &[u8],
426 iv: &[u8],
427 buffer: &mut [u8],
428 ) -> Result<()> {
429 let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer);
430
431 if !remaining.is_empty() {
432 return Err(Error::Length);
433 }
434
435 self.encryptor::<C>(key, iv)?.encrypt_blocks(blocks);
436 Ok(())
437 }
438
439 #[cfg(any(feature = "aes", feature = "tdes"))]
447 pub fn encryptor<C>(self, key: &[u8], iv: &[u8]) -> Result<block_cipher::Encryptor<C>>
448 where
449 C: BlockCipher,
450 {
451 block_cipher::Encryptor::new(self, key, iv)
452 }
453
454 #[cfg(any(feature = "aes", feature = "tdes"))]
456 pub(crate) fn block_mode(self) -> Option<BlockMode> {
457 match self {
458 #[cfg(feature = "aes")]
459 Self::Aes128Cbc | Self::Aes192Cbc | Self::Aes256Cbc => Some(BlockMode::Cbc),
460 #[cfg(feature = "aes")]
461 Self::Aes128Ctr | Self::Aes192Ctr | Self::Aes256Ctr => Some(BlockMode::Ctr),
462 #[cfg(feature = "tdes")]
463 Self::TdesCbc => Some(BlockMode::Cbc),
464 _ => None,
465 }
466 }
467}
468
469impl AsRef<str> for Cipher {
470 fn as_ref(&self) -> &str {
471 self.as_str()
472 }
473}
474
475impl Label for Cipher {}
476
477impl fmt::Display for Cipher {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 f.write_str(self.as_str())
480 }
481}
482
483impl str::FromStr for Cipher {
484 type Err = LabelError;
485
486 fn from_str(ciphername: &str) -> core::result::Result<Self, LabelError> {
487 match ciphername {
488 "none" => Ok(Self::None),
489 AES128_CBC => Ok(Self::Aes128Cbc),
490 AES192_CBC => Ok(Self::Aes192Cbc),
491 AES256_CBC => Ok(Self::Aes256Cbc),
492 AES128_CTR => Ok(Self::Aes128Ctr),
493 AES192_CTR => Ok(Self::Aes192Ctr),
494 AES256_CTR => Ok(Self::Aes256Ctr),
495 AES128_GCM => Ok(Self::Aes128Gcm),
496 AES256_GCM => Ok(Self::Aes256Gcm),
497 CHACHA20_POLY1305 => Ok(Self::ChaCha20Poly1305),
498 TDES_CBC => Ok(Self::TdesCbc),
499 _ => Err(LabelError::new(ciphername)),
500 }
501 }
502}