Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AES-CBC #41

Merged
merged 13 commits into from
May 12, 2022
6 changes: 6 additions & 0 deletions .changes/add-aes-cbc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"iota-crypto": minor
---

Add AES-CBC algorithms (`Aes128CbcHmac256`, `Aes192CbcHmac384`, `Aes256CbcHmac512`).

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ name = "crypto"
[features]
default = [ ]
std = [ ]
aes-cbc = [ "aes-crate", "block-modes", "cipher", "hmac", "sha", "subtle" ]
PhilippGackstatter marked this conversation as resolved.
Show resolved Hide resolved
semenov-vladyslav marked this conversation as resolved.
Show resolved Hide resolved
aes-kw = [ "aes-crate" ]
aes-gcm = [ "aes-gcm-crate", "cipher"]
chacha = [ "chacha20poly1305", "cipher" ]
ed25519 = [ "ed25519-zebra" ]
x25519 = [ "x25519-dalek", "curve25519-dalek" ]
random = [ "getrandom" ]
aes = [ "aes-gcm", "cipher" ]
aes = [ "aes-cbc", "aes-gcm", "aes-kw" ]
blake2b = [ "blake2", "digest" ]
ternary_hashes = [ ]
curl-p = [ "ternary_hashes", "bee-ternary", "lazy_static/spin_no_std" ]
Expand Down Expand Up @@ -62,9 +64,10 @@ slip10 = [ "hmac", "sha", "ed25519", "random", "serde" ]
cipher = [ "aead", "generic-array" ]

[dependencies]
block-modes = { version = "0.8", optional = true, default-features = false }
aead = { version = "0.4", optional = true, default-features = false }
aes-crate = { version = "0.7", optional = true, default-features = false, package = "aes" }
aes-gcm = { version = "0.9", optional = true, default-features = false, features = [ "aes" ] }
aes-gcm-crate = { version = "0.9", optional = true, default-features = false, package = "aes-gcm", features = [ "aes" ] }
bee-common-derive = { version = "0.1.1-alpha", optional = true, default-features = false }
bee-ternary = { version = "0.6.0", optional = true, default-features = false }
blake2 = { version = "0.9", optional = true, default-features = false }
Expand All @@ -78,6 +81,7 @@ hmac_ = { version = "0.11", optional = true, default-features = false, package =
lazy_static = { version = "1.4", optional = true, default-features = false }
pbkdf2 = { version = "0.8", optional = true, default-features = false }
rand = { version = "0.8", optional = true, default-features = false }
subtle = { version = "2.4", default-features = false, optional = true }
sha2 = { version = "0.9", optional = true, default-features = false }
serde = { version = "1.0", optional = true, features = [ "derive" ] }
sha3 = { version = "0.9", optional = true, default-features = false }
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ To be included in this list an implementation must:
| ciphers | AES-256-GCM | [`aes`](/src/ciphers/aes.rs) | [spec][AES-GCM-SPEC] | `aes-gcm` | [nist][AES-GCM-TEST] | ★★★☆☆ |
| ciphers | AES-KW | [`aes-kw`](/src/ciphers/aes_kw.rs) | [spec][AES-GCM-SPEC] | `aes-crate` | [nist][AES-GCM-TEST] | ★★★☆☆ |
| ciphers | XCHACHA20-POLY1305 | [`chacha`](/src/ciphers/chacha.rs) | [rfc][XCHACHA-RFC] | `chacha20poly1305` | [official][XCHACHA-TEST] | ★★★★★ |
| ciphers | AES-CBC | [`aes-cbc`](/src/aes_cbc.rs) | [rfc](https://tools.ietf.org/html/rfc7518) | `crypto.rs` | [official](https://tools.ietf.org/html/rfc7518#appendix-B) | ☆☆☆☆☆ |
| hashes | BLAKE2b-160 | [`blake2b`](/src/hashes/blake2b.rs) | [rfc][BLAKE2B-RFC] | `blake2` | [official][BLAKE2B-TEST] | ★★★★☆ |
| hashes | BLAKE2b-256 | [`blake2b`](/src/hashes/blake2b.rs) | [rfc][BLAKE2B-RFC] | `blake2` | [official][BLAKE2B-TEST] | ★★★★☆ |
| hashes | CURL-P | [`curl-p`](/src/hashes/ternary/curl_p/mod.rs) | [rfc][CURL-RFC] | `bee-ternary` | official | ★★☆☆☆ |
Expand Down
192 changes: 192 additions & 0 deletions src/ciphers/aes_cbc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2020 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use aes_crate::{Aes128, Aes192, Aes256, BlockCipher, NewBlockCipher};
semenov-vladyslav marked this conversation as resolved.
Show resolved Hide resolved
use block_modes::{block_padding::Pkcs7, BlockMode, Cbc};

use core::{
marker::PhantomData,
num::NonZeroUsize,
ops::{Shl, Sub},
};
use generic_array::{
sequence::Split,
typenum::{Double, Unsigned, B1, U16, U24, U32},
ArrayLength,
};
use hmac_::{Hmac, Mac, NewMac};
use sha2::{
digest::{BlockInput, FixedOutput, Reset, Update},
Sha256, Sha384, Sha512,
};
use subtle::ConstantTimeEq;

use crate::ciphers::traits::{Aead, Key, Nonce, Tag};

/// AES-CBC using 128-bit key and HMAC SHA-256.
pub type Aes128CbcHmac256 = AesCbc<Aes128, Sha256, U16, U16>;

/// AES-CBC using 192-bit key and HMAC SHA-384.
pub type Aes192CbcHmac384 = AesCbc<Aes192, Sha384, U24, U24>;

/// AES-CBC using 256-bit key and HMAC SHA-512.
pub type Aes256CbcHmac512 = AesCbc<Aes256, Sha512, U32, U32>;

type AesCbcPkcs7<Cipher> = Cbc<Cipher, Pkcs7>;

type NonceLength = U16;

type DigestOutput<Digest> = <Digest as FixedOutput>::OutputSize;

/// AES in Cipher Block Chaining mode with PKCS #7 padding and HMAC
///
/// See [RFC7518#Section-5.2](https://tools.ietf.org/html/rfc7518#section-5.2)
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AesCbc<Cipher, Digest, KeyLen, TagLen> {
cipher: PhantomData<Cipher>,
digest: PhantomData<Digest>,
key_len: PhantomData<KeyLen>,
tag_len: PhantomData<TagLen>,
}

impl<Cipher, Digest, KeyLen, TagLen> AesCbc<Cipher, Digest, KeyLen, TagLen>
where
Cipher: BlockCipher + NewBlockCipher,
{
const BLOCK_SIZE: usize = <<Cipher as BlockCipher>::BlockSize as Unsigned>::USIZE;
}

impl<Cipher, Digest, KeyLen, TagLen> AesCbc<Cipher, Digest, KeyLen, TagLen>
where
Cipher: BlockCipher
+ NewBlockCipher
+ block_modes::cipher::BlockCipher
+ block_modes::cipher::NewBlockCipher
+ aes_crate::BlockEncrypt
+ aes_crate::BlockDecrypt,
Digest: Clone + Default + BlockInput + FixedOutput + Reset + Update,
KeyLen: ArrayLength<u8> + Shl<B1>,
TagLen: ArrayLength<u8>,
Double<KeyLen>: ArrayLength<u8>,
DigestOutput<Digest>: ArrayLength<u8> + Sub<TagLen, Output = TagLen>,
{
fn compute_tag(
key: &Key<Self>,
nonce: &Nonce<Self>,
associated_data: &[u8],
ciphertext: &[u8],
) -> crate::Result<Tag<Self>> {
// The octet string AL is equal to the number of bits in the Additional
// Authenticated Data A expressed as a 64-bit unsigned big-endian integer.
//
// A message Authentication Tag T is computed by applying HMAC to the
// following data, in order:
//
// the Additional Authenticated Data A,
// the Initialization Vector IV,
// the ciphertext E computed in the previous step, and
// the octet string AL defined above.
//
// The string MAC_KEY is used as the MAC key. We denote the output
// of the MAC computed in this step as M. The first T_LEN octets of
// M are used as T.
let mut hmac: Hmac<Digest> =
Hmac::new_from_slice(&key[..KeyLen::USIZE]).map_err(|_| crate::Error::CipherError { alg: Self::NAME })?;

hmac.update(associated_data);
hmac.update(nonce);
hmac.update(ciphertext);
hmac.update(&((associated_data.len() as u64) * 8).to_be_bytes());

Ok(Split::split(hmac.finalize().into_bytes()).0)
}

fn cipher(key: &Key<Self>, nonce: &Nonce<Self>) -> crate::Result<AesCbcPkcs7<Cipher>> {
AesCbcPkcs7::new_from_slices(&key[KeyLen::USIZE..], nonce)
.map_err(|_| crate::Error::CipherError { alg: Self::NAME })
}
}

impl<Cipher, Digest, KeyLen, TagLen> Aead for AesCbc<Cipher, Digest, KeyLen, TagLen>
where
Cipher: BlockCipher + NewBlockCipher + aes_crate::BlockEncrypt + aes_crate::BlockDecrypt,
Digest: Clone + Default + BlockInput + FixedOutput + Reset + Update,
KeyLen: ArrayLength<u8> + Shl<B1>,
TagLen: ArrayLength<u8>,
Double<KeyLen>: ArrayLength<u8>,
DigestOutput<Digest>: ArrayLength<u8> + Sub<TagLen, Output = TagLen>,
{
type KeyLength = Double<KeyLen>;
type NonceLength = NonceLength;
type TagLength = TagLen;

const NAME: &'static str = "AES-CBC";

fn encrypt(
key: &Key<Self>,
nonce: &Nonce<Self>,
associated_data: &[u8],
plaintext: &[u8],
ciphertext: &mut [u8],
tag: &mut Tag<Self>,
) -> crate::Result<()> {
let padding: usize = Self::padsize(plaintext).map(NonZeroUsize::get).unwrap_or_default();
let expected: usize = plaintext.len() + padding;

if expected > ciphertext.len() {
return Err(crate::Error::BufferSize {
name: "ciphertext",
needs: expected,
has: ciphertext.len(),
});
}

let cipher: AesCbcPkcs7<Cipher> = Self::cipher(key, nonce)?;
let length: usize = plaintext.len();

ciphertext[..length].copy_from_slice(plaintext);

cipher
.encrypt(ciphertext, length)
.map_err(|_| crate::Error::CipherError { alg: Self::NAME })?;

tag.copy_from_slice(&Self::compute_tag(key, nonce, associated_data, ciphertext)?);

Ok(())
}

fn decrypt(
key: &Key<Self>,
nonce: &Nonce<Self>,
associated_data: &[u8],
plaintext: &mut [u8],
ciphertext: &[u8],
tag: &Tag<Self>,
) -> crate::Result<usize> {
if ciphertext.len() > plaintext.len() {
return Err(crate::Error::BufferSize {
name: "plaintext",
needs: ciphertext.len(),
has: plaintext.len(),
});
}

let cipher: AesCbcPkcs7<Cipher> = Self::cipher(key, nonce)?;
let computed: Tag<Self> = Self::compute_tag(key, nonce, associated_data, ciphertext)?;

if !bool::from(computed.ct_eq(tag)) {
return Err(crate::Error::CipherError { alg: Self::NAME });
}

plaintext[..ciphertext.len()].copy_from_slice(ciphertext);

cipher
.decrypt(plaintext)
.map_err(|_| crate::Error::CipherError { alg: Self::NAME })
.map(|output| output.len())
}

fn padsize(plaintext: &[u8]) -> Option<NonZeroUsize> {
NonZeroUsize::new(Self::BLOCK_SIZE - (plaintext.len() % Self::BLOCK_SIZE))
}
}
2 changes: 1 addition & 1 deletion src/ciphers/aes.rs → src/ciphers/aes_gcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

use crate::ciphers::traits::consts::{U12, U16, U32};

pub type Aes256Gcm = aes_gcm::Aes256Gcm;
pub type Aes256Gcm = aes_gcm_crate::Aes256Gcm;
impl_aead!(Aes256Gcm, "AES-256-GCM", U32, U12, U16);
1 change: 1 addition & 0 deletions src/ciphers/macros.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2020 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

#[allow(unused_macros)]
macro_rules! impl_aead {
($impl:ident, $name:expr, $key_len:ident, $nonce_len:ident, $tag_len:ident) => {
impl $crate::ciphers::traits::Aead for $impl {
Expand Down
10 changes: 7 additions & 3 deletions src/ciphers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ mod macros;
#[cfg_attr(docsrs, doc(cfg(feature = "chacha")))]
pub mod chacha;

#[cfg(feature = "aes")]
#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
pub mod aes;
#[cfg(feature = "aes-gcm")]
#[cfg_attr(docsrs, doc(cfg(feature = "aes-gcm")))]
pub mod aes_gcm;

#[cfg(feature = "aes-cbc")]
#[cfg_attr(docsrs, doc(cfg(feature = "aes-cbc")))]
pub mod aes_cbc;

#[cfg(feature = "aes-kw")]
#[cfg_attr(docsrs, doc(cfg(feature = "aes-kw")))]
Expand Down
8 changes: 6 additions & 2 deletions src/ciphers/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ pub type Tag<T> = GenericArray<u8, <T as Aead>::TagLength>;
/// Example using [`Aes256Gcm`][`crate::ciphers::aes::Aes256Gcm`]:
///
/// ```rust
/// # #[cfg(all(feature = "random", feature = "aes-gcm"))]
/// # {
/// use crypto::ciphers::{
/// aes::Aes256Gcm,
/// aes_gcm::Aes256Gcm,
/// traits::{Aead, Key, Nonce, Tag},
/// };
///
/// let plaintext: &[u8] = b"crypto.rs";
/// let associated_data: &[u8] = b"stronghodl";
/// let mut encrypted: Vec<u8> = vec![0; plaintext.len()];
Expand All @@ -43,6 +44,9 @@ pub type Tag<T> = GenericArray<u8, <T as Aead>::TagLength>;
/// assert_eq!(decrypted, plaintext);
///
/// # Ok::<(), crypto::Error>(())
/// # }
/// # #[cfg(not(feature = "random"))]
/// # Ok::<(), crypto::Error>(())
/// ```
pub trait Aead {
/// The size of the [`key`][`Key`] required by this algorithm.
Expand Down
30 changes: 26 additions & 4 deletions tests/aead.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2020 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

#![cfg(any(feature = "aes", feature = "chacha"))]
#![cfg(any(feature = "aes-gcm", feature = "chacha", feature = "aes-cbc"))]

mod utils;

Expand All @@ -25,8 +25,9 @@ fn test_aead_one<A: Aead>(tv: &TestVector) -> crypto::Result<()> {

let expected_ctx = hex::decode(tv.ciphertext).unwrap();
let expected_tag = hex::decode(tv.tag).unwrap();
let padding: usize = A::padsize(&ptx).map(|size| size.get()).unwrap_or_default();

let mut ctx = vec![0; ptx.len()];
let mut ctx = vec![0; ptx.len() + padding];
A::try_encrypt(&key, &nonce, &aad, &ptx, &mut ctx, &mut tag)?;

assert_eq!(&ctx[..], &expected_ctx[..]);
Expand Down Expand Up @@ -73,17 +74,38 @@ fn test_aead_all<A: Aead>(tvs: &[TestVector]) -> crypto::Result<()> {
Ok(())
}

#[cfg(feature = "aes")]
#[cfg(feature = "aes-gcm")]
mod aes {
use super::{test_aead_all, TestVector};
use crypto::ciphers::aes::Aes256Gcm;
use crypto::ciphers::aes_gcm::Aes256Gcm;

#[test]
fn test_vectors_aes_256_gcm() {
test_aead_all::<Aes256Gcm>(&include!("fixtures/aes_256_gcm.rs")).unwrap();
}
}

#[cfg(feature = "aes-cbc")]
mod aes_cbc {
use super::{test_aead_all, TestVector};
use crypto::ciphers::aes_cbc::{Aes128CbcHmac256, Aes192CbcHmac384, Aes256CbcHmac512};

#[test]
fn test_vectors_aes_128_cbc_hmac_256() {
test_aead_all::<Aes128CbcHmac256>(&include!("fixtures/aes_128_cbc_hmac_sha_256.rs")).unwrap();
}

#[test]
fn test_vectors_aes_192_cbc_hmac_384() {
test_aead_all::<Aes192CbcHmac384>(&include!("fixtures/aes_192_cbc_hmac_sha_384.rs")).unwrap();
}

#[test]
fn test_vectors_aes_256_cbc_hmac_512() {
test_aead_all::<Aes256CbcHmac512>(&include!("fixtures/aes_256_cbc_hmac_sha_512.rs")).unwrap();
}
}

#[cfg(feature = "chacha")]
mod chacha {
use super::{test_aead_all, TestVector};
Expand Down
1 change: 0 additions & 1 deletion tests/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ fn test_eq_ord() -> crypto::Result<()> {
assert!(pk == pk_eq);
assert!(pk != pk_diff);
assert!(pk > pk_diff);
assert!(pk <= pk_eq);

Ok(())
}
Expand Down
11 changes: 11 additions & 0 deletions tests/fixtures/aes_128_cbc_hmac_sha_256.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
// https://tools.ietf.org/html/rfc7518#appendix-B.1
TestVector {
key: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
nonce: "1af38c2dc2b96ffdd86694092341bc04",
associated_data: "546865207365636f6e64207072696e6369706c65206f662041756775737465204b6572636b686f666673",
plaintext: "41206369706865722073797374656d206d757374206e6f7420626520726571756972656420746f206265207365637265742c20616e64206974206d7573742062652061626c6520746f2066616c6c20696e746f207468652068616e6473206f662074686520656e656d7920776974686f757420696e636f6e76656e69656e6365",
ciphertext: "c80edfa32ddf39d5ef00c0b468834279a2e46a1b8049f792f76bfe54b903a9c9a94ac9b47ad2655c5f10f9aef71427e2fc6f9b3f399a221489f16362c703233609d45ac69864e3321cf82935ac4096c86e133314c54019e8ca7980dfa4b9cf1b384c486f3a54c51078158ee5d79de59fbd34d848b3d69550a67646344427ade54b8851ffb598f7f80074b9473c82e2db",
tag: "652c3fa36b0a7c5b3219fab3a30bc1c4",
},
]
11 changes: 11 additions & 0 deletions tests/fixtures/aes_192_cbc_hmac_sha_384.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
// https://tools.ietf.org/html/rfc7518#appendix-B.2
TestVector {
key: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
nonce: "1af38c2dc2b96ffdd86694092341bc04",
associated_data: "546865207365636f6e64207072696e6369706c65206f662041756775737465204b6572636b686f666673",
plaintext: "41206369706865722073797374656d206d757374206e6f7420626520726571756972656420746f206265207365637265742c20616e64206974206d7573742062652061626c6520746f2066616c6c20696e746f207468652068616e6473206f662074686520656e656d7920776974686f757420696e636f6e76656e69656e6365",
ciphertext: "ea65da6b59e61edb419be62d19712ae5d303eeb50052d0dfd6697f77224c8edb000d279bdc14c1072654bd30944230c657bed4ca0c9f4a8466f22b226d1746214bf8cfc2400add9f5126e479663fc90b3bed787a2f0ffcbf3904be2a641d5c2105bfe591bae23b1d7449e532eef60a9ac8bb6c6b01d35d49787bcd57ef484927f280adc91ac0c4e79c7b11efc60054e3",
tag: "8490ac0e58949bfe51875d733f93ac2075168039ccc733d7",
},
]
Loading