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
9mod error;
10
11#[cfg(feature = "chacha20poly1305")]
12mod chacha20poly1305;
13#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
14mod decryptor;
15#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
16mod encryptor;
17
18pub use crate::error::{Error, Result};
19pub use cipher;
20
21#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
22pub use crate::{decryptor::Decryptor, encryptor::Encryptor};
23
24#[cfg(feature = "chacha20poly1305")]
25pub use crate::chacha20poly1305::{ChaCha20, ChaCha20Poly1305, ChaChaKey, ChaChaNonce};
26
27use cipher::array::{Array, typenum::U16};
28use core::{fmt, str};
29use encoding::{Label, LabelError};
30
31#[cfg(feature = "aes-gcm")]
32use {
33 aead::array::typenum::U12,
34 aes_gcm::{Aes128Gcm, Aes256Gcm},
35};
36
37#[cfg(any(feature = "aes-gcm", feature = "chacha20poly1305"))]
38use aead::{AeadInOut, KeyInit};
39
40const AES128_CBC: &str = "aes128-cbc";
42
43const AES192_CBC: &str = "aes192-cbc";
45
46const AES256_CBC: &str = "aes256-cbc";
48
49const AES128_CTR: &str = "aes128-ctr";
51
52const AES192_CTR: &str = "aes192-ctr";
54
55const AES256_CTR: &str = "aes256-ctr";
57
58const AES128_GCM: &str = "aes128-gcm@openssh.com";
60
61const AES256_GCM: &str = "aes256-gcm@openssh.com";
63
64const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com";
66
67const TDES_CBC: &str = "3des-cbc";
69
70#[cfg(feature = "aes-gcm")]
72pub type AesGcmNonce = Array<u8, U12>;
73
74pub type Tag = Array<u8, U16>;
79
80#[cfg(feature = "aes-ctr")]
82type Ctr128BE<Cipher> = ctr::CtrCore<Cipher, ctr::flavors::Ctr128BE>;
83
84#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
86#[non_exhaustive]
87pub enum Cipher {
88 None,
90
91 Aes128Cbc,
93
94 Aes192Cbc,
96
97 Aes256Cbc,
99
100 Aes128Ctr,
102
103 Aes192Ctr,
105
106 Aes256Ctr,
108
109 Aes128Gcm,
111
112 Aes256Gcm,
114
115 ChaCha20Poly1305,
117
118 TDesCbc,
120}
121
122impl Cipher {
123 pub fn new(ciphername: &str) -> core::result::Result<Self, LabelError> {
140 ciphername.parse()
141 }
142
143 #[must_use]
145 pub fn as_str(self) -> &'static str {
146 match self {
147 Self::None => "none",
148 Self::Aes128Cbc => AES128_CBC,
149 Self::Aes192Cbc => AES192_CBC,
150 Self::Aes256Cbc => AES256_CBC,
151 Self::Aes128Ctr => AES128_CTR,
152 Self::Aes192Ctr => AES192_CTR,
153 Self::Aes256Ctr => AES256_CTR,
154 Self::Aes128Gcm => AES128_GCM,
155 Self::Aes256Gcm => AES256_GCM,
156 Self::ChaCha20Poly1305 => CHACHA20_POLY1305,
157 Self::TDesCbc => TDES_CBC,
158 }
159 }
160
161 #[must_use]
163 pub fn key_and_iv_size(self) -> Option<(usize, usize)> {
164 match self {
165 Self::None => None,
166 Self::Aes128Cbc => Some((16, 16)),
167 Self::Aes192Cbc => Some((24, 16)),
168 Self::Aes256Cbc => Some((32, 16)),
169 Self::Aes128Ctr => Some((16, 16)),
170 Self::Aes192Ctr => Some((24, 16)),
171 Self::Aes256Ctr => Some((32, 16)),
172 Self::Aes128Gcm => Some((16, 12)),
173 Self::Aes256Gcm => Some((32, 12)),
174 Self::ChaCha20Poly1305 => Some((32, 8)),
175 Self::TDesCbc => Some((24, 8)),
176 }
177 }
178
179 #[must_use]
181 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 #[must_use]
199 pub fn padding_len(self, input_size: usize) -> usize {
200 #[allow(
201 clippy::integer_division_remainder_used,
202 reason = "input_size is non-secret"
203 )]
204 match input_size % self.block_size() {
205 0 => 0,
206 input_rem => self.block_size() - input_rem,
207 }
208 }
209
210 #[must_use]
212 pub fn has_tag(self) -> bool {
213 matches!(
214 self,
215 Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305
216 )
217 }
218
219 #[must_use]
221 pub fn is_none(self) -> bool {
222 self == Self::None
223 }
224
225 #[must_use]
227 pub fn is_some(self) -> bool {
228 !self.is_none()
229 }
230
231 #[cfg_attr(
237 not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")),
238 allow(unused_variables)
239 )]
240 pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option<Tag>) -> Result<()> {
241 match self {
242 #[cfg(feature = "aes-gcm")]
243 Self::Aes128Gcm => {
244 let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
245 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
246 let tag = tag.ok_or(Error::TagSize)?;
247 cipher
248 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
249 .map_err(|_| Error::Crypto)?;
250
251 Ok(())
252 }
253 #[cfg(feature = "aes-gcm")]
254 Self::Aes256Gcm => {
255 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
256 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
257 let tag = tag.ok_or(Error::TagSize)?;
258 cipher
259 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
260 .map_err(|_| Error::Crypto)?;
261
262 Ok(())
263 }
264 #[cfg(feature = "chacha20poly1305")]
265 Self::ChaCha20Poly1305 => {
266 let key = key.try_into().map_err(|_| Error::KeySize)?;
267 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
268 let tag = tag.ok_or(Error::TagSize)?;
269 ChaCha20Poly1305::new(key)
270 .decrypt_inout_detached(nonce, &[], buffer.into(), &tag)
271 .map_err(|_| Error::Crypto)
272 }
273 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
275 _ => {
276 if tag.is_some() {
278 return Err(Error::Crypto);
279 }
280
281 self.decryptor(key, iv)?.decrypt(buffer)
282 }
283 #[cfg(not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")))]
284 _ => Err(self.unsupported()),
285 }
286 }
287
288 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
296 pub fn decryptor(self, key: &[u8], iv: &[u8]) -> Result<Decryptor> {
297 Decryptor::new(self, key, iv)
298 }
299
300 #[cfg_attr(
306 not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")),
307 allow(unused_variables)
308 )]
309 pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<Option<Tag>> {
310 match self {
311 #[cfg(feature = "aes-gcm")]
312 Self::Aes128Gcm => {
313 let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
314 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
315 let tag = cipher
316 .encrypt_inout_detached(nonce, &[], buffer.into())
317 .map_err(|_| Error::Crypto)?;
318
319 Ok(Some(tag))
320 }
321 #[cfg(feature = "aes-gcm")]
322 Self::Aes256Gcm => {
323 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
324 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
325 let tag = cipher
326 .encrypt_inout_detached(nonce, &[], buffer.into())
327 .map_err(|_| Error::Crypto)?;
328
329 Ok(Some(tag))
330 }
331 #[cfg(feature = "chacha20poly1305")]
332 Self::ChaCha20Poly1305 => {
333 let key = key.try_into().map_err(|_| Error::KeySize)?;
334 let nonce = iv.try_into().map_err(|_| Error::IvSize)?;
335 let tag = ChaCha20Poly1305::new(key)
336 .encrypt_inout_detached(nonce, &[], buffer.into())
337 .map_err(|_| Error::Crypto)?;
338 Ok(Some(tag))
339 }
340 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
342 _ => {
343 self.encryptor(key, iv)?.encrypt(buffer)?;
344 Ok(None)
345 }
346 #[cfg(not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")))]
347 _ => Err(self.unsupported()),
348 }
349 }
350
351 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
359 pub fn encryptor(self, key: &[u8], iv: &[u8]) -> Result<Encryptor> {
360 Encryptor::new(self, key, iv)
361 }
362
363 #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
365 fn check_key_and_iv(self, key: &[u8], iv: &[u8]) -> Result<()> {
366 let (key_size, iv_size) = self
367 .key_and_iv_size()
368 .ok_or(Error::UnsupportedCipher(self))?;
369
370 if key.len() != key_size {
371 return Err(Error::KeySize);
372 }
373
374 if iv.len() != iv_size {
375 return Err(Error::IvSize);
376 }
377
378 Ok(())
379 }
380
381 fn unsupported(self) -> Error {
383 Error::UnsupportedCipher(self)
384 }
385}
386
387impl AsRef<str> for Cipher {
388 fn as_ref(&self) -> &str {
389 self.as_str()
390 }
391}
392
393impl Label for Cipher {}
394
395impl fmt::Display for Cipher {
396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 f.write_str(self.as_str())
398 }
399}
400
401impl str::FromStr for Cipher {
402 type Err = LabelError;
403
404 fn from_str(ciphername: &str) -> core::result::Result<Self, LabelError> {
405 match ciphername {
406 "none" => Ok(Self::None),
407 AES128_CBC => Ok(Self::Aes128Cbc),
408 AES192_CBC => Ok(Self::Aes192Cbc),
409 AES256_CBC => Ok(Self::Aes256Cbc),
410 AES128_CTR => Ok(Self::Aes128Ctr),
411 AES192_CTR => Ok(Self::Aes192Ctr),
412 AES256_CTR => Ok(Self::Aes256Ctr),
413 AES128_GCM => Ok(Self::Aes128Gcm),
414 AES256_GCM => Ok(Self::Aes256Gcm),
415 CHACHA20_POLY1305 => Ok(Self::ChaCha20Poly1305),
416 TDES_CBC => Ok(Self::TDesCbc),
417 _ => Err(LabelError::new(ciphername)),
418 }
419 }
420}