#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(
clippy::arithmetic_side_effects,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
#[cfg(feature = "std")]
extern crate std;
mod error;
#[cfg(feature = "chacha20poly1305")]
mod chacha20poly1305;
pub use crate::error::{Error, Result};
use core::{fmt, str};
use encoding::{Label, LabelError};
#[cfg(feature = "aes-ctr")]
use cipher::StreamCipherCore;
#[cfg(feature = "aes-gcm")]
use aes_gcm::{aead::AeadInPlace, Aes128Gcm, Aes256Gcm};
#[cfg(feature = "chacha20poly1305")]
use crate::chacha20poly1305::ChaCha20Poly1305;
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
use aes::{Aes128, Aes192, Aes256};
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
use {
cbc::{Decryptor, Encryptor},
cipher::{block_padding::NoPadding, BlockCipher, BlockDecryptMut, BlockEncryptMut},
};
#[cfg(any(feature = "aes-cbc", feature = "aes-gcm", feature = "tdes"))]
use cipher::KeyInit;
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
use cipher::KeyIvInit;
#[cfg(feature = "tdes")]
use des::TdesEde3;
const AES128_CBC: &str = "aes128-cbc";
const AES192_CBC: &str = "aes192-cbc";
const AES256_CBC: &str = "aes256-cbc";
const AES128_CTR: &str = "aes128-ctr";
const AES192_CTR: &str = "aes192-ctr";
const AES256_CTR: &str = "aes256-ctr";
const AES128_GCM: &str = "aes128-gcm@openssh.com";
const AES256_GCM: &str = "aes256-gcm@openssh.com";
const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com";
const TDES_CBC: &str = "3des-cbc";
pub type Nonce = [u8; 12];
pub type Tag = [u8; 16];
#[cfg(feature = "aes-ctr")]
type Ctr128BE<Cipher> = ctr::CtrCore<Cipher, ctr::flavors::Ctr128BE>;
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Cipher {
None,
Aes128Cbc,
Aes192Cbc,
Aes256Cbc,
Aes128Ctr,
Aes192Ctr,
Aes256Ctr,
Aes128Gcm,
Aes256Gcm,
ChaCha20Poly1305,
TDesCbc,
}
impl Cipher {
pub fn new(ciphername: &str) -> core::result::Result<Self, LabelError> {
ciphername.parse()
}
pub fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Aes128Cbc => AES128_CBC,
Self::Aes192Cbc => AES192_CBC,
Self::Aes256Cbc => AES256_CBC,
Self::Aes128Ctr => AES128_CTR,
Self::Aes192Ctr => AES192_CTR,
Self::Aes256Ctr => AES256_CTR,
Self::Aes128Gcm => AES128_GCM,
Self::Aes256Gcm => AES256_GCM,
Self::ChaCha20Poly1305 => CHACHA20_POLY1305,
Self::TDesCbc => TDES_CBC,
}
}
pub fn key_and_iv_size(self) -> Option<(usize, usize)> {
match self {
Self::None => None,
Self::Aes128Cbc => Some((16, 16)),
Self::Aes192Cbc => Some((24, 16)),
Self::Aes256Cbc => Some((32, 16)),
Self::Aes128Ctr => Some((16, 16)),
Self::Aes192Ctr => Some((24, 16)),
Self::Aes256Ctr => Some((32, 16)),
Self::Aes128Gcm => Some((16, 12)),
Self::Aes256Gcm => Some((32, 12)),
Self::ChaCha20Poly1305 => Some((64, 0)),
Self::TDesCbc => Some((24, 8)),
}
}
pub fn block_size(self) -> usize {
match self {
Self::None | Self::ChaCha20Poly1305 | Self::TDesCbc => 8,
Self::Aes128Cbc
| Self::Aes192Cbc
| Self::Aes256Cbc
| Self::Aes128Ctr
| Self::Aes192Ctr
| Self::Aes256Ctr
| Self::Aes128Gcm
| Self::Aes256Gcm => 16,
}
}
#[allow(clippy::arithmetic_side_effects)]
pub fn padding_len(self, input_size: usize) -> usize {
match input_size % self.block_size() {
0 => 0,
input_rem => self.block_size() - input_rem,
}
}
pub fn has_tag(self) -> bool {
matches!(
self,
Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305
)
}
pub fn is_none(self) -> bool {
self == Self::None
}
pub fn is_some(self) -> bool {
!self.is_none()
}
pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option<Tag>) -> Result<()> {
match self {
#[cfg(feature = "aes-cbc")]
Self::Aes128Cbc => {
if tag.is_some() {
return Err(Error::TagSize);
}
cbc_decrypt::<Aes128>(key, iv, buffer)
}
#[cfg(feature = "aes-cbc")]
Self::Aes192Cbc => {
if tag.is_some() {
return Err(Error::TagSize);
}
cbc_decrypt::<Aes192>(key, iv, buffer)
}
#[cfg(feature = "aes-cbc")]
Self::Aes256Cbc => {
if tag.is_some() {
return Err(Error::TagSize);
}
cbc_decrypt::<Aes256>(key, iv, buffer)
}
#[cfg(feature = "aes-ctr")]
Self::Aes128Ctr | Self::Aes192Ctr | Self::Aes256Ctr => {
if tag.is_some() {
return Err(Error::TagSize);
}
self.encrypt(key, iv, buffer)?;
Ok(())
}
#[cfg(feature = "aes-gcm")]
Self::Aes128Gcm => {
let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
let tag = tag.ok_or(Error::TagSize)?;
cipher
.decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into())
.map_err(|_| Error::Crypto)?;
Ok(())
}
#[cfg(feature = "aes-gcm")]
Self::Aes256Gcm => {
let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
let tag = tag.ok_or(Error::TagSize)?;
cipher
.decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into())
.map_err(|_| Error::Crypto)?;
Ok(())
}
#[cfg(feature = "chacha20poly1305")]
Self::ChaCha20Poly1305 => {
let tag = tag.ok_or(Error::TagSize)?;
ChaCha20Poly1305::new(key, iv)?.decrypt(buffer, tag)
}
#[cfg(feature = "tdes")]
Self::TDesCbc => {
if tag.is_some() {
return Err(Error::TagSize);
}
cbc_decrypt::<TdesEde3>(key, iv, buffer)
}
_ => {
let (_, _, _, _) = (key, iv, buffer, tag);
Err(self.unsupported())
}
}
}
pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<Option<Tag>> {
match self {
#[cfg(feature = "aes-cbc")]
Self::Aes128Cbc => {
cbc_encrypt::<Aes128>(key, iv, buffer)?;
Ok(None)
}
#[cfg(feature = "aes-cbc")]
Self::Aes192Cbc => {
cbc_encrypt::<Aes192>(key, iv, buffer)?;
Ok(None)
}
#[cfg(feature = "aes-cbc")]
Self::Aes256Cbc => {
cbc_encrypt::<Aes256>(key, iv, buffer)?;
Ok(None)
}
#[cfg(feature = "aes-ctr")]
Self::Aes128Ctr => {
ctr_encrypt::<Ctr128BE<Aes128>>(key, iv, buffer)?;
Ok(None)
}
#[cfg(feature = "aes-ctr")]
Self::Aes192Ctr => {
ctr_encrypt::<Ctr128BE<Aes192>>(key, iv, buffer)?;
Ok(None)
}
#[cfg(feature = "aes-ctr")]
Self::Aes256Ctr => {
ctr_encrypt::<Ctr128BE<Aes256>>(key, iv, buffer)?;
Ok(None)
}
#[cfg(feature = "aes-gcm")]
Self::Aes128Gcm => {
let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
let tag = cipher
.encrypt_in_place_detached(&nonce.into(), &[], buffer)
.map_err(|_| Error::Crypto)?;
Ok(Some(tag.into()))
}
#[cfg(feature = "aes-gcm")]
Self::Aes256Gcm => {
let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
let tag = cipher
.encrypt_in_place_detached(&nonce.into(), &[], buffer)
.map_err(|_| Error::Crypto)?;
Ok(Some(tag.into()))
}
#[cfg(feature = "chacha20poly1305")]
Self::ChaCha20Poly1305 => {
let tag = ChaCha20Poly1305::new(key, iv)?.encrypt(buffer);
Ok(Some(tag))
}
#[cfg(feature = "tdes")]
Self::TDesCbc => {
cbc_encrypt::<TdesEde3>(key, iv, buffer)?;
Ok(None)
}
_ => {
let (_, _, _) = (key, iv, buffer);
Err(self.unsupported())
}
}
}
fn unsupported(self) -> Error {
Error::UnsupportedCipher(self)
}
}
impl AsRef<str> for Cipher {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Label for Cipher {}
impl fmt::Display for Cipher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for Cipher {
type Err = LabelError;
fn from_str(ciphername: &str) -> core::result::Result<Self, LabelError> {
match ciphername {
"none" => Ok(Self::None),
AES128_CBC => Ok(Self::Aes128Cbc),
AES192_CBC => Ok(Self::Aes192Cbc),
AES256_CBC => Ok(Self::Aes256Cbc),
AES128_CTR => Ok(Self::Aes128Ctr),
AES192_CTR => Ok(Self::Aes192Ctr),
AES256_CTR => Ok(Self::Aes256Ctr),
AES128_GCM => Ok(Self::Aes128Gcm),
AES256_GCM => Ok(Self::Aes256Gcm),
CHACHA20_POLY1305 => Ok(Self::ChaCha20Poly1305),
TDES_CBC => Ok(Self::TDesCbc),
_ => Err(LabelError::new(ciphername)),
}
}
}
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
fn cbc_encrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
where
C: BlockEncryptMut + BlockCipher + KeyInit,
{
let cipher = Encryptor::<C>::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
cipher
.encrypt_padded_mut::<NoPadding>(buffer, buffer.len())
.map_err(|_| Error::Crypto)?;
Ok(())
}
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
fn cbc_decrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
where
C: BlockDecryptMut + BlockCipher + KeyInit,
{
let cipher = Decryptor::<C>::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
cipher
.decrypt_padded_mut::<NoPadding>(buffer)
.map_err(|_| Error::Crypto)?;
Ok(())
}
#[cfg(feature = "aes-ctr")]
fn ctr_encrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
where
C: StreamCipherCore + KeyIvInit,
{
let cipher = C::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
cipher
.try_apply_keystream_partial(buffer.into())
.map_err(|_| Error::Crypto)?;
Ok(())
}