-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds stateful types which can incrementally decrypt/encrypt data in CBC or CTR modes. Closes #164
- Loading branch information
Showing
3 changed files
with
352 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
//! Stateful decryptor object. | ||
|
||
use crate::{Cipher, Error, Result}; | ||
use cipher::KeyIvInit; | ||
|
||
#[cfg(feature = "aes-ctr")] | ||
use crate::{encryptor::ctr_encrypt as ctr_decrypt, Ctr128BE}; | ||
|
||
#[cfg(feature = "tdes")] | ||
use des::TdesEde3; | ||
|
||
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))] | ||
use aes::{Aes128, Aes192, Aes256}; | ||
|
||
#[cfg(any(feature = "aes-cbc", feature = "tdes"))] | ||
use cipher::{Block, BlockCipher, BlockCipherDecrypt, BlockModeDecrypt}; | ||
|
||
/// Stateful decryptor object for unauthenticated SSH symmetric ciphers. | ||
/// | ||
/// Note that this deliberately does not support AEAD modes such as AES-GCM and ChaCha20Poly1305, | ||
/// which are one-shot by design. | ||
pub struct Decryptor { | ||
/// Inner enum over possible decryption ciphers. | ||
inner: Inner, | ||
} | ||
|
||
/// Inner decryptor enum which is deliberately kept out of the public API. | ||
enum Inner { | ||
#[cfg(feature = "aes-cbc")] | ||
Aes128Cbc(cbc::Decryptor<Aes128>), | ||
#[cfg(feature = "aes-cbc")] | ||
Aes192Cbc(cbc::Decryptor<Aes192>), | ||
#[cfg(feature = "aes-cbc")] | ||
Aes256Cbc(cbc::Decryptor<Aes256>), | ||
#[cfg(feature = "aes-ctr")] | ||
Aes128Ctr(Ctr128BE<Aes128>), | ||
#[cfg(feature = "aes-ctr")] | ||
Aes192Ctr(Ctr128BE<Aes192>), | ||
#[cfg(feature = "aes-ctr")] | ||
Aes256Ctr(Ctr128BE<Aes256>), | ||
#[cfg(feature = "tdes")] | ||
TDesCbc(cbc::Decryptor<TdesEde3>), | ||
} | ||
|
||
impl Decryptor { | ||
/// Create a new decryptor object with the given [`Cipher`], key, and IV. | ||
pub fn new(cipher: Cipher, key: &[u8], iv: &[u8]) -> Result<Self> { | ||
let inner = match cipher { | ||
#[cfg(feature = "aes-cbc")] | ||
Cipher::Aes128Cbc => Inner::Aes128Cbc( | ||
cbc::Decryptor::new_from_slices(key, iv).map_err(|_| Error::KeySize)?, | ||
), | ||
#[cfg(feature = "aes-cbc")] | ||
Cipher::Aes192Cbc => Inner::Aes192Cbc( | ||
cbc::Decryptor::new_from_slices(key, iv).map_err(|_| Error::KeySize)?, | ||
), | ||
#[cfg(feature = "aes-cbc")] | ||
Cipher::Aes256Cbc => Inner::Aes256Cbc( | ||
cbc::Decryptor::new_from_slices(key, iv).map_err(|_| Error::KeySize)?, | ||
), | ||
#[cfg(feature = "aes-ctr")] | ||
Cipher::Aes128Ctr => { | ||
Inner::Aes128Ctr(Ctr128BE::new_from_slices(key, iv).map_err(|_| Error::KeySize)?) | ||
} | ||
#[cfg(feature = "aes-ctr")] | ||
Cipher::Aes192Ctr => { | ||
Inner::Aes192Ctr(Ctr128BE::new_from_slices(key, iv).map_err(|_| Error::KeySize)?) | ||
} | ||
#[cfg(feature = "aes-ctr")] | ||
Cipher::Aes256Ctr => { | ||
Inner::Aes256Ctr(Ctr128BE::new_from_slices(key, iv).map_err(|_| Error::KeySize)?) | ||
} | ||
#[cfg(feature = "tdes")] | ||
Cipher::TDesCbc => Inner::TDesCbc( | ||
cbc::Decryptor::new_from_slices(key, iv).map_err(|_| Error::KeySize)?, | ||
), | ||
_ => return Err(cipher.unsupported()), | ||
}; | ||
|
||
Ok(Self { inner }) | ||
} | ||
|
||
/// Get the cipher for this decryptor. | ||
pub fn cipher(&self) -> Cipher { | ||
match &self.inner { | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes128Cbc(_) => Cipher::Aes128Cbc, | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes192Cbc(_) => Cipher::Aes192Cbc, | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes256Cbc(_) => Cipher::Aes256Cbc, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes128Ctr(_) => Cipher::Aes128Ctr, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes192Ctr(_) => Cipher::Aes192Ctr, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes256Ctr(_) => Cipher::Aes256Ctr, | ||
#[cfg(feature = "tdes")] | ||
Inner::TDesCbc(_) => Cipher::TDesCbc, | ||
} | ||
} | ||
|
||
/// Decrypt the given buffer in place, returning [`Error::Crypto`] on padding failure. | ||
pub fn decrypt(&mut self, buffer: &mut [u8]) -> Result<()> { | ||
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] | ||
match &mut self.inner { | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes128Cbc(cipher) => cbc_decrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes192Cbc(cipher) => cbc_decrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes256Cbc(cipher) => cbc_decrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes128Ctr(cipher) => ctr_decrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes192Ctr(cipher) => ctr_decrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes256Ctr(cipher) => ctr_decrypt(cipher, buffer)?, | ||
#[cfg(feature = "tdes")] | ||
Inner::TDesCbc(cipher) => cbc_decrypt(cipher, buffer)?, | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
/// CBC mode decryption helper which assumes the input is unpadded and block-aligned. | ||
#[cfg(any(feature = "aes-cbc", feature = "tdes"))] | ||
fn cbc_decrypt<C>(decryptor: &mut cbc::Decryptor<C>, buffer: &mut [u8]) -> Result<()> | ||
where | ||
C: BlockCipher + BlockCipherDecrypt, | ||
{ | ||
let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer); | ||
|
||
// Ensure input is block-aligned. | ||
if !remaining.is_empty() { | ||
return Err(Error::Crypto); | ||
} | ||
|
||
decryptor.decrypt_blocks(blocks); | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
//! Stateful encryptor object. | ||
|
||
use crate::{Cipher, Error, Result}; | ||
use cipher::{Block, BlockCipher, BlockCipherEncrypt, KeyIvInit}; | ||
|
||
#[cfg(feature = "aes-ctr")] | ||
use { | ||
crate::Ctr128BE, | ||
cipher::{array::sizes::U16, StreamCipherCore}, | ||
}; | ||
|
||
#[cfg(feature = "tdes")] | ||
use des::TdesEde3; | ||
|
||
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))] | ||
use aes::{Aes128, Aes192, Aes256}; | ||
|
||
#[cfg(any(feature = "aes-cbc", feature = "tdes"))] | ||
use cipher::BlockModeEncrypt; | ||
|
||
/// Stateful encryptor object for unauthenticated SSH symmetric ciphers. | ||
/// | ||
/// Note that this deliberately does not support AEAD modes such as AES-GCM and ChaCha20Poly1305, | ||
/// which are one-shot by design. | ||
pub struct Encryptor { | ||
/// Inner enum over possible encryption ciphers. | ||
inner: Inner, | ||
} | ||
|
||
/// Inner encryptor enum which is deliberately kept out of the public API. | ||
enum Inner { | ||
#[cfg(feature = "aes-cbc")] | ||
Aes128Cbc(cbc::Encryptor<Aes128>), | ||
#[cfg(feature = "aes-cbc")] | ||
Aes192Cbc(cbc::Encryptor<Aes192>), | ||
#[cfg(feature = "aes-cbc")] | ||
Aes256Cbc(cbc::Encryptor<Aes256>), | ||
#[cfg(feature = "aes-ctr")] | ||
Aes128Ctr(Ctr128BE<Aes128>), | ||
#[cfg(feature = "aes-ctr")] | ||
Aes192Ctr(Ctr128BE<Aes192>), | ||
#[cfg(feature = "aes-ctr")] | ||
Aes256Ctr(Ctr128BE<Aes256>), | ||
#[cfg(feature = "tdes")] | ||
TDesCbc(cbc::Encryptor<TdesEde3>), | ||
} | ||
|
||
impl Encryptor { | ||
/// Create a new encryptor object with the given [`Cipher`], key, and IV. | ||
pub fn new(cipher: Cipher, key: &[u8], iv: &[u8]) -> Result<Self> { | ||
let inner = match cipher { | ||
#[cfg(feature = "aes-cbc")] | ||
Cipher::Aes128Cbc => Inner::Aes128Cbc( | ||
cbc::Encryptor::new_from_slices(key, iv).map_err(|_| Error::KeySize)?, | ||
), | ||
#[cfg(feature = "aes-cbc")] | ||
Cipher::Aes192Cbc => Inner::Aes192Cbc( | ||
cbc::Encryptor::new_from_slices(key, iv).map_err(|_| Error::KeySize)?, | ||
), | ||
#[cfg(feature = "aes-cbc")] | ||
Cipher::Aes256Cbc => Inner::Aes256Cbc( | ||
cbc::Encryptor::new_from_slices(key, iv).map_err(|_| Error::KeySize)?, | ||
), | ||
#[cfg(feature = "aes-ctr")] | ||
Cipher::Aes128Ctr => { | ||
Inner::Aes128Ctr(Ctr128BE::new_from_slices(key, iv).map_err(|_| Error::KeySize)?) | ||
} | ||
#[cfg(feature = "aes-ctr")] | ||
Cipher::Aes192Ctr => { | ||
Inner::Aes192Ctr(Ctr128BE::new_from_slices(key, iv).map_err(|_| Error::KeySize)?) | ||
} | ||
#[cfg(feature = "aes-ctr")] | ||
Cipher::Aes256Ctr => { | ||
Inner::Aes256Ctr(Ctr128BE::new_from_slices(key, iv).map_err(|_| Error::KeySize)?) | ||
} | ||
#[cfg(feature = "tdes")] | ||
Cipher::TDesCbc => Inner::TDesCbc( | ||
cbc::Encryptor::new_from_slices(key, iv).map_err(|_| Error::KeySize)?, | ||
), | ||
_ => return Err(cipher.unsupported()), | ||
}; | ||
|
||
Ok(Self { inner }) | ||
} | ||
|
||
/// Get the cipher for this encryptor. | ||
pub fn cipher(&self) -> Cipher { | ||
match &self.inner { | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes128Cbc(_) => Cipher::Aes128Cbc, | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes192Cbc(_) => Cipher::Aes192Cbc, | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes256Cbc(_) => Cipher::Aes256Cbc, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes128Ctr(_) => Cipher::Aes128Ctr, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes192Ctr(_) => Cipher::Aes192Ctr, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes256Ctr(_) => Cipher::Aes256Ctr, | ||
#[cfg(feature = "tdes")] | ||
Inner::TDesCbc(_) => Cipher::TDesCbc, | ||
} | ||
} | ||
|
||
/// Encrypt the given buffer in place, returning [`Error::Crypto`] on padding failure. | ||
pub fn encrypt(&mut self, buffer: &mut [u8]) -> Result<()> { | ||
match &mut self.inner { | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes128Cbc(cipher) => cbc_encrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes192Cbc(cipher) => cbc_encrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-cbc")] | ||
Inner::Aes256Cbc(cipher) => cbc_encrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes128Ctr(cipher) => ctr_encrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes192Ctr(cipher) => ctr_encrypt(cipher, buffer)?, | ||
#[cfg(feature = "aes-ctr")] | ||
Inner::Aes256Ctr(cipher) => ctr_encrypt(cipher, buffer)?, | ||
#[cfg(feature = "tdes")] | ||
Inner::TDesCbc(cipher) => cbc_encrypt(cipher, buffer)?, | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
/// CBC mode encryption helper which assumes the input is unpadded and block-aligned. | ||
#[cfg(any(feature = "aes-cbc", feature = "tdes"))] | ||
fn cbc_encrypt<C>(encryptor: &mut cbc::Encryptor<C>, buffer: &mut [u8]) -> Result<()> | ||
where | ||
C: BlockCipher + BlockCipherEncrypt, | ||
{ | ||
let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer); | ||
|
||
// Ensure input is block-aligned. | ||
if !remaining.is_empty() { | ||
return Err(Error::Crypto); | ||
} | ||
|
||
encryptor.encrypt_blocks(blocks); | ||
Ok(()) | ||
} | ||
|
||
/// CTR mode encryption helper which assumes the input is unpadded and block-aligned. | ||
#[cfg(feature = "aes-ctr")] | ||
pub(crate) fn ctr_encrypt<C>(encryptor: &mut Ctr128BE<C>, buffer: &mut [u8]) -> Result<()> | ||
where | ||
C: BlockCipher<BlockSize = U16> + BlockCipherEncrypt, | ||
{ | ||
let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer); | ||
|
||
// Ensure input is block-aligned. | ||
if !remaining.is_empty() { | ||
return Err(Error::Crypto); | ||
} | ||
|
||
encryptor.apply_keystream_blocks(blocks); | ||
Ok(()) | ||
} |
Oops, something went wrong.