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