From 549326cf818661990010600e9e138dc0481a63eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?=
 =?UTF-8?q?=D0=BE=D0=B2?= <newpavlov@gmail.com>
Date: Sat, 10 Apr 2021 13:39:31 +0300
Subject: [PATCH 1/3] experimental design

---
 Cargo.lock                                    |  56 ++--
 aes/Cargo.toml                                |   4 +-
 aes/src/autodetect.rs                         |  37 ++-
 aes/src/lib.rs                                |  24 +-
 aes/src/ni/aes128.rs                          | 252 +++++++++-------
 aes/src/ni/aes192.rs                          | 265 +++++++++-------
 aes/src/ni/aes256.rs                          | 282 ++++++++++--------
 aes/src/ni/utils.rs                           |  45 ++-
 aes/src/soft/fixslice64.rs                    |  40 +--
 aes/src/soft/impls.rs                         |  80 +++--
 block-modes/Cargo.toml                        |   5 +-
 block-modes/src/cbc.rs                        | 146 ++++-----
 block-modes/src/cfb.rs                        | 172 +++++------
 block-modes/src/cfb8.rs                       | 118 ++++----
 block-modes/src/ecb.rs                        |  81 -----
 block-modes/src/errors.rs                     |  33 --
 block-modes/src/ige.rs                        | 166 ++++++++---
 block-modes/src/lib.rs                        |  53 ++--
 block-modes/src/ofb.rs                        |  72 +++--
 block-modes/src/pcbc.rs                       | 113 +++----
 block-modes/src/traits.rs                     | 126 --------
 block-modes/src/utils.rs                      |  52 ++--
 .../tests/data/cfb8-aes128.ciphertext.bin     |   3 +
 .../tests/data/ecb-aes128.ciphertext.bin      | Bin 288 -> 0 bytes
 block-modes/tests/lib.rs                      | 276 +++++++++++++++--
 25 files changed, 1360 insertions(+), 1141 deletions(-)
 delete mode 100644 block-modes/src/ecb.rs
 delete mode 100644 block-modes/src/errors.rs
 delete mode 100644 block-modes/src/traits.rs
 create mode 100644 block-modes/tests/data/cfb8-aes128.ciphertext.bin
 delete mode 100644 block-modes/tests/data/ecb-aes128.ciphertext.bin

diff --git a/Cargo.lock b/Cargo.lock
index b4203945..f68b2041 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5,7 +5,7 @@ name = "aes"
 version = "0.7.0-pre"
 dependencies = [
  "cfg-if",
- "cipher",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
  "cpuid-bool",
  "ctr",
  "opaque-debug",
@@ -22,23 +22,15 @@ name = "block-modes"
 version = "0.8.0-pre"
 dependencies = [
  "aes",
- "block-padding",
- "cipher",
- "hex-literal",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
 ]
 
-[[package]]
-name = "block-padding"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
-
 [[package]]
 name = "blowfish"
 version = "0.8.0-pre"
 dependencies = [
  "byteorder",
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "opaque-debug",
 ]
 
@@ -53,7 +45,7 @@ name = "cast5"
 version = "0.10.0-pre"
 dependencies = [
  "byteorder",
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex-literal",
  "opaque-debug",
 ]
@@ -74,19 +66,37 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "cipher"
+version = "0.3.0-pre.4"
+source = "git+https://github.com/RustCrypto/traits?branch=experimental_design#7bff7d65873402bf59f1cd09f0d0e23449db8ed9"
+dependencies = [
+ "blobby",
+ "crypto-common",
+ "generic-array",
+]
+
 [[package]]
 name = "cpuid-bool"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
 
+[[package]]
+name = "crypto-common"
+version = "0.1.0"
+source = "git+https://github.com/RustCrypto/traits?branch=experimental_design#7bff7d65873402bf59f1cd09f0d0e23449db8ed9"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "ctr"
 version = "0.7.0-pre.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fa78b1c3f6ce4edd238882705f9589c9f2443bf541dd98d7b75aaa7ca91d4ba6"
 dependencies = [
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -94,7 +104,7 @@ name = "des"
 version = "0.7.0-pre"
 dependencies = [
  "byteorder",
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "opaque-debug",
 ]
 
@@ -113,7 +123,7 @@ name = "gost-modes"
 version = "0.5.0-pre"
 dependencies = [
  "block-modes",
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "generic-array",
  "hex-literal",
  "kuznyechik",
@@ -143,7 +153,7 @@ dependencies = [
 name = "idea"
 version = "0.4.0-pre"
 dependencies = [
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "opaque-debug",
 ]
 
@@ -151,7 +161,7 @@ dependencies = [
 name = "kuznyechik"
 version = "0.7.0-pre"
 dependencies = [
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex-literal",
  "opaque-debug",
 ]
@@ -160,7 +170,7 @@ dependencies = [
 name = "magma"
 version = "0.7.0-pre"
 dependencies = [
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex-literal",
  "opaque-debug",
 ]
@@ -181,7 +191,7 @@ checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598"
 name = "rc2"
 version = "0.7.0-pre"
 dependencies = [
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "opaque-debug",
 ]
 
@@ -190,7 +200,7 @@ name = "serpent"
 version = "0.4.0-pre"
 dependencies = [
  "byteorder",
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "opaque-debug",
 ]
 
@@ -199,7 +209,7 @@ name = "sm4"
 version = "0.4.0-pre"
 dependencies = [
  "byteorder",
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex-literal",
  "opaque-debug",
 ]
@@ -208,7 +218,7 @@ dependencies = [
 name = "threefish"
 version = "0.4.0-pre"
 dependencies = [
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -216,7 +226,7 @@ name = "twofish"
 version = "0.6.0-pre"
 dependencies = [
  "byteorder",
- "cipher",
+ "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex-literal",
  "opaque-debug",
 ]
diff --git a/aes/Cargo.toml b/aes/Cargo.toml
index efbc4314..583fdb82 100644
--- a/aes/Cargo.toml
+++ b/aes/Cargo.toml
@@ -16,12 +16,12 @@ categories = ["cryptography", "no-std"]
 
 [dependencies]
 cfg-if = "1"
-cipher = "=0.3.0-pre.4"
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
 ctr = { version = "=0.7.0-pre.3", optional = true }
 opaque-debug = "0.3"
 
 [dev-dependencies]
-cipher = { version = "=0.3.0-pre.4", features = ["dev"] }
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design", features = ["dev"] }
 
 [target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies]
 cpuid-bool = "0.2"
diff --git a/aes/src/autodetect.rs b/aes/src/autodetect.rs
index 50e3f822..8f989874 100644
--- a/aes/src/autodetect.rs
+++ b/aes/src/autodetect.rs
@@ -1,12 +1,12 @@
 //! Autodetection support for hardware accelerated AES backends with fallback
 //! to the fixsliced "soft" implementation.
 
-use crate::{Block, ParBlocks};
 use cipher::{
-    consts::{U16, U24, U32, U8},
+    consts::{U16, U24, U32},
     generic_array::GenericArray,
-    BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
+    BlockCipher, BlockProcessing, BlockDecrypt, BlockEncrypt, KeyInit, InOutVal, InOutBuf, InResOutBuf,
 };
+use crate::Block;
 use core::mem::ManuallyDrop;
 
 cpuid_bool::new!(aes_cpuid, "aes");
@@ -33,7 +33,7 @@ macro_rules! define_aes_impl {
             }
         }
 
-        impl NewBlockCipher for $name {
+        impl KeyInit for $name {
             type KeySize = $key_size;
 
             #[inline]
@@ -62,14 +62,13 @@ macro_rules! define_aes_impl {
             }
         }
 
-        impl BlockCipher for $name {
+        impl BlockProcessing for $name {
             type BlockSize = U16;
-            type ParBlocks = U8;
         }
 
         impl BlockEncrypt for $name {
             #[inline]
-            fn encrypt_block(&self, block: &mut Block) {
+            fn encrypt_block(&self, block: impl InOutVal<Block>) {
                 if self.token.get() {
                     unsafe { self.inner.ni.encrypt_block(block) }
                 } else {
@@ -78,18 +77,22 @@ macro_rules! define_aes_impl {
             }
 
             #[inline]
-            fn encrypt_par_blocks(&self, blocks: &mut ParBlocks) {
+            fn encrypt_blocks(
+                &self,
+                blocks: InOutBuf<'_, '_, Block>,
+                proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+            ) {
                 if self.token.get() {
-                    unsafe { self.inner.ni.encrypt_par_blocks(blocks) }
+                    unsafe { self.inner.ni.encrypt_blocks(blocks, proc) }
                 } else {
-                    unsafe { self.inner.soft.encrypt_par_blocks(blocks) }
+                    unsafe { self.inner.soft.encrypt_blocks(blocks, proc) }
                 }
             }
         }
 
         impl BlockDecrypt for $name {
             #[inline]
-            fn decrypt_block(&self, block: &mut Block) {
+            fn decrypt_block(&self, block: impl InOutVal<Block>) {
                 if self.token.get() {
                     unsafe { self.inner.ni.decrypt_block(block) }
                 } else {
@@ -98,15 +101,21 @@ macro_rules! define_aes_impl {
             }
 
             #[inline]
-            fn decrypt_par_blocks(&self, blocks: &mut ParBlocks) {
+            fn decrypt_blocks(
+                &self,
+                blocks: InOutBuf<'_, '_, Block>,
+                proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+            ) {
                 if self.token.get() {
-                    unsafe { self.inner.ni.decrypt_par_blocks(blocks) }
+                    unsafe { self.inner.ni.decrypt_blocks(blocks, proc) }
                 } else {
-                    unsafe { self.inner.soft.decrypt_par_blocks(blocks) }
+                    unsafe { self.inner.soft.decrypt_blocks(blocks, proc) }
                 }
             }
         }
 
+        impl BlockCipher for $name {}
+
         opaque_debug::implement!($name);
     }
 }
diff --git a/aes/src/lib.rs b/aes/src/lib.rs
index 68a951d6..d7a58e86 100644
--- a/aes/src/lib.rs
+++ b/aes/src/lib.rs
@@ -19,13 +19,12 @@
 //! ```
 //! use aes::Aes128;
 //! use aes::cipher::{
-//!     BlockCipher, BlockEncrypt, BlockDecrypt, NewBlockCipher,
+//!     BlockEncrypt, BlockDecrypt, KeyInit,
 //!     generic_array::GenericArray,
 //! };
 //!
 //! let key = GenericArray::from_slice(&[0u8; 16]);
 //! let mut block = GenericArray::clone_from_slice(&[0u8; 16]);
-//! let mut block8 = GenericArray::clone_from_slice(&[block; 8]);
 //! // Initialize cipher
 //! let cipher = Aes128::new(&key);
 //!
@@ -36,12 +35,11 @@
 //! cipher.decrypt_block(&mut block);
 //! assert_eq!(block, block_copy);
 //!
-//! // We can encrypt 8 blocks simultaneously using
-//! // instruction-level parallelism
-//! let block8_copy = block8.clone();
-//! cipher.encrypt_par_blocks(&mut block8);
-//! cipher.decrypt_par_blocks(&mut block8);
-//! assert_eq!(block8, block8_copy);
+//! // It's also possible to process data in buffer-to-buffer fashion
+//! let mut res = GenericArray::default();
+//! cipher.encrypt_block((&block, &mut res));
+//! cipher.decrypt_block((&res, &mut block));
+//! assert_eq!(block, block_copy);
 //! ```
 //!
 //! For implementations of block cipher modes of operation see
@@ -83,10 +81,10 @@ cfg_if! {
     }
 }
 
-pub use cipher::{self, BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher};
+pub use cipher::{self, BlockDecrypt, BlockEncrypt, KeyInit};
 
-/// 128-bit AES block
-pub type Block = cipher::generic_array::GenericArray<u8, cipher::consts::U16>;
+use cipher::generic_array::{GenericArray, typenum::{U16, U8, U2}};
 
-/// 8 x 128-bit AES blocks to be processed in parallel
-pub type ParBlocks = cipher::generic_array::GenericArray<Block, cipher::consts::U8>;
+type Block = GenericArray<u8, U16>;
+type Block2 = GenericArray<Block, U2>;
+type Block8 = GenericArray<Block, U8>;
diff --git a/aes/src/ni/aes128.rs b/aes/src/ni/aes128.rs
index 16f29343..486c4d6c 100644
--- a/aes/src/ni/aes128.rs
+++ b/aes/src/ni/aes128.rs
@@ -1,14 +1,15 @@
 use super::{
     arch::*,
     utils::{
-        aesdec8, aesdeclast8, aesenc8, aesenclast8, load8, store8, xor8, Block128, Block128x8,
-        U128x8,
+        aesdec8, aesdeclast8, aesenc8, aesenclast8, load8, store8, xor8
     },
 };
+use crate::{Block, Block8};
 use cipher::{
     consts::{U16, U8},
     generic_array::GenericArray,
-    BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
+    BlockCipher, BlockDecrypt, BlockEncrypt, KeyInit, BlockProcessing,
+    InOutVal, InOutBuf, InResOutBuf,
 };
 
 mod expand;
@@ -25,58 +26,13 @@ pub struct Aes128 {
     decrypt_keys: RoundKeys,
 }
 
-impl Aes128 {
-    #[inline(always)]
-    pub(crate) fn encrypt8(&self, mut blocks: U128x8) -> U128x8 {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aesni128_encrypt8(keys: &RoundKeys, blocks: &mut U128x8) {
-            xor8(blocks, keys[0]);
-            aesenc8(blocks, keys[1]);
-            aesenc8(blocks, keys[2]);
-            aesenc8(blocks, keys[3]);
-            aesenc8(blocks, keys[4]);
-            aesenc8(blocks, keys[5]);
-            aesenc8(blocks, keys[6]);
-            aesenc8(blocks, keys[7]);
-            aesenc8(blocks, keys[8]);
-            aesenc8(blocks, keys[9]);
-            aesenclast8(blocks, keys[10]);
-        }
-        unsafe { aesni128_encrypt8(&self.encrypt_keys, &mut blocks) };
-        blocks
-    }
-
-    #[inline(always)]
-    pub(crate) fn encrypt(&self, block: __m128i) -> __m128i {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aesni128_encrypt1(keys: &RoundKeys, mut block: __m128i) -> __m128i {
-            block = _mm_xor_si128(block, keys[0]);
-            block = _mm_aesenc_si128(block, keys[1]);
-            block = _mm_aesenc_si128(block, keys[2]);
-            block = _mm_aesenc_si128(block, keys[3]);
-            block = _mm_aesenc_si128(block, keys[4]);
-            block = _mm_aesenc_si128(block, keys[5]);
-            block = _mm_aesenc_si128(block, keys[6]);
-            block = _mm_aesenc_si128(block, keys[7]);
-            block = _mm_aesenc_si128(block, keys[8]);
-            block = _mm_aesenc_si128(block, keys[9]);
-            _mm_aesenclast_si128(block, keys[10])
-        }
-        unsafe { aesni128_encrypt1(&self.encrypt_keys, block) }
-    }
-}
-
-impl NewBlockCipher for Aes128 {
+impl KeyInit for Aes128 {
     type KeySize = U16;
 
     #[inline]
     fn new(key: &GenericArray<u8, U16>) -> Self {
         let key = unsafe { &*(key as *const _ as *const [u8; 16]) };
-
         let (encrypt_keys, decrypt_keys) = expand::expand(key);
-
         Self {
             encrypt_keys,
             decrypt_keys,
@@ -84,82 +40,160 @@ impl NewBlockCipher for Aes128 {
     }
 }
 
-impl BlockCipher for Aes128 {
+impl BlockProcessing for Aes128 {
     type BlockSize = U16;
-    type ParBlocks = U8;
 }
 
+impl BlockCipher for Aes128 {}
+
 impl BlockEncrypt for Aes128 {
-    #[inline]
-    fn encrypt_block(&self, block: &mut Block128) {
-        // Safety: `loadu` and `storeu` support unaligned access
-        #[allow(clippy::cast_ptr_alignment)]
+    fn encrypt_block(&self, mut block: impl InOutVal<Block>) {
+        let in_ptr = block.get_in() as *const Block;
+        let out_ptr = block.get_out() as *mut Block;
         unsafe {
-            let b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
-            let b = self.encrypt(b);
-            _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
+            aes128_encrypt1(&self.encrypt_keys, in_ptr, out_ptr);
         }
     }
 
-    #[inline]
-    fn encrypt_par_blocks(&self, blocks: &mut Block128x8) {
-        let b = self.encrypt8(load8(blocks));
-        store8(blocks, b);
+    fn encrypt_blocks(
+        &self,
+        mut blocks: InOutBuf<'_, '_, Block>,
+        proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+    ) {
+        blocks.chunks::<U8, _, _, _, _>(
+            &self.encrypt_keys,
+            |keys, inc, res| unsafe {
+                aes128_encrypt8(
+                    keys,
+                    inc as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            |keys, inc, res| unsafe {
+                let n = inc.len();
+                res[..n].copy_from_slice(inc);
+                aes128_encrypt8(
+                    keys,
+                    res as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            proc,
+        );
     }
 }
 
 impl BlockDecrypt for Aes128 {
-    #[inline]
-    fn decrypt_block(&self, block: &mut Block128) {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aes128_decrypt1(block: &mut Block128, keys: &RoundKeys) {
-            // Safety: `loadu` and `storeu` support unaligned access
-            #[allow(clippy::cast_ptr_alignment)]
-            let mut b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
-
-            b = _mm_xor_si128(b, keys[10]);
-            b = _mm_aesdec_si128(b, keys[9]);
-            b = _mm_aesdec_si128(b, keys[8]);
-            b = _mm_aesdec_si128(b, keys[7]);
-            b = _mm_aesdec_si128(b, keys[6]);
-            b = _mm_aesdec_si128(b, keys[5]);
-            b = _mm_aesdec_si128(b, keys[4]);
-            b = _mm_aesdec_si128(b, keys[3]);
-            b = _mm_aesdec_si128(b, keys[2]);
-            b = _mm_aesdec_si128(b, keys[1]);
-            b = _mm_aesdeclast_si128(b, keys[0]);
-
-            // Safety: `loadu` and `storeu` support unaligned access
-            #[allow(clippy::cast_ptr_alignment)]
-            _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
+    fn decrypt_block(&self, mut block: impl InOutVal<Block>) {
+        let in_ptr = block.get_in() as *const Block;
+        let out_ptr = block.get_out() as *mut Block;
+        unsafe {
+            aes128_decrypt1(&self.decrypt_keys, in_ptr, out_ptr);
         }
-
-        unsafe { aes128_decrypt1(block, &self.decrypt_keys) }
     }
 
-    #[inline]
-    fn decrypt_par_blocks(&self, blocks: &mut Block128x8) {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aes128_decrypt8(blocks: &mut Block128x8, keys: &RoundKeys) {
-            let mut b = load8(blocks);
-            xor8(&mut b, keys[10]);
-            aesdec8(&mut b, keys[9]);
-            aesdec8(&mut b, keys[8]);
-            aesdec8(&mut b, keys[7]);
-            aesdec8(&mut b, keys[6]);
-            aesdec8(&mut b, keys[5]);
-            aesdec8(&mut b, keys[4]);
-            aesdec8(&mut b, keys[3]);
-            aesdec8(&mut b, keys[2]);
-            aesdec8(&mut b, keys[1]);
-            aesdeclast8(&mut b, keys[0]);
-            store8(blocks, b);
-        }
-
-        unsafe { aes128_decrypt8(blocks, &self.decrypt_keys) }
+    fn decrypt_blocks(
+        &self,
+        mut blocks: InOutBuf<'_, '_, Block>,
+        proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+    ) {
+        blocks.chunks::<U8, _, _, _, _>(
+            &self.decrypt_keys,
+            |keys, inc, res| unsafe {
+                aes128_decrypt8(
+                    keys,
+                    inc as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            |keys, inc, res| unsafe {
+                let n = inc.len();
+                res[..n].copy_from_slice(inc);
+                aes128_decrypt8(
+                    keys,
+                    res as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            proc,
+        );
     }
 }
 
 opaque_debug::implement!(Aes128);
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes128_encrypt1(keys: &RoundKeys, in_ptr: *const Block, out_ptr: *mut Block) {
+    // Safety: `loadu` and `storeu` support unaligned access
+    #[allow(clippy::cast_ptr_alignment)]
+    let mut block = _mm_loadu_si128(in_ptr as *const __m128i);
+    block = _mm_xor_si128(block, keys[0]);
+    block = _mm_aesenc_si128(block, keys[1]);
+    block = _mm_aesenc_si128(block, keys[2]);
+    block = _mm_aesenc_si128(block, keys[3]);
+    block = _mm_aesenc_si128(block, keys[4]);
+    block = _mm_aesenc_si128(block, keys[5]);
+    block = _mm_aesenc_si128(block, keys[6]);
+    block = _mm_aesenc_si128(block, keys[7]);
+    block = _mm_aesenc_si128(block, keys[8]);
+    block = _mm_aesenc_si128(block, keys[9]);
+    block = _mm_aesenclast_si128(block, keys[10]);
+    _mm_storeu_si128(out_ptr as *mut __m128i, block);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes128_encrypt8(keys: &RoundKeys, in_ptr: *const Block8, out_ptr: *mut Block8) {
+    let mut blocks = load8(in_ptr);
+    xor8(&mut blocks, keys[0]);
+    aesenc8(&mut blocks, keys[1]);
+    aesenc8(&mut blocks, keys[2]);
+    aesenc8(&mut blocks, keys[3]);
+    aesenc8(&mut blocks, keys[4]);
+    aesenc8(&mut blocks, keys[5]);
+    aesenc8(&mut blocks, keys[6]);
+    aesenc8(&mut blocks, keys[7]);
+    aesenc8(&mut blocks, keys[8]);
+    aesenc8(&mut blocks, keys[9]);
+    aesenclast8(&mut blocks, keys[10]);
+    store8(out_ptr, blocks);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+unsafe fn aes128_decrypt1(keys: &RoundKeys, in_ptr: *const Block, out_ptr: *mut Block) {
+    // Safety: `loadu` and `storeu` support unaligned access
+    #[allow(clippy::cast_ptr_alignment)]
+    let mut block = _mm_loadu_si128(in_ptr as *const __m128i);
+    block = _mm_xor_si128(block, keys[10]);
+    block = _mm_aesdec_si128(block, keys[9]);
+    block = _mm_aesdec_si128(block, keys[8]);
+    block = _mm_aesdec_si128(block, keys[7]);
+    block = _mm_aesdec_si128(block, keys[6]);
+    block = _mm_aesdec_si128(block, keys[5]);
+    block = _mm_aesdec_si128(block, keys[4]);
+    block = _mm_aesdec_si128(block, keys[3]);
+    block = _mm_aesdec_si128(block, keys[2]);
+    block = _mm_aesdec_si128(block, keys[1]);
+    block = _mm_aesdeclast_si128(block, keys[0]);
+    _mm_storeu_si128(out_ptr as *mut __m128i, block);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes128_decrypt8(keys: &RoundKeys, in_ptr: *const Block8, out_ptr: *mut Block8) {
+    let mut blocks = load8(in_ptr);
+    xor8(&mut blocks, keys[10]);
+    aesdec8(&mut blocks, keys[9]);
+    aesdec8(&mut blocks, keys[8]);
+    aesdec8(&mut blocks, keys[7]);
+    aesdec8(&mut blocks, keys[6]);
+    aesdec8(&mut blocks, keys[5]);
+    aesdec8(&mut blocks, keys[4]);
+    aesdec8(&mut blocks, keys[3]);
+    aesdec8(&mut blocks, keys[2]);
+    aesdec8(&mut blocks, keys[1]);
+    aesdeclast8(&mut blocks, keys[0]);
+    store8(out_ptr, blocks);
+}
diff --git a/aes/src/ni/aes192.rs b/aes/src/ni/aes192.rs
index 6ca30f05..92437057 100644
--- a/aes/src/ni/aes192.rs
+++ b/aes/src/ni/aes192.rs
@@ -1,15 +1,15 @@
 use super::{
     arch::*,
     utils::{
-        aesdec8, aesdeclast8, aesenc8, aesenclast8, load8, store8, xor8, Block128, Block128x8,
-        U128x8,
+        aesdec8, aesdeclast8, aesenc8, aesenclast8, load8, store8, xor8,
     },
 };
 use cipher::{
     consts::{U16, U24, U8},
     generic_array::GenericArray,
-    BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
+    BlockCipher, BlockProcessing, BlockDecrypt, BlockEncrypt, KeyInit, InOutVal, InOutBuf, InResOutBuf,
 };
+use crate::{Block, Block8};
 
 mod expand;
 #[cfg(test)]
@@ -25,54 +25,7 @@ pub struct Aes192 {
     decrypt_keys: RoundKeys,
 }
 
-impl Aes192 {
-    #[inline(always)]
-    pub(crate) fn encrypt8(&self, mut blocks: U128x8) -> U128x8 {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aesni192_encrypt8(keys: &RoundKeys, blocks: &mut U128x8) {
-            xor8(blocks, keys[0]);
-            aesenc8(blocks, keys[1]);
-            aesenc8(blocks, keys[2]);
-            aesenc8(blocks, keys[3]);
-            aesenc8(blocks, keys[4]);
-            aesenc8(blocks, keys[5]);
-            aesenc8(blocks, keys[6]);
-            aesenc8(blocks, keys[7]);
-            aesenc8(blocks, keys[8]);
-            aesenc8(blocks, keys[9]);
-            aesenc8(blocks, keys[10]);
-            aesenc8(blocks, keys[11]);
-            aesenclast8(blocks, keys[12]);
-        }
-        unsafe { aesni192_encrypt8(&self.encrypt_keys, &mut blocks) };
-        blocks
-    }
-
-    #[inline(always)]
-    pub(crate) fn encrypt(&self, block: __m128i) -> __m128i {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aesni192_encrypt1(keys: &RoundKeys, mut block: __m128i) -> __m128i {
-            block = _mm_xor_si128(block, keys[0]);
-            block = _mm_aesenc_si128(block, keys[1]);
-            block = _mm_aesenc_si128(block, keys[2]);
-            block = _mm_aesenc_si128(block, keys[3]);
-            block = _mm_aesenc_si128(block, keys[4]);
-            block = _mm_aesenc_si128(block, keys[5]);
-            block = _mm_aesenc_si128(block, keys[6]);
-            block = _mm_aesenc_si128(block, keys[7]);
-            block = _mm_aesenc_si128(block, keys[8]);
-            block = _mm_aesenc_si128(block, keys[9]);
-            block = _mm_aesenc_si128(block, keys[10]);
-            block = _mm_aesenc_si128(block, keys[11]);
-            _mm_aesenclast_si128(block, keys[12])
-        }
-        unsafe { aesni192_encrypt1(&self.encrypt_keys, block) }
-    }
-}
-
-impl NewBlockCipher for Aes192 {
+impl KeyInit for Aes192 {
     type KeySize = U24;
 
     #[inline]
@@ -86,86 +39,168 @@ impl NewBlockCipher for Aes192 {
     }
 }
 
-impl BlockCipher for Aes192 {
+impl BlockProcessing for Aes192 {
     type BlockSize = U16;
-    type ParBlocks = U8;
 }
 
+impl BlockCipher for Aes192 {}
+
 impl BlockEncrypt for Aes192 {
-    #[inline]
-    fn encrypt_block(&self, block: &mut Block128) {
-        // Safety: `loadu` and `storeu` support unaligned access
-        #[allow(clippy::cast_ptr_alignment)]
+    fn encrypt_block(&self, mut block: impl InOutVal<Block>) {
+        let in_ptr = block.get_in() as *const Block;
+        let out_ptr = block.get_out() as *mut Block;
         unsafe {
-            let b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
-            let b = self.encrypt(b);
-            _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
+            aes192_encrypt1(&self.encrypt_keys, in_ptr, out_ptr);
         }
     }
 
-    #[inline]
-    fn encrypt_par_blocks(&self, blocks: &mut Block128x8) {
-        let b = self.encrypt8(load8(blocks));
-        store8(blocks, b);
+    fn encrypt_blocks(
+        &self,
+        mut blocks: InOutBuf<'_, '_, Block>,
+        proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+    ) {
+        blocks.chunks::<U8, _, _, _, _>(
+            &self.encrypt_keys,
+            |keys, inc, res| unsafe {
+                aes192_encrypt8(
+                    keys,
+                    inc as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            |keys, inc, res| unsafe {
+                let n = inc.len();
+                res[..n].copy_from_slice(inc);
+                aes192_encrypt8(
+                    keys,
+                    res as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            proc,
+        );
     }
 }
 
 impl BlockDecrypt for Aes192 {
-    #[inline]
-    fn decrypt_block(&self, block: &mut Block128) {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aes192_decrypt1(block: &mut Block128, keys: &RoundKeys) {
-            // Safety: `loadu` and `storeu` support unaligned access
-            #[allow(clippy::cast_ptr_alignment)]
-            let mut b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
-
-            b = _mm_xor_si128(b, keys[12]);
-            b = _mm_aesdec_si128(b, keys[11]);
-            b = _mm_aesdec_si128(b, keys[10]);
-            b = _mm_aesdec_si128(b, keys[9]);
-            b = _mm_aesdec_si128(b, keys[8]);
-            b = _mm_aesdec_si128(b, keys[7]);
-            b = _mm_aesdec_si128(b, keys[6]);
-            b = _mm_aesdec_si128(b, keys[5]);
-            b = _mm_aesdec_si128(b, keys[4]);
-            b = _mm_aesdec_si128(b, keys[3]);
-            b = _mm_aesdec_si128(b, keys[2]);
-            b = _mm_aesdec_si128(b, keys[1]);
-            b = _mm_aesdeclast_si128(b, keys[0]);
-
-            // Safety: `loadu` and `storeu` support unaligned access
-            #[allow(clippy::cast_ptr_alignment)]
-            _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
+    fn decrypt_block(&self, mut block: impl InOutVal<Block>) {
+        let in_ptr = block.get_in() as *const Block;
+        let out_ptr = block.get_out() as *mut Block;
+        unsafe {
+            aes192_decrypt1(&self.decrypt_keys, in_ptr, out_ptr);
         }
-
-        unsafe { aes192_decrypt1(block, &self.decrypt_keys) }
     }
 
-    #[inline]
-    fn decrypt_par_blocks(&self, blocks: &mut Block128x8) {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aes192_decrypt8(blocks: &mut Block128x8, keys: &RoundKeys) {
-            let mut b = load8(blocks);
-            xor8(&mut b, keys[12]);
-            aesdec8(&mut b, keys[11]);
-            aesdec8(&mut b, keys[10]);
-            aesdec8(&mut b, keys[9]);
-            aesdec8(&mut b, keys[8]);
-            aesdec8(&mut b, keys[7]);
-            aesdec8(&mut b, keys[6]);
-            aesdec8(&mut b, keys[5]);
-            aesdec8(&mut b, keys[4]);
-            aesdec8(&mut b, keys[3]);
-            aesdec8(&mut b, keys[2]);
-            aesdec8(&mut b, keys[1]);
-            aesdeclast8(&mut b, keys[0]);
-            store8(blocks, b);
-        }
-
-        unsafe { aes192_decrypt8(blocks, &self.decrypt_keys) }
+    fn decrypt_blocks(
+        &self,
+        mut blocks: InOutBuf<'_, '_, Block>,
+        proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+    ) {
+        blocks.chunks::<U8, _, _, _, _>(
+            &self.decrypt_keys,
+            |keys, inc, res| unsafe {
+                aes192_decrypt8(
+                    keys,
+                    inc as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            |keys, inc, res| unsafe {
+                let n = inc.len();
+                res[..n].copy_from_slice(inc);
+                aes192_decrypt8(
+                    keys,
+                    res as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            proc,
+        );
     }
 }
 
 opaque_debug::implement!(Aes192);
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes192_encrypt1(keys: &RoundKeys, in_ptr: *const Block, out_ptr: *mut Block) {
+    // Safety: `loadu` and `storeu` support unaligned access
+    #[allow(clippy::cast_ptr_alignment)]
+    let mut block = _mm_loadu_si128(in_ptr as *const __m128i);
+    block = _mm_xor_si128(block, keys[0]);
+    block = _mm_aesenc_si128(block, keys[1]);
+    block = _mm_aesenc_si128(block, keys[2]);
+    block = _mm_aesenc_si128(block, keys[3]);
+    block = _mm_aesenc_si128(block, keys[4]);
+    block = _mm_aesenc_si128(block, keys[5]);
+    block = _mm_aesenc_si128(block, keys[6]);
+    block = _mm_aesenc_si128(block, keys[7]);
+    block = _mm_aesenc_si128(block, keys[8]);
+    block = _mm_aesenc_si128(block, keys[9]);
+    block = _mm_aesenc_si128(block, keys[10]);
+    block = _mm_aesenc_si128(block, keys[11]);
+    block = _mm_aesenclast_si128(block, keys[12]);
+    _mm_storeu_si128(out_ptr as *mut __m128i, block);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes192_encrypt8(keys: &RoundKeys, in_ptr: *const Block8, out_ptr: *mut Block8) {
+    let mut blocks = load8(in_ptr);
+    xor8(&mut blocks, keys[0]);
+    aesenc8(&mut blocks, keys[1]);
+    aesenc8(&mut blocks, keys[2]);
+    aesenc8(&mut blocks, keys[3]);
+    aesenc8(&mut blocks, keys[4]);
+    aesenc8(&mut blocks, keys[5]);
+    aesenc8(&mut blocks, keys[6]);
+    aesenc8(&mut blocks, keys[7]);
+    aesenc8(&mut blocks, keys[8]);
+    aesenc8(&mut blocks, keys[9]);
+    aesenc8(&mut blocks, keys[10]);
+    aesenc8(&mut blocks, keys[11]);
+    aesenclast8(&mut blocks, keys[12]);
+    store8(out_ptr, blocks);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+unsafe fn aes192_decrypt1(keys: &RoundKeys, in_ptr: *const Block, out_ptr: *mut Block) {
+    // Safety: `loadu` and `storeu` support unaligned access
+    #[allow(clippy::cast_ptr_alignment)]
+    let mut block = _mm_loadu_si128(in_ptr as *const __m128i);
+    block = _mm_xor_si128(block, keys[12]);
+    block = _mm_aesdec_si128(block, keys[11]);
+    block = _mm_aesdec_si128(block, keys[10]);
+    block = _mm_aesdec_si128(block, keys[9]);
+    block = _mm_aesdec_si128(block, keys[8]);
+    block = _mm_aesdec_si128(block, keys[7]);
+    block = _mm_aesdec_si128(block, keys[6]);
+    block = _mm_aesdec_si128(block, keys[5]);
+    block = _mm_aesdec_si128(block, keys[4]);
+    block = _mm_aesdec_si128(block, keys[3]);
+    block = _mm_aesdec_si128(block, keys[2]);
+    block = _mm_aesdec_si128(block, keys[1]);
+    block = _mm_aesdeclast_si128(block, keys[0]);
+    _mm_storeu_si128(out_ptr as *mut __m128i, block);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes192_decrypt8(keys: &RoundKeys, in_ptr: *const Block8, out_ptr: *mut Block8) {
+    let mut blocks = load8(in_ptr);
+    xor8(&mut blocks, keys[12]);
+    aesdec8(&mut blocks, keys[11]);
+    aesdec8(&mut blocks, keys[10]);
+    aesdec8(&mut blocks, keys[9]);
+    aesdec8(&mut blocks, keys[8]);
+    aesdec8(&mut blocks, keys[7]);
+    aesdec8(&mut blocks, keys[6]);
+    aesdec8(&mut blocks, keys[5]);
+    aesdec8(&mut blocks, keys[4]);
+    aesdec8(&mut blocks, keys[3]);
+    aesdec8(&mut blocks, keys[2]);
+    aesdec8(&mut blocks, keys[1]);
+    aesdeclast8(&mut blocks, keys[0]);
+    store8(out_ptr, blocks);
+}
diff --git a/aes/src/ni/aes256.rs b/aes/src/ni/aes256.rs
index a4170ad8..dddfbb62 100644
--- a/aes/src/ni/aes256.rs
+++ b/aes/src/ni/aes256.rs
@@ -1,15 +1,15 @@
 use super::{
     arch::*,
     utils::{
-        aesdec8, aesdeclast8, aesenc8, aesenclast8, load8, store8, xor8, Block128, Block128x8,
-        U128x8,
+        aesdec8, aesdeclast8, aesenc8, aesenclast8, load8, store8, xor8,
     },
 };
 use cipher::{
     consts::{U16, U32, U8},
     generic_array::GenericArray,
-    BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
+    BlockCipher, BlockProcessing, BlockDecrypt, BlockEncrypt, KeyInit, InOutVal, InOutBuf, InResOutBuf,
 };
+use crate::{Block, Block8};
 
 mod expand;
 #[cfg(test)]
@@ -25,58 +25,7 @@ pub struct Aes256 {
     decrypt_keys: RoundKeys,
 }
 
-impl Aes256 {
-    #[inline(always)]
-    pub(crate) fn encrypt8(&self, mut blocks: U128x8) -> U128x8 {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aesni256_encrypt8(keys: &RoundKeys, blocks: &mut U128x8) {
-            xor8(blocks, keys[0]);
-            aesenc8(blocks, keys[1]);
-            aesenc8(blocks, keys[2]);
-            aesenc8(blocks, keys[3]);
-            aesenc8(blocks, keys[4]);
-            aesenc8(blocks, keys[5]);
-            aesenc8(blocks, keys[6]);
-            aesenc8(blocks, keys[7]);
-            aesenc8(blocks, keys[8]);
-            aesenc8(blocks, keys[9]);
-            aesenc8(blocks, keys[10]);
-            aesenc8(blocks, keys[11]);
-            aesenc8(blocks, keys[12]);
-            aesenc8(blocks, keys[13]);
-            aesenclast8(blocks, keys[14]);
-        }
-        unsafe { aesni256_encrypt8(&self.encrypt_keys, &mut blocks) };
-        blocks
-    }
-
-    #[inline(always)]
-    pub(crate) fn encrypt(&self, block: __m128i) -> __m128i {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aesni256_encrypt1(keys: &RoundKeys, mut block: __m128i) -> __m128i {
-            block = _mm_xor_si128(block, keys[0]);
-            block = _mm_aesenc_si128(block, keys[1]);
-            block = _mm_aesenc_si128(block, keys[2]);
-            block = _mm_aesenc_si128(block, keys[3]);
-            block = _mm_aesenc_si128(block, keys[4]);
-            block = _mm_aesenc_si128(block, keys[5]);
-            block = _mm_aesenc_si128(block, keys[6]);
-            block = _mm_aesenc_si128(block, keys[7]);
-            block = _mm_aesenc_si128(block, keys[8]);
-            block = _mm_aesenc_si128(block, keys[9]);
-            block = _mm_aesenc_si128(block, keys[10]);
-            block = _mm_aesenc_si128(block, keys[11]);
-            block = _mm_aesenc_si128(block, keys[12]);
-            block = _mm_aesenc_si128(block, keys[13]);
-            _mm_aesenclast_si128(block, keys[14])
-        }
-        unsafe { aesni256_encrypt1(&self.encrypt_keys, block) }
-    }
-}
-
-impl NewBlockCipher for Aes256 {
+impl KeyInit for Aes256 {
     type KeySize = U32;
 
     #[inline]
@@ -90,90 +39,175 @@ impl NewBlockCipher for Aes256 {
     }
 }
 
-impl BlockCipher for Aes256 {
+impl BlockProcessing for Aes256 {
     type BlockSize = U16;
-    type ParBlocks = U8;
 }
 
+impl BlockCipher for Aes256 {}
+
 impl BlockEncrypt for Aes256 {
-    #[inline]
-    fn encrypt_block(&self, block: &mut Block128) {
-        // Safety: `loadu` and `storeu` support unaligned access
-        #[allow(clippy::cast_ptr_alignment)]
+    fn encrypt_block(&self, mut block: impl InOutVal<Block>) {
+        let in_ptr = block.get_in() as *const Block;
+        let out_ptr = block.get_out() as *mut Block;
         unsafe {
-            let b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
-            let b = self.encrypt(b);
-            _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
+            aes256_encrypt1(&self.encrypt_keys, in_ptr, out_ptr);
         }
     }
 
-    #[inline]
-    fn encrypt_par_blocks(&self, blocks: &mut Block128x8) {
-        let b = self.encrypt8(load8(blocks));
-        store8(blocks, b);
+    fn encrypt_blocks(
+        &self,
+        mut blocks: InOutBuf<'_, '_, Block>,
+        proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+    ) {
+        blocks.chunks::<U8, _, _, _, _>(
+            &self.encrypt_keys,
+            |keys, inc, res| unsafe {
+                aes256_encrypt8(
+                    keys,
+                    inc as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            |keys, inc, res| unsafe {
+                let n = inc.len();
+                res[..n].copy_from_slice(inc);
+                aes256_encrypt8(
+                    keys,
+                    res as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            proc,
+        );
     }
 }
 
 impl BlockDecrypt for Aes256 {
-    #[inline]
-    fn decrypt_block(&self, block: &mut Block128) {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aes256_decrypt1(block: &mut Block128, keys: &RoundKeys) {
-            // Safety: `loadu` and `storeu` support unaligned access
-            #[allow(clippy::cast_ptr_alignment)]
-            let mut b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
-
-            b = _mm_xor_si128(b, keys[14]);
-            b = _mm_aesdec_si128(b, keys[13]);
-            b = _mm_aesdec_si128(b, keys[12]);
-            b = _mm_aesdec_si128(b, keys[11]);
-            b = _mm_aesdec_si128(b, keys[10]);
-            b = _mm_aesdec_si128(b, keys[9]);
-            b = _mm_aesdec_si128(b, keys[8]);
-            b = _mm_aesdec_si128(b, keys[7]);
-            b = _mm_aesdec_si128(b, keys[6]);
-            b = _mm_aesdec_si128(b, keys[5]);
-            b = _mm_aesdec_si128(b, keys[4]);
-            b = _mm_aesdec_si128(b, keys[3]);
-            b = _mm_aesdec_si128(b, keys[2]);
-            b = _mm_aesdec_si128(b, keys[1]);
-            b = _mm_aesdeclast_si128(b, keys[0]);
-
-            // Safety: `loadu` and `storeu` support unaligned access
-            #[allow(clippy::cast_ptr_alignment)]
-            _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
+    fn decrypt_block(&self, mut block: impl InOutVal<Block>) {
+        let in_ptr = block.get_in() as *const Block;
+        let out_ptr = block.get_out() as *mut Block;
+        unsafe {
+            aes256_decrypt1(&self.decrypt_keys, in_ptr, out_ptr);
         }
-
-        unsafe { aes256_decrypt1(block, &self.decrypt_keys) }
     }
-
-    #[inline]
-    fn decrypt_par_blocks(&self, blocks: &mut Block128x8) {
-        #[inline]
-        #[target_feature(enable = "aes")]
-        unsafe fn aes256_decrypt8(blocks: &mut Block128x8, keys: &RoundKeys) {
-            let mut b = load8(blocks);
-            xor8(&mut b, keys[14]);
-            aesdec8(&mut b, keys[13]);
-            aesdec8(&mut b, keys[12]);
-            aesdec8(&mut b, keys[11]);
-            aesdec8(&mut b, keys[10]);
-            aesdec8(&mut b, keys[9]);
-            aesdec8(&mut b, keys[8]);
-            aesdec8(&mut b, keys[7]);
-            aesdec8(&mut b, keys[6]);
-            aesdec8(&mut b, keys[5]);
-            aesdec8(&mut b, keys[4]);
-            aesdec8(&mut b, keys[3]);
-            aesdec8(&mut b, keys[2]);
-            aesdec8(&mut b, keys[1]);
-            aesdeclast8(&mut b, keys[0]);
-            store8(blocks, b);
-        }
-
-        unsafe { aes256_decrypt8(blocks, &self.decrypt_keys) }
+    fn decrypt_blocks(
+        &self,
+        mut blocks: InOutBuf<'_, '_, Block>,
+        proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+    ) {
+        blocks.chunks::<U8, _, _, _, _>(
+            &self.decrypt_keys,
+            |keys, inc, res| unsafe {
+                aes256_decrypt8(
+                    keys,
+                    inc as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            |keys, inc, res| unsafe {
+                let n = inc.len();
+                res[..n].copy_from_slice(inc);
+                aes256_decrypt8(
+                    keys,
+                    res as *const Block8,
+                    res as *mut Block8,
+                )
+            },
+            proc,
+        );
     }
 }
 
 opaque_debug::implement!(Aes256);
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes256_encrypt1(keys: &RoundKeys, in_ptr: *const Block, out_ptr: *mut Block) {
+    // Safety: `loadu` and `storeu` support unaligned access
+    #[allow(clippy::cast_ptr_alignment)]
+    let mut block = _mm_loadu_si128(in_ptr as *const __m128i);
+    block = _mm_xor_si128(block, keys[0]);
+    block = _mm_aesenc_si128(block, keys[1]);
+    block = _mm_aesenc_si128(block, keys[2]);
+    block = _mm_aesenc_si128(block, keys[3]);
+    block = _mm_aesenc_si128(block, keys[4]);
+    block = _mm_aesenc_si128(block, keys[5]);
+    block = _mm_aesenc_si128(block, keys[6]);
+    block = _mm_aesenc_si128(block, keys[7]);
+    block = _mm_aesenc_si128(block, keys[8]);
+    block = _mm_aesenc_si128(block, keys[9]);
+    block = _mm_aesenc_si128(block, keys[10]);
+    block = _mm_aesenc_si128(block, keys[11]);
+    block = _mm_aesenc_si128(block, keys[12]);
+    block = _mm_aesenc_si128(block, keys[13]);
+    block = _mm_aesenclast_si128(block, keys[14]);
+    _mm_storeu_si128(out_ptr as *mut __m128i, block);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes256_encrypt8(keys: &RoundKeys, in_ptr: *const Block8, out_ptr: *mut Block8) {
+    let mut blocks = load8(in_ptr);
+    xor8(&mut blocks, keys[0]);
+    aesenc8(&mut blocks, keys[1]);
+    aesenc8(&mut blocks, keys[2]);
+    aesenc8(&mut blocks, keys[3]);
+    aesenc8(&mut blocks, keys[4]);
+    aesenc8(&mut blocks, keys[5]);
+    aesenc8(&mut blocks, keys[6]);
+    aesenc8(&mut blocks, keys[7]);
+    aesenc8(&mut blocks, keys[8]);
+    aesenc8(&mut blocks, keys[9]);
+    aesenc8(&mut blocks, keys[10]);
+    aesenc8(&mut blocks, keys[11]);
+    aesenc8(&mut blocks, keys[12]);
+    aesenc8(&mut blocks, keys[13]);
+    aesenclast8(&mut blocks, keys[14]);
+    store8(out_ptr, blocks);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+unsafe fn aes256_decrypt1(keys: &RoundKeys, in_ptr: *const Block, out_ptr: *mut Block) {
+    // Safety: `loadu` and `storeu` support unaligned access
+    #[allow(clippy::cast_ptr_alignment)]
+    let mut block = _mm_loadu_si128(in_ptr as *const __m128i);
+    block = _mm_xor_si128(block, keys[14]);
+    block = _mm_aesdec_si128(block, keys[13]);
+    block = _mm_aesdec_si128(block, keys[12]);
+    block = _mm_aesdec_si128(block, keys[11]);
+    block = _mm_aesdec_si128(block, keys[10]);
+    block = _mm_aesdec_si128(block, keys[9]);
+    block = _mm_aesdec_si128(block, keys[8]);
+    block = _mm_aesdec_si128(block, keys[7]);
+    block = _mm_aesdec_si128(block, keys[6]);
+    block = _mm_aesdec_si128(block, keys[5]);
+    block = _mm_aesdec_si128(block, keys[4]);
+    block = _mm_aesdec_si128(block, keys[3]);
+    block = _mm_aesdec_si128(block, keys[2]);
+    block = _mm_aesdec_si128(block, keys[1]);
+    block = _mm_aesdeclast_si128(block, keys[0]);
+    _mm_storeu_si128(out_ptr as *mut __m128i, block);
+}
+
+#[inline]
+#[target_feature(enable = "aes")]
+pub(crate) unsafe fn aes256_decrypt8(keys: &RoundKeys, in_ptr: *const Block8, out_ptr: *mut Block8) {
+    let mut blocks = load8(in_ptr);
+    xor8(&mut blocks, keys[14]);
+    aesdec8(&mut blocks, keys[13]);
+    aesdec8(&mut blocks, keys[12]);
+    aesdec8(&mut blocks, keys[11]);
+    aesdec8(&mut blocks, keys[10]);
+    aesdec8(&mut blocks, keys[9]);
+    aesdec8(&mut blocks, keys[8]);
+    aesdec8(&mut blocks, keys[7]);
+    aesdec8(&mut blocks, keys[6]);
+    aesdec8(&mut blocks, keys[5]);
+    aesdec8(&mut blocks, keys[4]);
+    aesdec8(&mut blocks, keys[3]);
+    aesdec8(&mut blocks, keys[2]);
+    aesdec8(&mut blocks, keys[1]);
+    aesdeclast8(&mut blocks, keys[0]);
+    store8(out_ptr, blocks);
+}
diff --git a/aes/src/ni/utils.rs b/aes/src/ni/utils.rs
index c24d98ce..b7b1eb6d 100644
--- a/aes/src/ni/utils.rs
+++ b/aes/src/ni/utils.rs
@@ -4,13 +4,8 @@
 #![allow(clippy::needless_range_loop)]
 
 use super::arch::*;
-use cipher::{
-    consts::{U16, U8},
-    generic_array::GenericArray,
-};
+use crate::{Block, Block8};
 
-pub type Block128 = GenericArray<u8, U16>;
-pub type Block128x8 = GenericArray<GenericArray<u8, U16>, U8>;
 pub type U128x8 = [__m128i; 8];
 
 #[cfg(test)]
@@ -23,32 +18,34 @@ pub(crate) fn check(a: &[__m128i], b: &[[u64; 2]]) {
 }
 
 #[inline(always)]
-pub(crate) fn load8(blocks: &Block128x8) -> U128x8 {
+pub(crate) fn load8(blocks: *const Block8) -> U128x8 {
     unsafe {
+        let p = blocks as *const Block;
         [
-            _mm_loadu_si128(blocks[0].as_ptr() as *const __m128i),
-            _mm_loadu_si128(blocks[1].as_ptr() as *const __m128i),
-            _mm_loadu_si128(blocks[2].as_ptr() as *const __m128i),
-            _mm_loadu_si128(blocks[3].as_ptr() as *const __m128i),
-            _mm_loadu_si128(blocks[4].as_ptr() as *const __m128i),
-            _mm_loadu_si128(blocks[5].as_ptr() as *const __m128i),
-            _mm_loadu_si128(blocks[6].as_ptr() as *const __m128i),
-            _mm_loadu_si128(blocks[7].as_ptr() as *const __m128i),
+            _mm_loadu_si128(p.add(0) as *const __m128i),
+            _mm_loadu_si128(p.add(1) as *const __m128i),
+            _mm_loadu_si128(p.add(2) as *const __m128i),
+            _mm_loadu_si128(p.add(3) as *const __m128i),
+            _mm_loadu_si128(p.add(4) as *const __m128i),
+            _mm_loadu_si128(p.add(5) as *const __m128i),
+            _mm_loadu_si128(p.add(6) as *const __m128i),
+            _mm_loadu_si128(p.add(7) as *const __m128i),
         ]
     }
 }
 
 #[inline(always)]
-pub(crate) fn store8(blocks: &mut Block128x8, b: U128x8) {
+pub(crate) fn store8(blocks: *mut Block8, b: U128x8) {
     unsafe {
-        _mm_storeu_si128(blocks[0].as_mut_ptr() as *mut __m128i, b[0]);
-        _mm_storeu_si128(blocks[1].as_mut_ptr() as *mut __m128i, b[1]);
-        _mm_storeu_si128(blocks[2].as_mut_ptr() as *mut __m128i, b[2]);
-        _mm_storeu_si128(blocks[3].as_mut_ptr() as *mut __m128i, b[3]);
-        _mm_storeu_si128(blocks[4].as_mut_ptr() as *mut __m128i, b[4]);
-        _mm_storeu_si128(blocks[5].as_mut_ptr() as *mut __m128i, b[5]);
-        _mm_storeu_si128(blocks[6].as_mut_ptr() as *mut __m128i, b[6]);
-        _mm_storeu_si128(blocks[7].as_mut_ptr() as *mut __m128i, b[7]);
+        let  p = blocks as *const Block;
+        _mm_storeu_si128(p.add(0) as *mut __m128i, b[0]);
+        _mm_storeu_si128(p.add(1) as *mut __m128i, b[1]);
+        _mm_storeu_si128(p.add(2) as *mut __m128i, b[2]);
+        _mm_storeu_si128(p.add(3) as *mut __m128i, b[3]);
+        _mm_storeu_si128(p.add(4) as *mut __m128i, b[4]);
+        _mm_storeu_si128(p.add(5) as *mut __m128i, b[5]);
+        _mm_storeu_si128(p.add(6) as *mut __m128i, b[6]);
+        _mm_storeu_si128(p.add(7) as *mut __m128i, b[7]);
     }
 }
 
diff --git a/aes/src/soft/fixslice64.rs b/aes/src/soft/fixslice64.rs
index 802081e4..6b11f892 100644
--- a/aes/src/soft/fixslice64.rs
+++ b/aes/src/soft/fixslice64.rs
@@ -15,15 +15,12 @@
 
 #![allow(clippy::unreadable_literal)]
 
-use crate::Block;
 use cipher::{
     consts::{U16, U24, U32},
     generic_array::GenericArray,
 };
 use core::convert::TryInto;
-
-/// AES block batch size for this implementation
-pub(crate) const FIXSLICE_BLOCKS: usize = 2;
+use crate::Block2;
 
 /// AES-128 round keys
 pub(crate) type FixsliceKeys128 = [u32; 88];
@@ -244,8 +241,7 @@ pub(crate) fn aes256_key_schedule(key: &GenericArray<u8, U32>) -> FixsliceKeys25
 /// Fully-fixsliced AES-128 decryption (the InvShiftRows is completely omitted).
 ///
 /// Decrypts four blocks in-place and in parallel.
-pub(crate) fn aes128_decrypt(rkeys: &FixsliceKeys128, blocks: &mut [Block]) {
-    debug_assert_eq!(blocks.len(), FIXSLICE_BLOCKS);
+pub(crate) fn aes128_decrypt(rkeys: &FixsliceKeys128, blocks: &Block2) -> Block2 {
     let mut state = State::default();
 
     bitslice(&mut state, &blocks[0], &blocks[1]);
@@ -295,14 +291,13 @@ pub(crate) fn aes128_decrypt(rkeys: &FixsliceKeys128, blocks: &mut [Block]) {
 
     add_round_key(&mut state, &rkeys[..8]);
 
-    inv_bitslice(&mut state, blocks);
+    inv_bitslice(&mut state)
 }
 
 /// Fully-fixsliced AES-128 encryption (the ShiftRows is completely omitted).
 ///
 /// Encrypts four blocks in-place and in parallel.
-pub(crate) fn aes128_encrypt(rkeys: &FixsliceKeys128, blocks: &mut [Block]) {
-    debug_assert_eq!(blocks.len(), FIXSLICE_BLOCKS);
+pub(crate) fn aes128_encrypt(rkeys: &FixsliceKeys128, blocks: &Block2) -> Block2 {
     let mut state = State::default();
 
     bitslice(&mut state, &blocks[0], &blocks[1]);
@@ -352,14 +347,13 @@ pub(crate) fn aes128_encrypt(rkeys: &FixsliceKeys128, blocks: &mut [Block]) {
     sub_bytes(&mut state);
     add_round_key(&mut state, &rkeys[80..]);
 
-    inv_bitslice(&mut state, blocks);
+    inv_bitslice(&mut state)
 }
 
 /// Fully-fixsliced AES-192 decryption (the InvShiftRows is completely omitted).
 ///
 /// Decrypts four blocks in-place and in parallel.
-pub(crate) fn aes192_decrypt(rkeys: &FixsliceKeys192, blocks: &mut [Block]) {
-    debug_assert_eq!(blocks.len(), FIXSLICE_BLOCKS);
+pub(crate) fn aes192_decrypt(rkeys: &FixsliceKeys192, blocks: &Block2) -> Block2 {
     let mut state = State::default();
 
     bitslice(&mut state, &blocks[0], &blocks[1]);
@@ -403,14 +397,13 @@ pub(crate) fn aes192_decrypt(rkeys: &FixsliceKeys192, blocks: &mut [Block]) {
 
     add_round_key(&mut state, &rkeys[..8]);
 
-    inv_bitslice(&mut state, blocks);
+    inv_bitslice(&mut state)
 }
 
 /// Fully-fixsliced AES-192 encryption (the ShiftRows is completely omitted).
 ///
 /// Encrypts four blocks in-place and in parallel.
-pub(crate) fn aes192_encrypt(rkeys: &FixsliceKeys192, blocks: &mut [Block]) {
-    debug_assert_eq!(blocks.len(), FIXSLICE_BLOCKS);
+pub(crate) fn aes192_encrypt(rkeys: &FixsliceKeys192, blocks: &Block2) -> Block2 {
     let mut state = State::default();
 
     bitslice(&mut state, &blocks[0], &blocks[1]);
@@ -454,14 +447,13 @@ pub(crate) fn aes192_encrypt(rkeys: &FixsliceKeys192, blocks: &mut [Block]) {
     sub_bytes(&mut state);
     add_round_key(&mut state, &rkeys[96..]);
 
-    inv_bitslice(&mut state, blocks);
+    inv_bitslice(&mut state)
 }
 
 /// Fully-fixsliced AES-256 decryption (the InvShiftRows is completely omitted).
 ///
 /// Decrypts four blocks in-place and in parallel.
-pub(crate) fn aes256_decrypt(rkeys: &FixsliceKeys256, blocks: &mut [Block]) {
-    debug_assert_eq!(blocks.len(), FIXSLICE_BLOCKS);
+pub(crate) fn aes256_decrypt(rkeys: &FixsliceKeys256, blocks: &Block2) -> Block2 {
     let mut state = State::default();
 
     bitslice(&mut state, &blocks[0], &blocks[1]);
@@ -511,14 +503,13 @@ pub(crate) fn aes256_decrypt(rkeys: &FixsliceKeys256, blocks: &mut [Block]) {
 
     add_round_key(&mut state, &rkeys[..8]);
 
-    inv_bitslice(&mut state, blocks);
+    inv_bitslice(&mut state)
 }
 
 /// Fully-fixsliced AES-256 encryption (the ShiftRows is completely omitted).
 ///
 /// Encrypts four blocks in-place and in parallel.
-pub(crate) fn aes256_encrypt(rkeys: &FixsliceKeys256, blocks: &mut [Block]) {
-    debug_assert_eq!(blocks.len(), FIXSLICE_BLOCKS);
+pub(crate) fn aes256_encrypt(rkeys: &FixsliceKeys256, blocks: &Block2) -> Block2 {
     let mut state = State::default();
 
     bitslice(&mut state, &blocks[0], &blocks[1]);
@@ -568,7 +559,7 @@ pub(crate) fn aes256_encrypt(rkeys: &FixsliceKeys256, blocks: &mut [Block]) {
     sub_bytes(&mut state);
     add_round_key(&mut state, &rkeys[112..]);
 
-    inv_bitslice(&mut state, blocks);
+    inv_bitslice(&mut state)
 }
 
 /// Note that the 4 bitwise NOT (^= 0xffffffff) are accounted for here so that it is a true
@@ -1234,9 +1225,8 @@ fn bitslice(output: &mut [u32], input0: &[u8], input1: &[u8]) {
 }
 
 /// Un-bitslice a 256-bit internal state into two 128-bit blocks of output.
-fn inv_bitslice(input: &mut [u32], output: &mut [Block]) {
+fn inv_bitslice(input: &mut [u32]) -> Block2 {
     debug_assert_eq!(input.len(), 8);
-    debug_assert_eq!(output.len(), 2);
 
     // Unbitslicing is a bit index manipulation. 256 bits of data means each bit is positioned at
     // an 8-bit index. AES data is 2 blocks, each one a 4x4 column-major matrix of bytes, so the
@@ -1283,6 +1273,7 @@ fn inv_bitslice(input: &mut [u32], output: &mut [Block]) {
 
     // De-interleave the columns on output (note the order of output)
     //     c1 c0 b0 __ __ __ __ __ => b0 c1 c0 __ __ __ __ __
+    let mut output = Block2::default();
     output[0][0x00..0x04].copy_from_slice(&t0.to_le_bytes());
     output[0][0x04..0x08].copy_from_slice(&t2.to_le_bytes());
     output[0][0x08..0x0c].copy_from_slice(&t4.to_le_bytes());
@@ -1294,6 +1285,7 @@ fn inv_bitslice(input: &mut [u32], output: &mut [Block]) {
 
     // Final AES bit index, as desired:
     //     b0 c1 c0 r1 r0 p2 p1 p0
+    output
 }
 
 /// Copy 32-bytes within the provided slice to an 8-byte offset
diff --git a/aes/src/soft/impls.rs b/aes/src/soft/impls.rs
index 5b76926c..69c97cba 100644
--- a/aes/src/soft/impls.rs
+++ b/aes/src/soft/impls.rs
@@ -1,13 +1,13 @@
 //! Macros for implementing `Aes*` structs and the `BlockCipher` interface
 
 use cipher::{
-    consts::{U16, U24, U32, U8},
+    consts::{U16, U24, U32, U2},
     generic_array::GenericArray,
-    BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
+    BlockProcessing, BlockCipher, BlockDecrypt, BlockEncrypt, KeyInit, InOutVal, InOutBuf, InResOutBuf,
 };
+use crate::{Block, Block2};
 
-use super::fixslice::{self, FixsliceKeys128, FixsliceKeys192, FixsliceKeys256, FIXSLICE_BLOCKS};
-use crate::{Block, ParBlocks};
+use super::fixslice::{self, FixsliceKeys128, FixsliceKeys192, FixsliceKeys256};
 
 macro_rules! define_aes_impl {
     (
@@ -25,7 +25,7 @@ macro_rules! define_aes_impl {
             keys: $fixslice_keys,
         }
 
-        impl NewBlockCipher for $name {
+        impl KeyInit for $name {
             type KeySize = $key_size;
 
             #[inline]
@@ -34,42 +34,66 @@ macro_rules! define_aes_impl {
             }
         }
 
-        impl BlockCipher for $name {
+        impl BlockProcessing for $name {
             type BlockSize = U16;
-            type ParBlocks = U8;
         }
 
+        impl BlockCipher for $name {}
+
         impl BlockEncrypt for $name {
-            #[inline]
-            fn encrypt_block(&self, block: &mut Block) {
-                let mut blocks = [Block::default(); FIXSLICE_BLOCKS];
-                blocks[0].copy_from_slice(block);
-                $fixslice_encrypt(&self.keys, &mut blocks);
-                block.copy_from_slice(&blocks[0]);
+            fn encrypt_block(&self, mut block: impl InOutVal<Block>) {
+                let mut t = Block2::default();
+                t[0] = *(block.get_in());
+                *(block.get_out()) = $fixslice_encrypt(&self.keys, &t)[0];
             }
 
-            #[inline]
-            fn encrypt_par_blocks(&self, blocks: &mut ParBlocks) {
-                for chunk in blocks.chunks_mut(FIXSLICE_BLOCKS) {
-                    $fixslice_encrypt(&self.keys, chunk);
-                }
+            fn encrypt_blocks(
+                &self,
+                mut blocks: InOutBuf<'_, '_, Block>,
+                proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+            ) {
+                blocks.chunks::<U2, _, _, _, _>(
+                    &self.keys,
+                    |keys, inc, res| *res = $fixslice_encrypt(keys, inc),
+                    |keys, inc, res| {
+                        debug_assert_eq!(inc.len(), 1);
+                        res[0] = inc[0];
+                        res[0] = $fixslice_encrypt(
+                            keys,
+                            &res
+                        )[0];
+                    },
+                    proc,
+                );
             }
         }
 
         impl BlockDecrypt for $name {
             #[inline]
-            fn decrypt_block(&self, block: &mut Block) {
-                let mut blocks = [Block::default(); FIXSLICE_BLOCKS];
-                blocks[0].copy_from_slice(block);
-                $fixslice_decrypt(&self.keys, &mut blocks);
-                block.copy_from_slice(&blocks[0]);
+            fn decrypt_block(&self, mut block: impl InOutVal<Block>) {
+                let mut t = Block2::default();
+                t[0] = *(block.get_in());
+                *(block.get_out()) = $fixslice_decrypt(&self.keys, &t)[0];
             }
 
-            #[inline]
-            fn decrypt_par_blocks(&self, blocks: &mut ParBlocks) {
-                for chunk in blocks.chunks_mut(FIXSLICE_BLOCKS) {
-                    $fixslice_decrypt(&self.keys, chunk);
-                }
+            fn decrypt_blocks(
+                &self,
+                mut blocks: InOutBuf<'_, '_, Block>,
+                proc: impl FnMut(InResOutBuf<'_, '_, '_, Block>),
+            ) {
+                blocks.chunks::<U2, _, _, _, _>(
+                    &self.keys,
+                    |keys, inc, res| *res = $fixslice_decrypt(keys, inc),
+                    |keys, inc, res| {
+                        debug_assert_eq!(inc.len(), 1);
+                        res[0] = inc[0];
+                        res[0] = $fixslice_decrypt(
+                            keys,
+                            &res
+                        )[0];
+                    },
+                    proc,
+                );
             }
         }
 
diff --git a/block-modes/Cargo.toml b/block-modes/Cargo.toml
index 90a604c8..aa251234 100644
--- a/block-modes/Cargo.toml
+++ b/block-modes/Cargo.toml
@@ -11,12 +11,11 @@ repository = "https://github.com/RustCrypto/block-ciphers"
 keywords = ["crypto", "block-cipher", "ciphers"]
 
 [dependencies]
-block-padding = "0.2"
-cipher = "=0.3.0-pre.4"
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
 
 [dev-dependencies]
 aes = { version = "=0.7.0-pre", path = "../aes", features = ["force-soft"] }
-hex-literal = "0.2"
+#hex-literal = "0.2"
 
 [features]
 default = ["std"]
diff --git a/block-modes/src/cbc.rs b/block-modes/src/cbc.rs
index c7455762..2336a005 100644
--- a/block-modes/src/cbc.rs
+++ b/block-modes/src/cbc.rs
@@ -1,94 +1,104 @@
-use crate::traits::{BlockMode, IvState};
-use crate::utils::{get_par_blocks, xor, Block, ParBlocks};
-use block_padding::Padding;
-use cipher::generic_array::{typenum::Unsigned, GenericArray};
-use cipher::{BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher};
-use core::marker::PhantomData;
+//! [Cipher Block Chaining][1] (CBC) mode.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC
+use crate::{xor, xor_ret};
+use cipher::{
+    generic_array::GenericArray, Block, BlockCipher, BlockDecryptMut, BlockEncryptMut,
+    BlockProcessing, InOutBuf, InOutVal, InResOutBuf, InnerIvInit, IvState,
+};
 
-/// [Cipher Block Chaining][1] (CBC) block cipher mode instance.
-///
-/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC
+/// CBC mode encryptor.
 #[derive(Clone)]
-pub struct Cbc<C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher, P: Padding> {
+pub struct Encrypt<C: BlockEncryptMut + BlockCipher> {
     cipher: C,
-    iv: GenericArray<u8, C::BlockSize>,
-    _p: PhantomData<P>,
+    iv: Block<C>,
 }
 
-impl<C, P> Cbc<C, P>
-where
-    C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher,
-    P: Padding,
-{
-    #[inline(always)]
-    fn single_blocks_decrypt(&mut self, blocks: &mut [Block<C>]) {
-        let mut iv = self.iv.clone();
-        for block in blocks {
-            let block_copy = block.clone();
-            self.cipher.decrypt_block(block);
-            xor(block, iv.as_slice());
-            iv = block_copy;
-        }
-        self.iv = iv;
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Encrypt<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let t = xor_ret(block.get_in(), &self.iv);
+        self.cipher.encrypt_block((&t, block.get_out()));
+        self.iv = block.get_out().clone();
     }
 }
 
-impl<C, P> BlockMode<C, P> for Cbc<C, P>
-where
-    C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher,
-    P: Padding,
-{
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Encrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Encrypt<C> {
+    type Inner = C;
     type IvSize = C::BlockSize;
 
-    fn new(cipher: C, iv: &Block<C>) -> Self {
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
         Self {
             cipher,
             iv: iv.clone(),
-            _p: Default::default(),
         }
     }
+}
 
-    fn encrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        self.iv = {
-            let mut iv = &self.iv;
-            for block in blocks {
-                xor(block, &iv);
-                self.cipher.encrypt_block(block);
-                iv = block;
-            }
-            iv.clone()
-        };
+impl<C: BlockEncryptMut + BlockCipher> IvState for Encrypt<C> {
+    #[inline]
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
     }
+}
+
+/// CBC mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C: BlockDecryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
 
-    fn decrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        let pbn = C::ParBlocks::to_usize();
-        if pbn != 1 {
-            let (par_blocks, leftover) = get_par_blocks::<C>(blocks);
-            let mut iv_buf = ParBlocks::<C>::default();
-            iv_buf[0] = self.iv.clone();
-            for pb in par_blocks {
-                iv_buf[1..].clone_from_slice(&pb[..pbn - 1]);
-                let next_iv = pb[pbn - 1].clone();
-                self.cipher.decrypt_blocks(pb);
-                pb.iter_mut()
-                    .zip(iv_buf.iter())
-                    .for_each(|(a, b)| xor(a, b));
-                iv_buf[0] = next_iv;
+impl<C: BlockDecryptMut + BlockCipher> BlockDecryptMut for Decrypt<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let enc_block = block.get_in().clone();
+        self.cipher.decrypt_block((&enc_block, block.get_out()));
+        xor(block.get_out(), &self.iv);
+        self.iv = enc_block;
+    }
+
+    fn decrypt_blocks(
+        &mut self,
+        blocks: InOutBuf<'_, '_, Block<Self>>,
+        mut proc: impl FnMut(InResOutBuf<'_, '_, '_, Block<Self>>),
+    ) {
+        let iv = &mut self.iv;
+        self.cipher.decrypt_blocks(blocks, |mut buf| {
+            let len = buf.len();
+            let (in_buf, res_buf) = buf.get_in_res();
+            xor(&mut res_buf[0], iv);
+            for i in 1..len {
+                xor(&mut res_buf[i], &in_buf[i - 1]);
             }
-            self.iv = iv_buf[0].clone();
-            self.single_blocks_decrypt(leftover);
-        } else {
-            self.single_blocks_decrypt(blocks);
+            *iv = in_buf[len - 1].clone();
+            proc(buf);
+        });
+    }
+}
+
+impl<C: BlockDecryptMut + BlockCipher> BlockProcessing for Decrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockDecryptMut + BlockCipher> InnerIvInit for Decrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
         }
     }
 }
 
-impl<C, P> IvState<C, P> for Cbc<C, P>
-where
-    C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher,
-    P: Padding,
-{
-    fn iv_state(&self) -> GenericArray<u8, <Self as BlockMode<C, P>>::IvSize> {
+impl<C: BlockDecryptMut + BlockCipher> IvState for Decrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
         self.iv.clone()
     }
 }
diff --git a/block-modes/src/cfb.rs b/block-modes/src/cfb.rs
index b51b213a..15350014 100644
--- a/block-modes/src/cfb.rs
+++ b/block-modes/src/cfb.rs
@@ -1,124 +1,110 @@
-use crate::{
-    traits::{BlockMode, IvState},
-    utils::{xor, Block, ParBlocks},
-};
-use block_padding::Padding;
+//! [Cipher feedback][1] (CFB) mode with full block feedback.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)
+use crate::xor;
 use cipher::{
-    generic_array::{typenum::Unsigned, GenericArray},
-    BlockCipher, BlockEncrypt, NewBlockCipher,
+    generic_array::GenericArray, AsyncStreamCipher, Block, BlockCipher, BlockDecryptMut,
+    BlockEncryptMut, BlockProcessing, InOutBuf, InOutVal, InResOutBuf, InnerIvInit, IvState,
 };
-use core::{marker::PhantomData, ptr};
 
-/// [Cipher feedback][1] (CFB) block mode instance with a full block feedback.
-///
-/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)
+/// CFB mode encryptor.
 #[derive(Clone)]
-pub struct Cfb<C: BlockCipher + BlockEncrypt + NewBlockCipher, P: Padding> {
+pub struct Encrypt<C: BlockEncryptMut + BlockCipher> {
     cipher: C,
-    iv: GenericArray<u8, C::BlockSize>,
-    _p: PhantomData<P>,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Encrypt<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        self.cipher.encrypt_block(&mut self.iv);
+        xor(&mut self.iv, block.get_in());
+        *block.get_out() = self.iv.clone();
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Encrypt<C> {
+    type BlockSize = C::BlockSize;
 }
 
-impl<C, P> BlockMode<C, P> for Cfb<C, P>
-where
-    C: BlockCipher + BlockEncrypt + NewBlockCipher,
-    P: Padding,
-{
+impl<C: BlockEncryptMut + BlockCipher> AsyncStreamCipher for Encrypt<C> {}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Encrypt<C> {
+    type Inner = C;
     type IvSize = C::BlockSize;
 
-    fn new(cipher: C, iv: &Block<C>) -> Self {
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
         Self {
             cipher,
             iv: iv.clone(),
-            _p: Default::default(),
         }
     }
+}
 
-    fn encrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        for block in blocks {
-            self.cipher.encrypt_block(&mut self.iv);
-            xor_set1(block, self.iv.as_mut_slice());
-        }
+impl<C: BlockEncryptMut + BlockCipher> IvState for Encrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
     }
+}
 
-    fn decrypt_blocks(&mut self, mut blocks: &mut [Block<C>]) {
-        let pb = C::ParBlocks::to_usize();
-
-        if blocks.len() > pb {
-            // SAFETY: we have checked that `blocks` has enough elements
-            #[allow(unsafe_code)]
-            let mut par_iv = read_par_block::<C>(&blocks[..pb]);
-
-            let (b, r) = { blocks }.split_at_mut(1);
-            blocks = r;
-            self.cipher.encrypt_block(&mut self.iv);
-            xor(&mut b[0], &self.iv);
-
-            // Remember IV for trailing blocks
-            self.iv = blocks[blocks.len() - (blocks.len() % pb) - 1].clone();
-
-            while blocks.len() >= 2 * pb {
-                let next_par_iv = read_par_block::<C>(&blocks[pb - 1..2 * pb - 1]);
-                self.cipher.encrypt_blocks(&mut par_iv);
-                let (par_block, r) = { blocks }.split_at_mut(pb);
-                blocks = r;
-
-                for (a, b) in par_block.iter_mut().zip(par_iv.iter()) {
-                    xor(a, b)
-                }
-                par_iv = next_par_iv;
-            }
+/// CFB mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
 
-            self.cipher.encrypt_blocks(&mut par_iv);
-            let (par_block, r) = { blocks }.split_at_mut(pb);
-            blocks = r;
+impl<C: BlockEncryptMut + BlockCipher> BlockDecryptMut for Decrypt<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = self.iv.clone();
+        self.cipher.encrypt_block(&mut t);
+        xor(&mut t, block.get_in());
+        self.iv = block.get_in().clone();
+        *block.get_out() = t;
+    }
 
-            for (a, b) in par_block.iter_mut().zip(par_iv[..pb].iter()) {
-                xor(a, b)
+    fn decrypt_blocks(
+        &mut self,
+        blocks: InOutBuf<'_, '_, Block<Self>>,
+        mut proc: impl FnMut(InResOutBuf<'_, '_, '_, Block<Self>>),
+    ) {
+        let mut enc_iv = self.iv.clone();
+        self.cipher.encrypt_block(&mut enc_iv);
+        let iv = &mut self.iv;
+        self.cipher.encrypt_blocks(blocks, |mut buf| {
+            let len = buf.len();
+            let (in_buf, res_buf) = buf.get_in_res();
+            for i in 0..len {
+                xor(&mut enc_iv, &in_buf[i]);
+                core::mem::swap(&mut res_buf[i], &mut enc_iv);
             }
-        }
-
-        for block in blocks {
-            self.cipher.encrypt_block(&mut self.iv);
-            xor_set2(block, self.iv.as_mut_slice());
-        }
+            *iv = in_buf[len - 1].clone();
+            proc(buf);
+        });
     }
 }
 
-impl<C, P> IvState<C, P> for Cfb<C, P>
-where
-    C: BlockCipher + BlockEncrypt + NewBlockCipher,
-    P: Padding,
-{
-    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
-        self.iv.clone()
-    }
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Decrypt<C> {
+    type BlockSize = C::BlockSize;
 }
 
-#[inline(always)]
-fn read_par_block<C: BlockCipher>(blocks: &[Block<C>]) -> ParBlocks<C> {
-    assert!(blocks.len() >= C::ParBlocks::to_usize());
-    // SAFETY: assert checks that `blocks` is long enough
-    #[allow(unsafe_code)]
-    unsafe {
-        ptr::read(blocks.as_ptr() as *const ParBlocks<C>)
-    }
-}
+impl<C: BlockEncryptMut + BlockCipher> AsyncStreamCipher for Decrypt<C> {}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Decrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
 
-#[inline(always)]
-fn xor_set1(buf1: &mut [u8], buf2: &mut [u8]) {
-    for (a, b) in buf1.iter_mut().zip(buf2) {
-        let t = *a ^ *b;
-        *a = t;
-        *b = t;
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
     }
 }
 
-#[inline(always)]
-fn xor_set2(buf1: &mut [u8], buf2: &mut [u8]) {
-    for (a, b) in buf1.iter_mut().zip(buf2) {
-        let t = *a;
-        *a ^= *b;
-        *b = t;
+impl<C: BlockEncryptMut + BlockCipher> IvState for Decrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
     }
 }
diff --git a/block-modes/src/cfb8.rs b/block-modes/src/cfb8.rs
index d0e26c17..2240e7dc 100644
--- a/block-modes/src/cfb8.rs
+++ b/block-modes/src/cfb8.rs
@@ -1,74 +1,86 @@
-use crate::{
-    traits::{BlockMode, IvState},
-    utils::Block,
+//! [Cipher feedback][1] (CFB) mode with eight bit feedback.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)
+use cipher::{
+    generic_array::{typenum::U1, GenericArray},
+    AsyncStreamCipher, Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing,
+    InOutVal, InnerIvInit,
 };
-use block_padding::Padding;
-use cipher::{generic_array::GenericArray, BlockCipher, BlockEncrypt, NewBlockCipher};
-use core::marker::PhantomData;
 
-/// [Cipher feedback][1] (CFB) block mode instance with a full block feedback.
-///
-/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)
+/// CFB-8 mode encryptor.
 #[derive(Clone)]
-pub struct Cfb8<C: BlockCipher + BlockEncrypt + NewBlockCipher, P: Padding> {
+pub struct Encrypt<C: BlockEncryptMut + BlockCipher> {
     cipher: C,
-    iv: GenericArray<u8, C::BlockSize>,
-    _p: PhantomData<P>,
+    iv: Block<C>,
 }
 
-impl<C, P> BlockMode<C, P> for Cfb8<C, P>
-where
-    C: BlockCipher + BlockEncrypt + NewBlockCipher,
-    P: Padding,
-{
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Encrypt<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = self.iv.clone();
+        self.cipher.encrypt_block(&mut t);
+        let r = block.get_in()[0] ^ t[0];
+        block.get_out()[0] = r;
+        let n = self.iv.len();
+        for i in 0..n - 1 {
+            self.iv[i] = self.iv[i + 1];
+        }
+        self.iv[n - 1] = r;
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Encrypt<C> {
+    type BlockSize = U1;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> AsyncStreamCipher for Encrypt<C> {}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Encrypt<C> {
+    type Inner = C;
     type IvSize = C::BlockSize;
 
-    fn new(cipher: C, iv: &Block<C>) -> Self {
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
         Self {
             cipher,
             iv: iv.clone(),
-            _p: Default::default(),
         }
     }
+}
 
-    fn encrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        let mut iv = self.iv.clone();
-        let n = iv.len();
-        for block in blocks.iter_mut() {
-            for b in block.iter_mut() {
-                let iv_copy = iv.clone();
-                self.cipher.encrypt_block(&mut iv);
-                *b ^= iv[0];
-                iv[..n - 1].clone_from_slice(&iv_copy[1..]);
-                iv[n - 1] = *b;
-            }
-        }
-        self.iv = iv;
-    }
+/// CFB-8 mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
 
-    fn decrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        let mut iv = self.iv.clone();
-        let n = iv.len();
-        for block in blocks.iter_mut() {
-            for b in block.iter_mut() {
-                let iv_copy = iv.clone();
-                self.cipher.encrypt_block(&mut iv);
-                let t = *b;
-                *b ^= iv[0];
-                iv[..n - 1].clone_from_slice(&iv_copy[1..]);
-                iv[n - 1] = t;
-            }
+impl<C: BlockEncryptMut + BlockCipher> BlockDecryptMut for Decrypt<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = self.iv.clone();
+        self.cipher.encrypt_block(&mut t);
+        let r = block.get_in()[0];
+        block.get_out()[0] = r ^ t[0];
+        let n = self.iv.len();
+        for i in 0..n - 1 {
+            self.iv[i] = self.iv[i + 1];
         }
-        self.iv = iv;
+        self.iv[n - 1] = r;
     }
 }
 
-impl<C, P> IvState<C, P> for Cfb8<C, P>
-where
-    C: BlockCipher + BlockEncrypt + NewBlockCipher,
-    P: Padding,
-{
-    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
-        self.iv.clone()
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Decrypt<C> {
+    type BlockSize = U1;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> AsyncStreamCipher for Decrypt<C> {}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Decrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
     }
 }
diff --git a/block-modes/src/ecb.rs b/block-modes/src/ecb.rs
deleted file mode 100644
index 39d9bd3f..00000000
--- a/block-modes/src/ecb.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use crate::{
-    errors::InvalidKeyIvLength,
-    traits::BlockMode,
-    utils::{get_par_blocks, Block},
-};
-use block_padding::Padding;
-use cipher::{
-    generic_array::{
-        typenum::{Unsigned, U0},
-        GenericArray,
-    },
-    BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
-};
-use core::marker::PhantomData;
-
-/// [Electronic Codebook][1] (ECB) block cipher mode instance.
-///
-/// Note that `new` method ignores IV, so during initialization you can
-/// just pass `Default::default()` instead.
-///
-/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#ECB
-#[derive(Clone)]
-pub struct Ecb<C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher, P: Padding> {
-    cipher: C,
-    _p: PhantomData<P>,
-}
-
-impl<C, P> BlockMode<C, P> for Ecb<C, P>
-where
-    C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher,
-    P: Padding,
-{
-    type IvSize = U0;
-
-    fn new(cipher: C, _iv: &GenericArray<u8, U0>) -> Self {
-        Self {
-            cipher,
-            _p: Default::default(),
-        }
-    }
-
-    fn new_from_slices(key: &[u8], _iv: &[u8]) -> Result<Self, InvalidKeyIvLength> {
-        let cipher = C::new_from_slice(key).map_err(|_| InvalidKeyIvLength)?;
-        Ok(Self {
-            cipher,
-            _p: Default::default(),
-        })
-    }
-
-    fn encrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        if C::ParBlocks::to_usize() != 1 {
-            let (par_blocks, blocks) = get_par_blocks::<C>(blocks);
-            par_blocks
-                .iter_mut()
-                .for_each(|pb| self.cipher.encrypt_blocks(pb));
-            blocks
-                .iter_mut()
-                .for_each(|pb| self.cipher.encrypt_block(pb));
-        } else {
-            blocks
-                .iter_mut()
-                .for_each(|pb| self.cipher.encrypt_block(pb));
-        }
-    }
-
-    fn decrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        if C::ParBlocks::to_usize() != 1 {
-            let (par_blocks, blocks) = get_par_blocks::<C>(blocks);
-            par_blocks
-                .iter_mut()
-                .for_each(|pb| self.cipher.decrypt_blocks(pb));
-            blocks
-                .iter_mut()
-                .for_each(|pb| self.cipher.decrypt_block(pb));
-        } else {
-            blocks
-                .iter_mut()
-                .for_each(|pb| self.cipher.decrypt_block(pb));
-        }
-    }
-}
diff --git a/block-modes/src/errors.rs b/block-modes/src/errors.rs
deleted file mode 100644
index 476beddc..00000000
--- a/block-modes/src/errors.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use core::fmt;
-#[cfg(feature = "std")]
-use std::error;
-
-/// Block mode error.
-#[derive(Clone, Copy, Debug)]
-pub struct BlockModeError;
-
-/// Invalid key or IV length error.
-#[derive(Clone, Copy, Debug)]
-pub struct InvalidKeyIvLength;
-
-impl fmt::Display for BlockModeError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-        f.write_str("BlockModeError")
-    }
-}
-
-#[cfg(feature = "std")]
-impl error::Error for BlockModeError {
-    fn description(&self) -> &str {
-        "block mode error"
-    }
-}
-
-impl fmt::Display for InvalidKeyIvLength {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-        f.write_str("InvalidKeyIvLength")
-    }
-}
-
-#[cfg(feature = "std")]
-impl error::Error for InvalidKeyIvLength {}
diff --git a/block-modes/src/ige.rs b/block-modes/src/ige.rs
index f87c3c39..17031fdb 100644
--- a/block-modes/src/ige.rs
+++ b/block-modes/src/ige.rs
@@ -1,84 +1,156 @@
-use crate::{
-    traits::{BlockMode, IvState},
-    utils::{xor, Block},
-};
-use block_padding::Padding;
+//! [Infinite Garble Extension][1] (IGE) block cipher mode of operation.
+//!
+//! [1]: https://www.links.org/files/openssl-ige.pdf
+use crate::{xor, xor_ret};
+use core::ops::Add;
 use cipher::{
     generic_array::{
         sequence::Concat,
         typenum::{Sum, Unsigned},
         ArrayLength, GenericArray,
     },
-    BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
+    Block, BlockCipher, BlockDecryptMut, BlockEncryptMut,
+    BlockProcessing, InOutVal, InnerIvInit, IvState,
 };
-use core::{marker::PhantomData, ops::Add};
 
-type IgeIvBlockSize<C> = Sum<<C as BlockCipher>::BlockSize, <C as BlockCipher>::BlockSize>;
+type BlockSize<C> = <C as BlockProcessing>::BlockSize;
+type IgeIvSize<C> = Sum<BlockSize<C>, BlockSize<C>>;
 
-/// [Infinite Garble Extension][1] (IGE) block cipher mode instance.
-///
-/// [1]: https://www.links.org/files/openssl-ige.pdf
-pub struct Ige<C, P>
+/// IGE mode encryptor.
+#[derive(Clone)]
+pub struct Encrypt<C>
 where
-    C: BlockCipher + NewBlockCipher + BlockEncrypt + BlockDecrypt,
-    P: Padding,
+    C: BlockEncryptMut + BlockCipher,
     C::BlockSize: Add,
-    IgeIvBlockSize<C>: ArrayLength<u8>,
+    IgeIvSize<C>: ArrayLength<u8>,
 {
     cipher: C,
-    x: GenericArray<u8, C::BlockSize>,
-    y: GenericArray<u8, C::BlockSize>,
-    _p: PhantomData<P>,
+    x: Block<C>,
+    y: Block<C>,
+}
+
+impl<C> BlockEncryptMut for Encrypt<C>
+where
+    C: BlockEncryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let new_x = block.get_in().clone();
+        let mut t = xor_ret(block.get_in(), &self.y);
+        self.cipher.encrypt_block(&mut t);
+        xor(&mut t, &self.x);
+        *block.get_out() = t.clone();
+        self.x = new_x;
+        self.y = t;
+    }
+}
+
+impl<C> BlockProcessing for Encrypt<C>
+where
+    C: BlockEncryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    type BlockSize = C::BlockSize;
 }
 
-impl<C, P> BlockMode<C, P> for Ige<C, P>
+impl<C> InnerIvInit for Encrypt<C>
 where
-    C: BlockCipher + NewBlockCipher + BlockEncrypt + BlockDecrypt,
-    P: Padding,
+    C: BlockEncryptMut + BlockCipher,
     C::BlockSize: Add,
-    IgeIvBlockSize<C>: ArrayLength<u8>,
+    IgeIvSize<C>: ArrayLength<u8>,
 {
-    type IvSize = IgeIvBlockSize<C>;
+    type Inner = C;
+    type IvSize = IgeIvSize<C>;
 
-    fn new(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
         let (y, x) = iv.split_at(C::BlockSize::to_usize());
-        Ige {
+        Self {
             cipher,
             x: GenericArray::clone_from_slice(x),
             y: GenericArray::clone_from_slice(y),
-            _p: Default::default(),
         }
     }
+}
 
-    fn encrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        for block in blocks {
-            let t = block.clone();
-            xor(block, &self.y);
-            self.cipher.encrypt_block(block);
-            xor(block, &self.x);
-            self.x = t;
-            self.y = block.clone();
-        }
+impl<C> IvState for Encrypt<C>
+where
+    C: BlockEncryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    #[inline]
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.y.clone().concat(self.x.clone())
     }
+}
 
-    fn decrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        for block in blocks {
-            let t = block.clone();
-            xor(block, &self.x);
-            self.cipher.decrypt_block(block);
-            xor(block, &self.y);
-            self.y = t;
-            self.x = block.clone();
+/// IGE mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    cipher: C,
+    x: Block<C>,
+    y: Block<C>,
+}
+
+impl<C> BlockDecryptMut for Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let new_y = block.get_in().clone();
+        let mut t = xor_ret(block.get_in(), &self.x);
+        self.cipher.decrypt_block(&mut t);
+        xor(&mut t, &self.y);
+        *block.get_out() = t.clone();
+        self.y = new_y;
+        self.x = t;
+    }
+}
+
+impl<C> BlockProcessing for Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    type BlockSize = C::BlockSize;
+}
+
+impl<C> InnerIvInit for Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    type Inner = C;
+    type IvSize = IgeIvSize<C>;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        let (y, x) = iv.split_at(C::BlockSize::to_usize());
+        Self {
+            cipher,
+            x: GenericArray::clone_from_slice(x),
+            y: GenericArray::clone_from_slice(y),
         }
     }
 }
 
-impl<C, P> IvState<C, P> for Ige<C, P>
+impl<C> IvState for Decrypt<C>
 where
-    C: BlockCipher + NewBlockCipher + BlockEncrypt + BlockDecrypt,
-    P: Padding,
+    C: BlockDecryptMut + BlockCipher,
     C::BlockSize: Add,
-    IgeIvBlockSize<C>: ArrayLength<u8>,
+    IgeIvSize<C>: ArrayLength<u8>,
 {
     fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
         self.y.clone().concat(self.x.clone())
diff --git a/block-modes/src/lib.rs b/block-modes/src/lib.rs
index 220e9f99..e2c6d289 100644
--- a/block-modes/src/lib.rs
+++ b/block-modes/src/lib.rs
@@ -78,34 +78,37 @@
 #![deny(unsafe_code)]
 #![warn(missing_docs, rust_2018_idioms)]
 
-#[cfg(feature = "alloc")]
-extern crate alloc;
-#[cfg(feature = "std")]
-extern crate std;
+pub use cipher;
 
-mod errors;
-mod traits;
 mod utils;
 
-mod cbc;
-mod cfb;
-mod cfb8;
-mod ecb;
-mod ige;
+pub mod cbc;
+pub mod cfb;
+pub mod cfb8;
+pub mod pcbc;
+pub mod ige;
 mod ofb;
-mod pcbc;
 
-pub use block_padding;
-pub use cipher;
+pub use ofb::Ofb;
+
+
+use cipher::generic_array::{ArrayLength, GenericArray};
+
+#[inline(always)]
+fn xor<N: ArrayLength<u8>>(out: &mut GenericArray<u8, N>, buf: &GenericArray<u8, N>) {
+    for (a, b) in out.iter_mut().zip(buf) {
+        *a ^= *b;
+    }
+}
 
-pub use crate::{
-    cbc::Cbc,
-    cfb::Cfb,
-    cfb8::Cfb8,
-    ecb::Ecb,
-    errors::{BlockModeError, InvalidKeyIvLength},
-    ige::Ige,
-    ofb::Ofb,
-    pcbc::Pcbc,
-    traits::{BlockMode, IvState},
-};
+#[inline(always)]
+fn xor_ret<N: ArrayLength<u8>>(
+    buf1: &GenericArray<u8, N>,
+    buf2: &GenericArray<u8, N>,
+) -> GenericArray<u8, N> {
+    let mut res = GenericArray::<u8, N>::default();
+    for i in 0..N::USIZE {
+        res[i] = buf1[i] ^ buf2[i];
+    }
+    res
+}
diff --git a/block-modes/src/ofb.rs b/block-modes/src/ofb.rs
index edade926..84f820fb 100644
--- a/block-modes/src/ofb.rs
+++ b/block-modes/src/ofb.rs
@@ -1,54 +1,62 @@
-use crate::{
-    traits::{BlockMode, IvState},
-    utils::{xor, Block},
+/// [Output feedback][1] (OFB) mode.
+///
+/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB)
+use cipher::{
+    generic_array::GenericArray, Block, BlockCipher, StreamCipherCore, BlockEncryptMut,
+    BlockProcessing, InnerIvInit, IvState, InOutVal, BlockDecryptMut, errors::LoopError,
 };
-use block_padding::Padding;
-use cipher::{generic_array::GenericArray, BlockCipher, BlockEncrypt, NewBlockCipher};
-use core::marker::PhantomData;
+use crate::xor_ret;
 
-/// [Output feedback][1] (OFB) block mode instance with a full block feedback.
+/// [Output feedback][1] (OFB) mode.
 ///
-/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)
+/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB)
 #[derive(Clone)]
-pub struct Ofb<C: BlockCipher + BlockEncrypt + NewBlockCipher, P: Padding> {
+pub struct Ofb<C: BlockEncryptMut + BlockCipher> {
     cipher: C,
-    iv: GenericArray<u8, C::BlockSize>,
-    _p: PhantomData<P>,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> StreamCipherCore for Ofb<C> {
+    fn gen_keystream_block(&mut self) -> Result<Block<Self>, LoopError> {
+        self.cipher.encrypt_block(&mut self.iv);
+        Ok(self.iv.clone())
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Ofb<C> {
+    type BlockSize = C::BlockSize;
 }
 
-impl<C, P> BlockMode<C, P> for Ofb<C, P>
-where
-    C: BlockCipher + BlockEncrypt + NewBlockCipher,
-    P: Padding,
-{
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Ofb<C> {
+    type Inner = C;
     type IvSize = C::BlockSize;
 
-    fn new(cipher: C, iv: &Block<C>) -> Self {
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
         Self {
             cipher,
             iv: iv.clone(),
-            _p: Default::default(),
         }
     }
+}
 
-    fn encrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        for block in blocks.iter_mut() {
-            self.cipher.encrypt_block(&mut self.iv);
-            xor(block, &self.iv);
-        }
+impl<C: BlockEncryptMut + BlockCipher> IvState for Ofb<C> {
+    #[inline]
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
     }
+}
 
-    fn decrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        self.encrypt_blocks(blocks)
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Ofb<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        self.cipher.encrypt_block(&mut self.iv);
+        *block.get_out() = xor_ret(&self.iv, block.get_in());
     }
 }
 
-impl<C, P> IvState<C, P> for Ofb<C, P>
-where
-    C: BlockCipher + BlockEncrypt + NewBlockCipher,
-    P: Padding,
-{
-    fn iv_state(&self) -> GenericArray<u8, <Self as BlockMode<C, P>>::IvSize> {
-        self.iv.clone()
+impl<C: BlockEncryptMut + BlockCipher> BlockDecryptMut for Ofb<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        self.cipher.encrypt_block(&mut self.iv);
+        *block.get_out() = xor_ret(&self.iv, block.get_in());
     }
 }
diff --git a/block-modes/src/pcbc.rs b/block-modes/src/pcbc.rs
index e9ecaf2d..a38dc77c 100644
--- a/block-modes/src/pcbc.rs
+++ b/block-modes/src/pcbc.rs
@@ -1,79 +1,88 @@
-use crate::{
-    traits::{BlockMode, IvState},
-    utils::{xor, Block},
-};
-use block_padding::Padding;
+//! [Propagating cipher block chaining][1] (PCBC) mode.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Propagating_cipher_block_chaining_(PCBC)
+use crate::{xor, xor_ret};
 use cipher::{
-    generic_array::GenericArray, BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
+    generic_array::GenericArray, Block, BlockCipher, BlockDecryptMut, BlockEncryptMut,
+    BlockProcessing, InOutVal, InnerIvInit, IvState,
 };
-use core::marker::PhantomData;
 
-/// [Propagating Cipher Block Chaining][1] (PCBC) mode instance.
-///
-/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#PCBC
+/// PCBC mode encryptor.
 #[derive(Clone)]
-pub struct Pcbc<C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher, P: Padding> {
+pub struct Encrypt<C: BlockEncryptMut + BlockCipher> {
     cipher: C,
-    iv: GenericArray<u8, C::BlockSize>,
-    _p: PhantomData<P>,
+    iv: Block<C>,
 }
 
-impl<C, P> Pcbc<C, P>
-where
-    C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher,
-    P: Padding,
-{
-    /// Initialize PCBC
-    pub fn new(cipher: C, iv: &Block<C>) -> Self {
-        Self {
-            cipher,
-            iv: iv.clone(),
-            _p: Default::default(),
-        }
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Encrypt<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = self.iv.clone();
+        xor(&mut t, block.get_in());
+        self.cipher.encrypt_block((&t, block.get_out()));
+        self.iv = xor_ret(&t, block.get_out());
     }
 }
 
-impl<C, P> BlockMode<C, P> for Pcbc<C, P>
-where
-    C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher,
-    P: Padding,
-{
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Encrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Encrypt<C> {
+    type Inner = C;
     type IvSize = C::BlockSize;
 
-    fn new(cipher: C, iv: &GenericArray<u8, C::BlockSize>) -> Self {
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
         Self {
             cipher,
             iv: iv.clone(),
-            _p: Default::default(),
         }
     }
+}
 
-    fn encrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        for block in blocks {
-            let plaintext = block.clone();
-            xor(block, &self.iv);
-            self.cipher.encrypt_block(block);
-            self.iv = plaintext;
-            xor(&mut self.iv, block);
-        }
+impl<C: BlockEncryptMut + BlockCipher> IvState for Encrypt<C> {
+    #[inline]
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+/// PCBC mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C: BlockDecryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockDecryptMut + BlockCipher> BlockDecryptMut for Decrypt<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = Default::default();
+        self.cipher.decrypt_block((block.get_in(), &mut t));
+        xor(&mut t, &self.iv);
+        self.iv.copy_from_slice(block.get_in());
+        block.get_out().copy_from_slice(&t);
+        xor(&mut self.iv, &t);
     }
+}
 
-    fn decrypt_blocks(&mut self, blocks: &mut [Block<C>]) {
-        for block in blocks {
-            let ciphertext = block.clone();
-            self.cipher.decrypt_block(block);
-            xor(block, &self.iv);
-            self.iv = ciphertext;
-            xor(&mut self.iv, block);
+impl<C: BlockDecryptMut + BlockCipher> BlockProcessing for Decrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockDecryptMut + BlockCipher> InnerIvInit for Decrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
         }
     }
 }
 
-impl<C, P> IvState<C, P> for Pcbc<C, P>
-where
-    C: BlockCipher + BlockEncrypt + BlockDecrypt + NewBlockCipher,
-    P: Padding,
-{
+impl<C: BlockDecryptMut + BlockCipher> IvState for Decrypt<C> {
     fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
         self.iv.clone()
     }
diff --git a/block-modes/src/traits.rs b/block-modes/src/traits.rs
deleted file mode 100644
index 58d44c92..00000000
--- a/block-modes/src/traits.rs
+++ /dev/null
@@ -1,126 +0,0 @@
-#[cfg(feature = "alloc")]
-pub use alloc::vec::Vec;
-
-use crate::{
-    errors::{BlockModeError, InvalidKeyIvLength},
-    utils::{to_blocks, Block, Key},
-};
-use block_padding::Padding;
-use cipher::{
-    generic_array::{typenum::Unsigned, ArrayLength, GenericArray},
-    BlockCipher, NewBlockCipher,
-};
-
-/// Trait for a block cipher mode of operation that is used to apply a block cipher
-/// operation to input data to transform it into a variable-length output message.
-pub trait BlockMode<C, P>: Sized
-where
-    C: BlockCipher + NewBlockCipher,
-    P: Padding,
-{
-    /// Initialization Vector size.
-    type IvSize: ArrayLength<u8>;
-
-    /// Create a new block mode instance from initialized block cipher and IV.
-    fn new(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self;
-
-    /// Create a new block mode instance from fixed sized key and IV.
-    fn new_fix(key: &Key<C>, iv: &GenericArray<u8, Self::IvSize>) -> Self {
-        Self::new(C::new(key), iv)
-    }
-
-    /// Create a new block mode instance from variable size key and IV.
-    ///
-    /// Returns an error if key or IV have unsupported length.
-    fn new_from_slices(key: &[u8], iv: &[u8]) -> Result<Self, InvalidKeyIvLength> {
-        if iv.len() != Self::IvSize::USIZE {
-            return Err(InvalidKeyIvLength);
-        }
-        let iv = GenericArray::from_slice(iv);
-        let cipher = C::new_from_slice(key).map_err(|_| InvalidKeyIvLength)?;
-        Ok(Self::new(cipher, iv))
-    }
-
-    /// Encrypt blocks of data
-    fn encrypt_blocks(&mut self, blocks: &mut [Block<C>]);
-
-    /// Decrypt blocks of data
-    fn decrypt_blocks(&mut self, blocks: &mut [Block<C>]);
-
-    /// Encrypt message in-place.
-    ///
-    /// `&buffer[..pos]` is used as a message and `&buffer[pos..]` as a reserved
-    /// space for padding. The padding space should be big enough for padding,
-    /// otherwise method will return `Err(BlockModeError)`.
-    fn encrypt(mut self, buffer: &mut [u8], pos: usize) -> Result<&[u8], BlockModeError> {
-        let bs = C::BlockSize::to_usize();
-        let buf = P::pad(buffer, pos, bs).map_err(|_| BlockModeError)?;
-        self.encrypt_blocks(to_blocks(buf));
-        Ok(buf)
-    }
-
-    /// Decrypt message in-place.
-    ///
-    /// Returns an error if `buffer` length is not multiple of block size and
-    /// if after decoding message has malformed padding.
-    fn decrypt(mut self, buffer: &mut [u8]) -> Result<&[u8], BlockModeError> {
-        let bs = C::BlockSize::to_usize();
-        if buffer.len() % bs != 0 {
-            return Err(BlockModeError);
-        }
-        self.decrypt_blocks(to_blocks(buffer));
-        P::unpad(buffer).map_err(|_| BlockModeError)
-    }
-
-    /// Encrypt message and store result in vector.
-    #[cfg(feature = "alloc")]
-    fn encrypt_vec(mut self, plaintext: &[u8]) -> Vec<u8> {
-        let bs = C::BlockSize::to_usize();
-        let pos = plaintext.len();
-        let n = pos + bs;
-        let mut buf = Vec::with_capacity(n);
-        buf.extend_from_slice(plaintext);
-        // prepare space for padding
-        let block: Block<C> = Default::default();
-        buf.extend_from_slice(&block[..n - pos]);
-
-        let n = P::pad(&mut buf, pos, bs)
-            .expect("enough space for padding is allocated")
-            .len();
-        buf.truncate(n);
-        self.encrypt_blocks(to_blocks(&mut buf));
-        buf
-    }
-
-    /// Encrypt message and store result in vector.
-    #[cfg(feature = "alloc")]
-    fn decrypt_vec(mut self, ciphertext: &[u8]) -> Result<Vec<u8>, BlockModeError> {
-        let bs = C::BlockSize::to_usize();
-        if ciphertext.len() % bs != 0 {
-            return Err(BlockModeError);
-        }
-        let mut buf = ciphertext.to_vec();
-        self.decrypt_blocks(to_blocks(&mut buf));
-        let n = P::unpad(&buf).map_err(|_| BlockModeError)?.len();
-        buf.truncate(n);
-        Ok(buf)
-    }
-}
-
-/// Trait for a BlockMode, used to obtain the current state in the form of an IV
-/// that can initialize a BlockMode later and resume the original operation.
-///
-/// The IV value SHOULD be used for resuming operations only and MUST NOT be
-/// exposed to attackers. Failing to comply with this requirement breaks
-/// unpredictability and opens attack venues (see e.g. [1], sec. 3.6.2).
-///
-/// [1]: https://www.cs.umd.edu/~jkatz/imc.html
-pub trait IvState<C, P>: BlockMode<C, P>
-where
-    C: BlockCipher + NewBlockCipher,
-    P: Padding,
-{
-    /// Returns the IV needed to process the following block. This value MUST
-    /// NOT be exposed to attackers.
-    fn iv_state(&self) -> GenericArray<u8, Self::IvSize>;
-}
diff --git a/block-modes/src/utils.rs b/block-modes/src/utils.rs
index a16d2908..9e9a8afa 100644
--- a/block-modes/src/utils.rs
+++ b/block-modes/src/utils.rs
@@ -1,9 +1,3 @@
-use cipher::{
-    generic_array::{typenum::Unsigned, ArrayLength, GenericArray},
-    BlockCipher, NewBlockCipher,
-};
-use core::slice;
-
 #[inline(always)]
 pub fn xor(buf: &mut [u8], key: &[u8]) {
     debug_assert_eq!(buf.len(), key.len());
@@ -12,32 +6,28 @@ pub fn xor(buf: &mut [u8], key: &[u8]) {
     }
 }
 
-pub(crate) type Key<C> = GenericArray<u8, <C as NewBlockCipher>::KeySize>;
-pub(crate) type Block<C> = GenericArray<u8, <C as BlockCipher>::BlockSize>;
-pub(crate) type ParBlocks<C> = GenericArray<Block<C>, <C as BlockCipher>::ParBlocks>;
+// pub(crate) fn to_blocks<N>(data: &mut [u8]) -> &mut [GenericArray<u8, N>]
+// where
+//     N: ArrayLength<u8>,
+// {
+//     let n = N::to_usize();
+//     debug_assert!(data.len() % n == 0);
 
-pub(crate) fn to_blocks<N>(data: &mut [u8]) -> &mut [GenericArray<u8, N>]
-where
-    N: ArrayLength<u8>,
-{
-    let n = N::to_usize();
-    debug_assert!(data.len() % n == 0);
+//     #[allow(unsafe_code)]
+//     unsafe {
+//         slice::from_raw_parts_mut(data.as_ptr() as *mut GenericArray<u8, N>, data.len() / n)
+//     }
+// }
 
-    #[allow(unsafe_code)]
-    unsafe {
-        slice::from_raw_parts_mut(data.as_ptr() as *mut GenericArray<u8, N>, data.len() / n)
-    }
-}
+// pub(crate) fn get_par_blocks<C: BlockCipher>(
+//     blocks: &mut [Block<C>],
+// ) -> (&mut [ParBlocks<C>], &mut [Block<C>]) {
+//     let pb = C::ParBlocks::to_usize();
+//     let n_par = blocks.len() / pb;
 
-pub(crate) fn get_par_blocks<C: BlockCipher>(
-    blocks: &mut [Block<C>],
-) -> (&mut [ParBlocks<C>], &mut [Block<C>]) {
-    let pb = C::ParBlocks::to_usize();
-    let n_par = blocks.len() / pb;
+//     let (par, single) = blocks.split_at_mut(n_par * pb);
 
-    let (par, single) = blocks.split_at_mut(n_par * pb);
-
-    #[allow(unsafe_code)]
-    let par = unsafe { slice::from_raw_parts_mut(par.as_ptr() as *mut ParBlocks<C>, n_par) };
-    (par, single)
-}
+//     #[allow(unsafe_code)]
+//     let par = unsafe { slice::from_raw_parts_mut(par.as_ptr() as *mut ParBlocks<C>, n_par) };
+//     (par, single)
+// }
diff --git a/block-modes/tests/data/cfb8-aes128.ciphertext.bin b/block-modes/tests/data/cfb8-aes128.ciphertext.bin
new file mode 100644
index 00000000..d99c1935
--- /dev/null
+++ b/block-modes/tests/data/cfb8-aes128.ciphertext.bin
@@ -0,0 +1,3 @@
+m�Ý�\{a��9f8ܛ�9����t���`��!���W����U])���=d�꜈+[ǵ�P�s�ȗ�I)"���[8\r"潝�f�� ��J��������^��vh#���%>��/�j�
���MD2��_?���C����~ey���u�2���.-��ˢ�!*XV:oK����Mn�c�Y56X�
+O�^���;Ձ�D�G_	Di���ny�B��Z͸�
+¹�|���`H��\�P�X$Ď�׀tա���!�^+P�X ��ip<�k}�!~
\ No newline at end of file
diff --git a/block-modes/tests/data/ecb-aes128.ciphertext.bin b/block-modes/tests/data/ecb-aes128.ciphertext.bin
deleted file mode 100644
index 339ce3cce6be64ccc5734eb0b29a7db9aff8d282..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/block-modes/tests/lib.rs b/block-modes/tests/lib.rs
index 73b5de9b..9e062e01 100644
--- a/block-modes/tests/lib.rs
+++ b/block-modes/tests/lib.rs
@@ -1,40 +1,259 @@
 //! Test vectors generated with OpenSSL
 
-use aes::{Aes128, BlockCipher, NewBlockCipher};
-use block_modes::block_padding::NoPadding;
-use block_modes::{BlockMode, IvState};
-use block_modes::{Cbc, Cfb, Ecb, Ige, Ofb};
+// use aes::{Aes128, BlockCipher, NewBlockCipher};
+// use block_modes::block_padding::NoPadding;
+// use block_modes::{BlockMode, IvState};
+// use block_modes::{CbcEncrypt, CbcCfb, Ecb, Ige, Ofb};
+
 use cipher::generic_array::{ArrayLength, GenericArray};
 
+macro_rules! new_test {
+    (
+        $name:ident, $cipher:path, $encrypt:path, $decrypt:path,
+        $key_path:expr, $iv_path:expr, $pt_path:expr, $ct_path:expr,
+    ) => {
+        #[test]
+        fn $name() {
+            use cipher::{
+                copy_res2out, generic_array::GenericArray, BlockDecryptMut, BlockEncryptMut,
+                InnerIvInit, KeyInit,
+            };
+            use {$cipher as Cipher, $decrypt as Decrypt, $encrypt as Encrypt};
+
+            let key = GenericArray::from_slice(include_bytes!($key_path));
+            let iv = GenericArray::from_slice(include_bytes!($iv_path));
+            let pt_blocks = to_blocks(include_bytes!($pt_path));
+            let ct_blocks = to_blocks(include_bytes!($ct_path));
+            assert_eq!(pt_blocks.len(), ct_blocks.len());
+
+            let cipher = Cipher::new(key);
+
+            let mut enc = Encrypt::inner_iv_init(&cipher, iv);
+            for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+                let mut t = Default::default();
+                enc.encrypt_block((ptb, &mut t));
+                assert_eq!(ctb, &t);
+            }
+            let mut enc = Encrypt::inner_iv_init(&cipher, iv);
+            for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+                let mut t = *ptb;
+                enc.encrypt_block(&mut t);
+                assert_eq!(ctb, &t);
+            }
+            let mut dec = Decrypt::inner_iv_init(&cipher, iv);
+            for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+                let mut t = Default::default();
+                dec.decrypt_block((ctb, &mut t));
+                assert_eq!(ptb, &t);
+            }
+            let mut dec = Decrypt::inner_iv_init(&cipher, iv);
+            for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+                let mut t = *ctb;
+                dec.decrypt_block(&mut t);
+                assert_eq!(ptb, &t);
+            }
+
+            for i in 0..pt_blocks.len() {
+                let mut enc = Encrypt::inner_iv_init(&cipher, iv);
+                let mut buf = pt_blocks.to_vec();
+                for chunk in buf.chunks_mut(i + 1) {
+                    enc.encrypt_blocks(chunk.into(), copy_res2out);
+                }
+                assert_eq!(buf, ct_blocks);
+
+                let mut dec = Decrypt::inner_iv_init(&cipher, iv);
+                let mut buf = ct_blocks.to_vec();
+                for chunk in buf.chunks_mut(i + 1) {
+                    dec.decrypt_blocks(chunk.into(), copy_res2out);
+                }
+                assert_eq!(buf, pt_blocks);
+            }
+        }
+    };
+}
+
+// TODO: add PCBC test
+
+new_test!(
+    cbc_aes128,
+    aes::Aes128,
+    block_modes::cbc::Encrypt,
+    block_modes::cbc::Decrypt,
+    "data/aes128.key.bin",
+    "data/aes128.iv.bin",
+    "data/aes128.plaintext.bin",
+    "data/cbc-aes128.ciphertext.bin",
+);
+
+new_test!(
+    cfb_aes128,
+    aes::Aes128,
+    block_modes::cfb::Encrypt,
+    block_modes::cfb::Decrypt,
+    "data/aes128.key.bin",
+    "data/aes128.iv.bin",
+    "data/aes128.plaintext.bin",
+    "data/cfb-aes128.ciphertext.bin",
+);
+
+new_test!(
+    cfb8_aes128,
+    aes::Aes128,
+    block_modes::cfb8::Encrypt,
+    block_modes::cfb8::Decrypt,
+    "data/aes128.key.bin",
+    "data/aes128.iv.bin",
+    "data/aes128.plaintext.bin",
+    "data/cfb8-aes128.ciphertext.bin",
+);
+
+new_test!(
+    ofb_aes128,
+    aes::Aes128,
+    block_modes::Ofb,
+    block_modes::Ofb,
+    "data/aes128.key.bin",
+    "data/aes128.iv.bin",
+    "data/aes128.plaintext.bin",
+    "data/ofb-aes128.ciphertext.bin",
+);
+
+new_test!(
+    ige1_aes128,
+    aes::Aes128,
+    block_modes::ige::Encrypt,
+    block_modes::ige::Decrypt,
+    "data/ige-aes128-1.key.bin",
+    "data/ige-aes128-1.iv.bin",
+    "data/ige-aes128-1.plaintext.bin",
+    "data/ige-aes128-1.ciphertext.bin",
+);
+
+new_test!(
+    ige2_aes128,
+    aes::Aes128,
+    block_modes::ige::Encrypt,
+    block_modes::ige::Decrypt,
+    "data/ige-aes128-2.key.bin",
+    "data/ige-aes128-2.iv.bin",
+    "data/ige-aes128-2.plaintext.bin",
+    "data/ige-aes128-2.ciphertext.bin",
+);
+
+/*
 #[test]
-fn ecb_aes128() {
+fn cbc_aes128() {
+    use block_modes::{CbcEncrypt as Encrypt, CbcDecrypt as Decrypt};
     let key = include_bytes!("data/aes128.key.bin");
+    let iv = include_bytes!("data/aes128.iv.bin");
     let plaintext = include_bytes!("data/aes128.plaintext.bin");
-    let ciphertext = include_bytes!("data/ecb-aes128.ciphertext.bin");
-    // ECB mode ignores IV
-    let iv = Default::default();
+    let ciphertext = include_bytes!("data/cbc-aes128.ciphertext.bin");
 
-    let mode = Ecb::<Aes128, NoPadding>::new_from_slices(key, iv).unwrap();
-    assert_eq!(mode.encrypt_vec(plaintext), &ciphertext[..]);
+    let cipher = Aes128::new_from_slice(key).unwrap();
+    let pt_blocks = &to_blocks(plaintext)[..15];
+    let ct_blocks = &to_blocks(ciphertext)[..15];
+    assert_eq!(pt_blocks.len(), ct_blocks.len());
+
+    let mut enc = Encrypt::inner_iv_slice_init(&cipher, iv).unwrap();
+    let mut dec = Decrypt::inner_iv_slice_init(&cipher, iv).unwrap();
+    for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+        let mut t = Default::default();
+        enc.encrypt_block((ptb, &mut t));
+        assert_eq!(ctb, &t);
+
+        let mut t = Default::default();
+        dec.decrypt_block((ctb, &mut t));
+        assert_eq!(ptb, &t);
+    }
 
-    let mode = Ecb::<Aes128, NoPadding>::new_from_slices(key, iv).unwrap();
-    assert_eq!(mode.decrypt_vec(ciphertext).unwrap(), &plaintext[..]);
+    let mut enc = Encrypt::inner_iv_slice_init(&cipher, iv).unwrap();
+    let mut dec = Decrypt::inner_iv_slice_init(&cipher, iv).unwrap();
+    for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+        let mut t = *ptb;
+        enc.encrypt_block(&mut t);
+        assert_eq!(ctb, &t);
+
+        let mut t = *ctb;
+        dec.decrypt_block(&mut t);
+        assert_eq!(ptb, &t);
+    }
+
+    for i in (0..pt_blocks.len()).rev() {
+        println!("{:?}", i);
+        let mut enc = Encrypt::inner_iv_slice_init(&cipher, iv).unwrap();
+        let mut buf = pt_blocks.to_vec();
+        for chunk in buf.chunks_mut(i + 1) {
+            enc.encrypt_blocks(chunk.into(), copy_res2out);
+        }
+        assert_eq!(buf, ct_blocks);
+
+        let mut dec = Decrypt::inner_iv_slice_init(&cipher, iv).unwrap();
+        let mut buf = ct_blocks.to_vec();
+        for chunk in buf.chunks_mut(i + 1) {
+            dec.decrypt_blocks(chunk.into(), copy_res2out);
+        }
+        assert_eq!(buf, pt_blocks);
+    }
 }
 
+
 #[test]
-fn cbc_aes128() {
-    let key = include_bytes!("data/aes128.key.bin");
-    let iv = include_bytes!("data/aes128.iv.bin");
+fn cfb_aes128() {
+    use block_modes::{CfbEncrypt as Encrypt, CfbDecrypt as Decrypt};
+    let key = GenericArray::from_slice(include_bytes!("data/aes128.key.bin"));
+    let iv = GenericArray::from_slice(include_bytes!("data/aes128.iv.bin"));
     let plaintext = include_bytes!("data/aes128.plaintext.bin");
-    let ciphertext = include_bytes!("data/cbc-aes128.ciphertext.bin");
+    let ciphertext = include_bytes!("data/cfb-aes128.ciphertext.bin");
 
-    let mode = Cbc::<Aes128, NoPadding>::new_from_slices(key, iv).unwrap();
-    assert_eq!(mode.encrypt_vec(plaintext), &ciphertext[..]);
+    let cipher = Aes128::new(key);
+    let pt_blocks = to_blocks(plaintext);
+    let ct_blocks = to_blocks(ciphertext);
+    assert_eq!(pt_blocks.len(), ct_blocks.len());
 
-    let mode = Cbc::<Aes128, NoPadding>::new_from_slices(key, iv).unwrap();
-    assert_eq!(mode.decrypt_vec(ciphertext).unwrap(), &plaintext[..]);
+    let mut enc = Encrypt::inner_iv_init(&cipher, iv);
+    for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+        let mut t = Default::default();
+        enc.encrypt_block((ptb, &mut t));
+        assert_eq!(ctb, &t);
+    }
+    let mut enc = Encrypt::inner_iv_init(&cipher, iv);
+    for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+        let mut t = *ptb;
+        enc.encrypt_block(&mut t);
+        assert_eq!(ctb, &t);
+    }
+    let mut dec = Decrypt::inner_iv_init(&cipher, iv);
+    for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+        let mut t = Default::default();
+        dec.decrypt_block((ctb, &mut t));
+        assert_eq!(ptb, &t);
+    }
+    let mut dec = Decrypt::inner_iv_init(&cipher, iv);
+    for (ptb, ctb) in pt_blocks.iter().zip(ct_blocks.iter()) {
+        let mut t = *ctb;
+        dec.decrypt_block(&mut t);
+        assert_eq!(ptb, &t);
+    }
+
+    for i in 0..pt_blocks.len() {
+        let mut enc = Encrypt::inner_iv_init(&cipher, iv);
+        let mut buf = pt_blocks.to_vec();
+        for chunk in buf.chunks_mut(i + 1) {
+            enc.encrypt_blocks(chunk.into(), copy_res2out);
+        }
+        assert_eq!(buf, ct_blocks);
+
+        let mut dec = Decrypt::inner_iv_init(&cipher, iv);
+        let mut buf = ct_blocks.to_vec();
+        for chunk in buf.chunks_mut(i + 1) {
+            dec.decrypt_blocks(chunk.into(), copy_res2out);
+        }
+        assert_eq!(buf, pt_blocks);
+    }
 }
+*/
 
+/*
 #[test]
 fn cbc_aes128_continued() {
     type BlockSize = <Aes128 as BlockCipher>::BlockSize;
@@ -364,8 +583,9 @@ fn ige_aes256_2_continued() {
         assert_eq!(blocks, plaintext_blocks);
     }
 }
+*/
 
-fn to_blocks<N>(data: &mut [u8]) -> &mut [GenericArray<u8, N>]
+fn to_blocks_mut<N>(data: &mut [u8]) -> &mut [GenericArray<u8, N>]
 where
     N: ArrayLength<u8>,
 {
@@ -378,3 +598,17 @@ where
         slice::from_raw_parts_mut(data.as_ptr() as *mut GenericArray<u8, N>, data.len() / n)
     }
 }
+
+fn to_blocks<N>(data: &[u8]) -> &[GenericArray<u8, N>]
+where
+    N: ArrayLength<u8>,
+{
+    use core::slice;
+    let n = N::to_usize();
+    debug_assert!(data.len() % n == 0);
+
+    #[allow(unsafe_code)]
+    unsafe {
+        slice::from_raw_parts(data.as_ptr() as *const GenericArray<u8, N>, data.len() / n)
+    }
+}

From 2d1ed44acea2f444e65d4691cf9f7a1aba99fa6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?=
 =?UTF-8?q?=D0=BE=D0=B2?= <newpavlov@gmail.com>
Date: Mon, 12 Apr 2021 20:13:47 +0300
Subject: [PATCH 2/3] separate crates for each block mode

---
 Cargo.lock                  |  68 +++++++++++++--
 Cargo.toml                  |   7 ++
 modes/cbc/CHANGELOG.md      |   9 ++
 modes/cbc/Cargo.toml        |  17 ++++
 modes/cbc/README.md         |  60 +++++++++++++
 modes/cbc/src/lib.rs        | 112 ++++++++++++++++++++++++
 modes/cfb-mode/CHANGELOG.md |  36 ++++++++
 modes/cfb-mode/Cargo.toml   |  17 ++++
 modes/cfb-mode/README.md    |  60 +++++++++++++
 modes/cfb-mode/src/lib.rs   | 117 +++++++++++++++++++++++++
 modes/cfb8/CHANGELOG.md     |  34 ++++++++
 modes/cfb8/Cargo.toml       |  17 ++++
 modes/cfb8/README.md        |  60 +++++++++++++
 modes/cfb8/src/lib.rs       |  98 +++++++++++++++++++++
 modes/ctr/CHANGELOG.md      |  41 +++++++++
 modes/ctr/Cargo.toml        |  17 ++++
 modes/ctr/README.md         |  59 +++++++++++++
 modes/ctr/src/lib.rs        |   0
 modes/ige/CHANGELOG.md      |   9 ++
 modes/ige/Cargo.toml        |  17 ++++
 modes/ige/README.md         |  60 +++++++++++++
 modes/ige/src/lib.rs        | 166 ++++++++++++++++++++++++++++++++++++
 modes/ofb/CHANGELOG.md      |  30 +++++++
 modes/ofb/Cargo.toml        |  17 ++++
 modes/ofb/README.md         |  60 +++++++++++++
 modes/ofb/src/lib.rs        |  73 ++++++++++++++++
 modes/pcbc/CHANGELOG.md     |   9 ++
 modes/pcbc/Cargo.toml       |  17 ++++
 modes/pcbc/README.md        |  60 +++++++++++++
 modes/pcbc/src/lib.rs       |  97 +++++++++++++++++++++
 30 files changed, 1439 insertions(+), 5 deletions(-)
 create mode 100644 modes/cbc/CHANGELOG.md
 create mode 100644 modes/cbc/Cargo.toml
 create mode 100644 modes/cbc/README.md
 create mode 100644 modes/cbc/src/lib.rs
 create mode 100644 modes/cfb-mode/CHANGELOG.md
 create mode 100644 modes/cfb-mode/Cargo.toml
 create mode 100644 modes/cfb-mode/README.md
 create mode 100644 modes/cfb-mode/src/lib.rs
 create mode 100644 modes/cfb8/CHANGELOG.md
 create mode 100644 modes/cfb8/Cargo.toml
 create mode 100644 modes/cfb8/README.md
 create mode 100644 modes/cfb8/src/lib.rs
 create mode 100644 modes/ctr/CHANGELOG.md
 create mode 100644 modes/ctr/Cargo.toml
 create mode 100644 modes/ctr/README.md
 create mode 100644 modes/ctr/src/lib.rs
 create mode 100644 modes/ige/CHANGELOG.md
 create mode 100644 modes/ige/Cargo.toml
 create mode 100644 modes/ige/README.md
 create mode 100644 modes/ige/src/lib.rs
 create mode 100644 modes/ofb/CHANGELOG.md
 create mode 100644 modes/ofb/Cargo.toml
 create mode 100644 modes/ofb/README.md
 create mode 100644 modes/ofb/src/lib.rs
 create mode 100644 modes/pcbc/CHANGELOG.md
 create mode 100644 modes/pcbc/Cargo.toml
 create mode 100644 modes/pcbc/README.md
 create mode 100644 modes/pcbc/src/lib.rs

diff --git a/Cargo.lock b/Cargo.lock
index f68b2041..d52e7e35 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,5 +1,7 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
 [[package]]
 name = "aes"
 version = "0.7.0-pre"
@@ -7,7 +9,7 @@ dependencies = [
  "cfg-if",
  "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
  "cpuid-bool",
- "ctr",
+ "ctr 0.7.0-pre.3",
  "opaque-debug",
 ]
 
@@ -50,6 +52,30 @@ dependencies = [
  "opaque-debug",
 ]
 
+[[package]]
+name = "cbc"
+version = "0.1.0"
+dependencies = [
+ "aes",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
+]
+
+[[package]]
+name = "cfb-mode"
+version = "0.7.0"
+dependencies = [
+ "aes",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
+]
+
+[[package]]
+name = "cfb8"
+version = "0.7.0"
+dependencies = [
+ "aes",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
+]
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -59,20 +85,20 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 [[package]]
 name = "cipher"
 version = "0.3.0-pre.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1db53c34251ece12ba40366dd5ece6dc5672296d7fb34f7b1791e8f0d449e69"
+source = "git+https://github.com/RustCrypto/traits?branch=experimental_design#7bff7d65873402bf59f1cd09f0d0e23449db8ed9"
 dependencies = [
  "blobby",
+ "crypto-common",
  "generic-array",
 ]
 
 [[package]]
 name = "cipher"
 version = "0.3.0-pre.4"
-source = "git+https://github.com/RustCrypto/traits?branch=experimental_design#7bff7d65873402bf59f1cd09f0d0e23449db8ed9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1db53c34251ece12ba40366dd5ece6dc5672296d7fb34f7b1791e8f0d449e69"
 dependencies = [
  "blobby",
- "crypto-common",
  "generic-array",
 ]
 
@@ -99,6 +125,14 @@ dependencies = [
  "cipher 0.3.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "ctr"
+version = "0.7.0"
+dependencies = [
+ "aes",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
+]
+
 [[package]]
 name = "des"
 version = "0.7.0-pre"
@@ -157,6 +191,14 @@ dependencies = [
  "opaque-debug",
 ]
 
+[[package]]
+name = "ige"
+version = "0.1.0"
+dependencies = [
+ "aes",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
+]
+
 [[package]]
 name = "kuznyechik"
 version = "0.7.0-pre"
@@ -175,12 +217,28 @@ dependencies = [
  "opaque-debug",
 ]
 
+[[package]]
+name = "ofb"
+version = "0.5.0"
+dependencies = [
+ "aes",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
+]
+
 [[package]]
 name = "opaque-debug"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
+[[package]]
+name = "pcbc"
+version = "0.1.0"
+dependencies = [
+ "aes",
+ "cipher 0.3.0-pre.4 (git+https://github.com/RustCrypto/traits?branch=experimental_design)",
+]
+
 [[package]]
 name = "proc-macro-hack"
 version = "0.5.18"
diff --git a/Cargo.toml b/Cargo.toml
index 3cc9549c..5b691c1f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,13 @@ members = [
     "idea",
     "kuznyechik",
     "magma",
+    "modes/cbc",
+    "modes/cfb8",
+    "modes/cfb-mode",
+    "modes/ctr",
+    "modes/ige",
+    "modes/ofb",
+    "modes/pcbc",
     "rc2",
     "serpent",
     "sm4",
diff --git a/modes/cbc/CHANGELOG.md b/modes/cbc/CHANGELOG.md
new file mode 100644
index 00000000..0c00eeb0
--- /dev/null
+++ b/modes/cbc/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.1.0 (2018-04-25)
+- Initial release.
diff --git a/modes/cbc/Cargo.toml b/modes/cbc/Cargo.toml
new file mode 100644
index 00000000..7ac0fb42
--- /dev/null
+++ b/modes/cbc/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "cbc"
+version = "0.1.0"
+description = "Cipher Block Chaining (CBC) block cipher mode of operation"
+authors = ["RustCrypto Developers"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+edition = "2018"
+documentation = "https://docs.rs/cbc"
+repository = "https://github.com/RustCrypto/block-ciphers"
+keywords = ["crypto", "block-cipher", "ciphers"]
+
+[dependencies]
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
+
+[dev-dependencies]
+aes = { version = "=0.7.0-pre", path = "../../aes", features = ["force-soft"] }
diff --git a/modes/cbc/README.md b/modes/cbc/README.md
new file mode 100644
index 00000000..4c602898
--- /dev/null
+++ b/modes/cbc/README.md
@@ -0,0 +1,60 @@
+# RustCrypto: CBC
+
+[![crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image]
+![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Generic implementation of the [Cipher Block Chaining][CBC] (CBC) block cipher mode
+of operation.
+
+<img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/cbc_enc.svg" width="50%"><img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/cbc_dec.svg" width="50%">
+
+See [documentation][cipher-doc] of the `cipher` crate for additional information.
+
+## Minimum Supported Rust Version
+
+Rust **1.41** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be
+done with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+Licensed under either of:
+
+ * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+ * [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/cbc.svg
+[crate-link]: https://crates.io/crates/cbc
+[docs-image]: https://docs.rs/cbc/badge.svg
+[docs-link]: https://docs.rs/cbc/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260039-block-ciphers
+[build-image]: https://github.com/RustCrypto/block-ciphers/workflows/cbc/badge.svg?branch=master&event=push
+[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%cbc
+
+[//]: # (general links)
+
+[CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC
+[cipher-doc]: https://docs.rs/cipher/
diff --git a/modes/cbc/src/lib.rs b/modes/cbc/src/lib.rs
new file mode 100644
index 00000000..22174894
--- /dev/null
+++ b/modes/cbc/src/lib.rs
@@ -0,0 +1,112 @@
+//! [Cipher Block Chaining][1] (CBC) mode.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC
+use cipher::{
+    generic_array::{ArrayLength, GenericArray},
+    Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing, InOutBuf, InOutVal,
+    InResOutBuf, InnerIvInit, IvState,
+};
+
+/// CBC mode encryptor.
+#[derive(Clone)]
+pub struct Encrypt<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Encrypt<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = block.get_in().clone();
+        xor(&mut t, &self.iv);
+        self.cipher.encrypt_block((&t, block.get_out()));
+        self.iv = block.get_out().clone();
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Encrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Encrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> IvState for Encrypt<C> {
+    #[inline]
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+/// CBC mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C: BlockDecryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockDecryptMut + BlockCipher> BlockDecryptMut for Decrypt<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let enc_block = block.get_in().clone();
+        self.cipher.decrypt_block((&enc_block, block.get_out()));
+        xor(block.get_out(), &self.iv);
+        self.iv = enc_block;
+    }
+
+    fn decrypt_blocks(
+        &mut self,
+        blocks: InOutBuf<'_, '_, Block<Self>>,
+        mut proc: impl FnMut(InResOutBuf<'_, '_, '_, Block<Self>>),
+    ) {
+        let iv = &mut self.iv;
+        self.cipher.decrypt_blocks(blocks, |mut buf| {
+            let len = buf.len();
+            let (in_buf, res_buf) = buf.get_in_res();
+            xor(&mut res_buf[0], iv);
+            for i in 1..len {
+                xor(&mut res_buf[i], &in_buf[i - 1]);
+            }
+            *iv = in_buf[len - 1].clone();
+            proc(buf);
+        });
+    }
+}
+
+impl<C: BlockDecryptMut + BlockCipher> BlockProcessing for Decrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockDecryptMut + BlockCipher> InnerIvInit for Decrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockDecryptMut + BlockCipher> IvState for Decrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+#[inline(always)]
+fn xor<N: ArrayLength<u8>>(out: &mut GenericArray<u8, N>, buf: &GenericArray<u8, N>) {
+    for (a, b) in out.iter_mut().zip(buf) {
+        *a ^= *b;
+    }
+}
diff --git a/modes/cfb-mode/CHANGELOG.md b/modes/cfb-mode/CHANGELOG.md
new file mode 100644
index 00000000..8d79397f
--- /dev/null
+++ b/modes/cfb-mode/CHANGELOG.md
@@ -0,0 +1,36 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.6.0 (2020-10-16)
+### Changed
+- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#177])
+
+[#177]: https://github.com/RustCrypto/stream-ciphers/pull/177
+
+## 0.5.0 (2020-08-25)
+### Changed
+- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164])
+
+[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161
+[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164
+
+## 0.4.0 (2020-06-08)
+### Changed
+- Bump `stream-cipher` dependency to v0.4 ([#119])
+- Upgrade to Rust 2018 edition ([#119])
+
+[#119]: https://github.com/RustCrypto/stream-ciphers/pull/119
+
+## 0.3.2 (2019-03-11)
+
+## 0.3.1 (2018-11-01)
+
+## 0.3.0 (2018-11-01)
+
+## 0.2.0 (2018-10-13)
+
+## 0.1.0 (2018-10-01)
diff --git a/modes/cfb-mode/Cargo.toml b/modes/cfb-mode/Cargo.toml
new file mode 100644
index 00000000..e41deb98
--- /dev/null
+++ b/modes/cfb-mode/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "cfb-mode"
+version = "0.7.0"
+description = "Cipher Feedback (CFB) block cipher mode of operation"
+authors = ["RustCrypto Developers"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+edition = "2018"
+documentation = "https://docs.rs/cfb-mode"
+repository = "https://github.com/RustCrypto/block-ciphers"
+keywords = ["crypto", "block-cipher", "ciphers"]
+
+[dependencies]
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
+
+[dev-dependencies]
+aes = { version = "=0.7.0-pre", path = "../../aes", features = ["force-soft"] }
diff --git a/modes/cfb-mode/README.md b/modes/cfb-mode/README.md
new file mode 100644
index 00000000..19bd34fe
--- /dev/null
+++ b/modes/cfb-mode/README.md
@@ -0,0 +1,60 @@
+# RustCrypto: CFB
+
+[![crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image]
+![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Generic implementation of the [Cipher Feedback][CFB] (CFB) block cipher mode
+of operation.
+
+<img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/cfb_enc.svg" width="50%"><img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/cfb_dec.svg" width="50%">
+
+See [documentation][cipher-doc] of the `cipher` crate for additional information.
+
+## Minimum Supported Rust Version
+
+Rust **1.41** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be
+done with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+Licensed under either of:
+
+ * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+ * [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/cfb-mode.svg
+[crate-link]: https://crates.io/crates/cfb-mode
+[docs-image]: https://docs.rs/cfb-mode/badge.svg
+[docs-link]: https://docs.rs/cfb-mode/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260039-block-ciphers
+[build-image]: https://github.com/RustCrypto/block-ciphers/workflows/cfb-mode/badge.svg?branch=master&event=push
+[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%cfb-mode
+
+[//]: # (general links)
+
+[CFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)
+[cipher-doc]: https://docs.rs/cipher/
diff --git a/modes/cfb-mode/src/lib.rs b/modes/cfb-mode/src/lib.rs
new file mode 100644
index 00000000..8205a799
--- /dev/null
+++ b/modes/cfb-mode/src/lib.rs
@@ -0,0 +1,117 @@
+//! [Cipher feedback][1] (CFB) mode with full block feedback.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)
+use cipher::{
+    generic_array::{ArrayLength, GenericArray},
+    AsyncStreamCipher, Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing,
+    InOutBuf, InOutVal, InResOutBuf, InnerIvInit, IvState,
+};
+
+/// CFB mode encryptor.
+#[derive(Clone)]
+pub struct Encrypt<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Encrypt<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        self.cipher.encrypt_block(&mut self.iv);
+        xor(&mut self.iv, block.get_in());
+        *block.get_out() = self.iv.clone();
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Encrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> AsyncStreamCipher for Encrypt<C> {}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Encrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> IvState for Encrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+/// CFB mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockDecryptMut for Decrypt<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = self.iv.clone();
+        self.cipher.encrypt_block(&mut t);
+        xor(&mut t, block.get_in());
+        self.iv = block.get_in().clone();
+        *block.get_out() = t;
+    }
+
+    fn decrypt_blocks(
+        &mut self,
+        blocks: InOutBuf<'_, '_, Block<Self>>,
+        mut proc: impl FnMut(InResOutBuf<'_, '_, '_, Block<Self>>),
+    ) {
+        let mut enc_iv = self.iv.clone();
+        self.cipher.encrypt_block(&mut enc_iv);
+        let iv = &mut self.iv;
+        self.cipher.encrypt_blocks(blocks, |mut buf| {
+            let len = buf.len();
+            let (in_buf, res_buf) = buf.get_in_res();
+            for i in 0..len {
+                xor(&mut enc_iv, &in_buf[i]);
+                core::mem::swap(&mut res_buf[i], &mut enc_iv);
+            }
+            *iv = in_buf[len - 1].clone();
+            proc(buf);
+        });
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Decrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> AsyncStreamCipher for Decrypt<C> {}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Decrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> IvState for Decrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+#[inline(always)]
+fn xor<N: ArrayLength<u8>>(out: &mut GenericArray<u8, N>, buf: &GenericArray<u8, N>) {
+    for (a, b) in out.iter_mut().zip(buf) {
+        *a ^= *b;
+    }
+}
diff --git a/modes/cfb8/CHANGELOG.md b/modes/cfb8/CHANGELOG.md
new file mode 100644
index 00000000..87bd6720
--- /dev/null
+++ b/modes/cfb8/CHANGELOG.md
@@ -0,0 +1,34 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.6.0 (2020-10-16)
+### Changed
+- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#177])
+
+[#177]: https://github.com/RustCrypto/stream-ciphers/pull/177
+
+## 0.5.0 (2020-08-25)
+### Changed
+- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164])
+
+[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161
+[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164
+
+## 0.4.0 (2020-06-08)
+### Changed
+- Bump `stream-cipher` dependency to v0.4 ([#120])
+- Upgrade to Rust 2018 edition ([#120])
+
+[#120]: https://github.com/RustCrypto/stream-ciphers/pull/120
+
+## 0.3.2 (2019-03-11)
+
+## 0.3.1 (2018-11-01)
+
+## 0.3.0 (2018-11-01)
+
+## 0.1.0 (2018-11-01)
diff --git a/modes/cfb8/Cargo.toml b/modes/cfb8/Cargo.toml
new file mode 100644
index 00000000..cf6aef8b
--- /dev/null
+++ b/modes/cfb8/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "cfb8"
+version = "0.7.0"
+description = "Cipher Feedback with eight bit feedback (CFB-8) block cipher mode of operation"
+authors = ["RustCrypto Developers"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+edition = "2018"
+documentation = "https://docs.rs/cfb8"
+repository = "https://github.com/RustCrypto/block-ciphers"
+keywords = ["crypto", "block-cipher", "ciphers"]
+
+[dependencies]
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
+
+[dev-dependencies]
+aes = { version = "=0.7.0-pre", path = "../../aes", features = ["force-soft"] }
diff --git a/modes/cfb8/README.md b/modes/cfb8/README.md
new file mode 100644
index 00000000..46cdda5d
--- /dev/null
+++ b/modes/cfb8/README.md
@@ -0,0 +1,60 @@
+# RustCrypto: CFB-8
+
+[![crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image]
+![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Generic implementation of the [Cipher Feedback][CFB-8] with eight bit feedback (CFB-8)
+block cipher mode of operation.
+
+<img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/cfb_enc.svg" width="50%"><img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/cfb_dec.svg" width="50%">
+
+See [documentation][cipher-doc] of the `cipher` crate for additional information.
+
+## Minimum Supported Rust Version
+
+Rust **1.41** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be
+done with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+Licensed under either of:
+
+ * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+ * [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/cfb.svg
+[crate-link]: https://crates.io/crates/cfb
+[docs-image]: https://docs.rs/cfb/badge.svg
+[docs-link]: https://docs.rs/cfb/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260039-block-ciphers
+[build-image]: https://github.com/RustCrypto/block-ciphers/workflows/cfb/badge.svg?branch=master&event=push
+[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%cfb
+
+[//]: # (general links)
+
+[CFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB-1,_CFB-8,_CFB-64,_CFB-128,_etc.
+[cipher-doc]: https://docs.rs/cipher/
diff --git a/modes/cfb8/src/lib.rs b/modes/cfb8/src/lib.rs
new file mode 100644
index 00000000..d2db7ce1
--- /dev/null
+++ b/modes/cfb8/src/lib.rs
@@ -0,0 +1,98 @@
+//! [Cipher Feedback with eight bit feedback][1] (CFB-8) mode.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB-1,_CFB-8,_CFB-64,_CFB-128,_etc.
+use cipher::{
+    generic_array::{typenum::U1, GenericArray},
+    AsyncStreamCipher, Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing,
+    InOutVal, InnerIvInit, IvState,
+};
+
+/// CFB-8 mode encryptor.
+#[derive(Clone)]
+pub struct Encrypt<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Encrypt<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = self.iv.clone();
+        self.cipher.encrypt_block(&mut t);
+        let r = block.get_in()[0] ^ t[0];
+        block.get_out()[0] = r;
+        let n = self.iv.len();
+        for i in 0..n - 1 {
+            self.iv[i] = self.iv[i + 1];
+        }
+        self.iv[n - 1] = r;
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Encrypt<C> {
+    type BlockSize = U1;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> AsyncStreamCipher for Encrypt<C> {}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Encrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> IvState for Encrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+/// CFB-8 mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockDecryptMut for Decrypt<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = self.iv.clone();
+        self.cipher.encrypt_block(&mut t);
+        let r = block.get_in()[0];
+        block.get_out()[0] = r ^ t[0];
+        let n = self.iv.len();
+        for i in 0..n - 1 {
+            self.iv[i] = self.iv[i + 1];
+        }
+        self.iv[n - 1] = r;
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Decrypt<C> {
+    type BlockSize = U1;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> AsyncStreamCipher for Decrypt<C> {}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Decrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> IvState for Decrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
diff --git a/modes/ctr/CHANGELOG.md b/modes/ctr/CHANGELOG.md
new file mode 100644
index 00000000..cad9c7f7
--- /dev/null
+++ b/modes/ctr/CHANGELOG.md
@@ -0,0 +1,41 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.6.0 (2020-10-16)
+### Added
+- `Ctr32BE` and `Ctr32LE` ([#170])
+
+### Changed
+- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#177])
+
+[#177]: https://github.com/RustCrypto/stream-ciphers/pull/177
+[#170]: https://github.com/RustCrypto/stream-ciphers/pull/170
+
+## 0.5.0 (2020-08-26)
+### Changed
+- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164])
+
+[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161
+[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164
+
+## 0.4.0 (2020-06-06)
+### Changed
+- Upgrade to the `stream-cipher` v0.4 crate ([#116], [#138])
+- Upgrade to Rust 2018 edition ([#116])
+
+[#138]: https://github.com/RustCrypto/stream-ciphers/pull/138
+[#116]: https://github.com/RustCrypto/stream-ciphers/pull/121
+
+## 0.3.2 (2019-03-11)
+
+## 0.3.0 (2018-11-01)
+
+## 0.2.0 (2018-10-13)
+
+## 0.1.1 (2018-10-13)
+
+## 0.1.0 (2018-07-30)
diff --git a/modes/ctr/Cargo.toml b/modes/ctr/Cargo.toml
new file mode 100644
index 00000000..ce27e473
--- /dev/null
+++ b/modes/ctr/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "ctr"
+version = "0.7.0"
+description = "Counter (CTR) block cipher mode of operation"
+authors = ["RustCrypto Developers"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+edition = "2018"
+documentation = "https://docs.rs/ctr"
+repository = "https://github.com/RustCrypto/block-ciphers"
+keywords = ["crypto", "block-cipher", "ciphers"]
+
+[dependencies]
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
+
+[dev-dependencies]
+aes = { version = "=0.7.0-pre", path = "../../aes", features = ["force-soft"] }
diff --git a/modes/ctr/README.md b/modes/ctr/README.md
new file mode 100644
index 00000000..7a87fc22
--- /dev/null
+++ b/modes/ctr/README.md
@@ -0,0 +1,59 @@
+# RustCrypto: CTR
+
+[![crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image]
+![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Generic implementation of the [Counter][CTR] (CTR) block cipher mode of operation.
+
+<img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/ctr_enc.svg" width="50%"><img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/ctr_dec.svg" width="50%">
+
+See [documentation][cipher-doc] of the `cipher` crate for additional information.
+
+## Minimum Supported Rust Version
+
+Rust **1.41** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be
+done with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+Licensed under either of:
+
+ * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+ * [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/ctr.svg
+[crate-link]: https://crates.io/crates/ctr
+[docs-image]: https://docs.rs/ctr/badge.svg
+[docs-link]: https://docs.rs/ctr/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260039-block-ciphers
+[build-image]: https://github.com/RustCrypto/block-ciphers/workflows/ctr/badge.svg?branch=master&event=push
+[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%ctr
+
+[//]: # (general links)
+
+[CTR]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)
+[cipher-doc]: https://docs.rs/cipher/
diff --git a/modes/ctr/src/lib.rs b/modes/ctr/src/lib.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/modes/ige/CHANGELOG.md b/modes/ige/CHANGELOG.md
new file mode 100644
index 00000000..da01deb5
--- /dev/null
+++ b/modes/ige/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.1.0 (2018-04-25)
+- Initial release
diff --git a/modes/ige/Cargo.toml b/modes/ige/Cargo.toml
new file mode 100644
index 00000000..512f35aa
--- /dev/null
+++ b/modes/ige/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "ige"
+version = "0.1.0"
+description = "Infinite Garble Extension (IGE) block cipher mode of operation"
+authors = ["RustCrypto Developers"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+edition = "2018"
+documentation = "https://docs.rs/ige"
+repository = "https://github.com/RustCrypto/block-ciphers"
+keywords = ["crypto", "block-cipher", "ciphers"]
+
+[dependencies]
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
+
+[dev-dependencies]
+aes = { version = "=0.7.0-pre", path = "../../aes", features = ["force-soft"] }
diff --git a/modes/ige/README.md b/modes/ige/README.md
new file mode 100644
index 00000000..af76ed4e
--- /dev/null
+++ b/modes/ige/README.md
@@ -0,0 +1,60 @@
+# RustCrypto: IGE
+
+[![crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image]
+![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Generic implementation of the [Infinite Garble Extension][IGE] (IGE) block cipher mode
+of operation.
+
+<img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/ige_enc.svg" width="50%"><img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/ige_dec.svg" width="50%">
+
+See [documentation][cipher-doc] of the `cipher` crate for additional information.
+
+## Minimum Supported Rust Version
+
+Rust **1.41** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be
+done with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+Licensed under either of:
+
+ * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+ * [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/ige.svg
+[crate-link]: https://crates.io/crates/ige
+[docs-image]: https://docs.rs/ige/badge.svg
+[docs-link]: https://docs.rs/ige/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260039-block-ciphers
+[build-image]: https://github.com/RustCrypto/block-ciphers/workflows/ige/badge.svg?branch=master&event=push
+[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%ige
+
+[//]: # (general links)
+
+[CBC]: https://www.links.org/files/openssl-ige.pdf
+[cipher-doc]: https://docs.rs/cipher/
diff --git a/modes/ige/src/lib.rs b/modes/ige/src/lib.rs
new file mode 100644
index 00000000..d86282be
--- /dev/null
+++ b/modes/ige/src/lib.rs
@@ -0,0 +1,166 @@
+//! [Infinite Garble Extension][1] (IGE) block cipher mode of operation.
+//!
+//! [1]: https://www.links.org/files/openssl-ige.pdf
+use cipher::{
+    generic_array::{
+        sequence::Concat,
+        typenum::{Sum, Unsigned},
+        ArrayLength, GenericArray,
+    },
+    Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing, InOutVal, InnerIvInit,
+    IvState,
+};
+use core::ops::Add;
+
+type BlockSize<C> = <C as BlockProcessing>::BlockSize;
+type IgeIvSize<C> = Sum<BlockSize<C>, BlockSize<C>>;
+
+/// IGE mode encryptor.
+#[derive(Clone)]
+pub struct Encrypt<C>
+where
+    C: BlockEncryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    cipher: C,
+    x: Block<C>,
+    y: Block<C>,
+}
+
+impl<C> BlockEncryptMut for Encrypt<C>
+where
+    C: BlockEncryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let new_x = block.get_in().clone();
+        let mut t = block.get_in().clone();
+        xor(&mut t, &self.y);
+        self.cipher.encrypt_block(&mut t);
+        xor(&mut t, &self.x);
+        *block.get_out() = t.clone();
+        self.x = new_x;
+        self.y = t;
+    }
+}
+
+impl<C> BlockProcessing for Encrypt<C>
+where
+    C: BlockEncryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    type BlockSize = C::BlockSize;
+}
+
+impl<C> InnerIvInit for Encrypt<C>
+where
+    C: BlockEncryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    type Inner = C;
+    type IvSize = IgeIvSize<C>;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        let (y, x) = iv.split_at(C::BlockSize::to_usize());
+        Self {
+            cipher,
+            x: GenericArray::clone_from_slice(x),
+            y: GenericArray::clone_from_slice(y),
+        }
+    }
+}
+
+impl<C> IvState for Encrypt<C>
+where
+    C: BlockEncryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    #[inline]
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.y.clone().concat(self.x.clone())
+    }
+}
+
+/// IGE mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    cipher: C,
+    x: Block<C>,
+    y: Block<C>,
+}
+
+impl<C> BlockDecryptMut for Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let new_y = block.get_in().clone();
+        let mut t = block.get_in().clone();
+        xor(&mut t, &self.x);
+        self.cipher.decrypt_block(&mut t);
+        xor(&mut t, &self.y);
+        *block.get_out() = t.clone();
+        self.y = new_y;
+        self.x = t;
+    }
+}
+
+impl<C> BlockProcessing for Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    type BlockSize = C::BlockSize;
+}
+
+impl<C> InnerIvInit for Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    type Inner = C;
+    type IvSize = IgeIvSize<C>;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        let (y, x) = iv.split_at(C::BlockSize::to_usize());
+        Self {
+            cipher,
+            x: GenericArray::clone_from_slice(x),
+            y: GenericArray::clone_from_slice(y),
+        }
+    }
+}
+
+impl<C> IvState for Decrypt<C>
+where
+    C: BlockDecryptMut + BlockCipher,
+    C::BlockSize: Add,
+    IgeIvSize<C>: ArrayLength<u8>,
+{
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.y.clone().concat(self.x.clone())
+    }
+}
+
+#[inline(always)]
+fn xor<N: ArrayLength<u8>>(out: &mut GenericArray<u8, N>, buf: &GenericArray<u8, N>) {
+    for (a, b) in out.iter_mut().zip(buf) {
+        *a ^= *b;
+    }
+}
diff --git a/modes/ofb/CHANGELOG.md b/modes/ofb/CHANGELOG.md
new file mode 100644
index 00000000..46b84fbb
--- /dev/null
+++ b/modes/ofb/CHANGELOG.md
@@ -0,0 +1,30 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.4.0 (2020-10-16)
+### Changed
+- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#177])
+
+[#177]: https://github.com/RustCrypto/stream-ciphers/pull/177
+
+## 0.3.0 (2020-08-25)
+### Changed
+- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164])
+
+[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161
+[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164
+
+## 0.2.0 (2020-06-08)
+### Changed
+- Bump `stream-cipher` dependency to v0.4 ([#123])
+- Upgrade to Rust 2018 edition ([#123])
+
+[#123]: https://github.com/RustCrypto/stream-ciphers/pull/123
+
+## 0.1.1 (2019-03-11)
+
+## 0.1.0 (2018-12-26)
diff --git a/modes/ofb/Cargo.toml b/modes/ofb/Cargo.toml
new file mode 100644
index 00000000..32572f5a
--- /dev/null
+++ b/modes/ofb/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "ofb"
+version = "0.5.0"
+description = "Output Feedback][OFB] (OFB) block cipher mode of operation"
+authors = ["RustCrypto Developers"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+edition = "2018"
+documentation = "https://docs.rs/ofb"
+repository = "https://github.com/RustCrypto/block-ciphers"
+keywords = ["crypto", "block-cipher", "ciphers"]
+
+[dependencies]
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
+
+[dev-dependencies]
+aes = { version = "=0.7.0-pre", path = "../../aes", features = ["force-soft"] }
diff --git a/modes/ofb/README.md b/modes/ofb/README.md
new file mode 100644
index 00000000..cde9dd09
--- /dev/null
+++ b/modes/ofb/README.md
@@ -0,0 +1,60 @@
+# RustCrypto: OFB
+
+[![crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image]
+![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Generic implementation of the [Output Feedback][OFB] (OFB) block cipher mode
+of operation.
+
+<img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/ofb_enc.svg" width="50%"><img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/ofb_dec.svg" width="50%">
+
+See [documentation][cipher-doc] of the `cipher` crate for additional information.
+
+## Minimum Supported Rust Version
+
+Rust **1.41** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be
+done with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+Licensed under either of:
+
+ * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+ * [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/ofb.svg
+[crate-link]: https://crates.io/crates/ofb
+[docs-image]: https://docs.rs/ofb/badge.svg
+[docs-link]: https://docs.rs/ofb/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260039-block-ciphers
+[build-image]: https://github.com/RustCrypto/block-ciphers/workflows/ofb/badge.svg?branch=master&event=push
+[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%ofb
+
+[//]: # (general links)
+
+[OFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB)
+[cipher-doc]: https://docs.rs/cipher/
diff --git a/modes/ofb/src/lib.rs b/modes/ofb/src/lib.rs
new file mode 100644
index 00000000..a8be6305
--- /dev/null
+++ b/modes/ofb/src/lib.rs
@@ -0,0 +1,73 @@
+/// [Output feedback][1] (OFB) mode.
+///
+/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB)
+use cipher::{
+    errors::LoopError,
+    generic_array::{ArrayLength, GenericArray},
+    Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing, InOutVal, InnerIvInit,
+    IvState, StreamCipherCore,
+};
+
+/// Output feedback (OFB) mode.
+#[derive(Clone)]
+pub struct Ofb<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> StreamCipherCore for Ofb<C> {
+    fn gen_keystream_block(&mut self) -> Result<Block<Self>, LoopError> {
+        self.cipher.encrypt_block(&mut self.iv);
+        Ok(self.iv.clone())
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Ofb<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Ofb<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> IvState for Ofb<C> {
+    #[inline]
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Ofb<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        self.cipher.encrypt_block(&mut self.iv);
+        *block.get_out() = xor_ret(&self.iv, block.get_in());
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockDecryptMut for Ofb<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        self.cipher.encrypt_block(&mut self.iv);
+        *block.get_out() = xor_ret(&self.iv, block.get_in());
+    }
+}
+
+#[inline(always)]
+fn xor_ret<N: ArrayLength<u8>>(
+    buf1: &GenericArray<u8, N>,
+    buf2: &GenericArray<u8, N>,
+) -> GenericArray<u8, N> {
+    let mut res = GenericArray::<u8, N>::default();
+    for i in 0..N::USIZE {
+        res[i] = buf1[i] ^ buf2[i];
+    }
+    res
+}
diff --git a/modes/pcbc/CHANGELOG.md b/modes/pcbc/CHANGELOG.md
new file mode 100644
index 00000000..da01deb5
--- /dev/null
+++ b/modes/pcbc/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.1.0 (2018-04-25)
+- Initial release
diff --git a/modes/pcbc/Cargo.toml b/modes/pcbc/Cargo.toml
new file mode 100644
index 00000000..ba98ab56
--- /dev/null
+++ b/modes/pcbc/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "pcbc"
+version = "0.1.0"
+description = "Propagating Cipher Block Chaining (PCBC) block cipher mode of operation"
+authors = ["RustCrypto Developers"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+edition = "2018"
+documentation = "https://docs.rs/pcbc"
+repository = "https://github.com/RustCrypto/block-ciphers"
+keywords = ["crypto", "block-cipher", "ciphers"]
+
+[dependencies]
+cipher = { git = "https://github.com/RustCrypto/traits", branch = "experimental_design" }
+
+[dev-dependencies]
+aes = { version = "=0.7.0-pre", path = "../../aes", features = ["force-soft"] }
diff --git a/modes/pcbc/README.md b/modes/pcbc/README.md
new file mode 100644
index 00000000..dbf70203
--- /dev/null
+++ b/modes/pcbc/README.md
@@ -0,0 +1,60 @@
+# RustCrypto: PCBC
+
+[![crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image]
+![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Generic implementation of the [Propagating Cipher Block Chaining][PCBC] (PCBC)
+block cipher mode of operation.
+
+<img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/pcbc_enc.svg" width="50%"><img src="https://raw.githubusercontent.com/RustCrypto/meta/master/img/block-ciphers/modes/pcbc_dec.svg" width="50%">
+
+See [documentation][cipher-doc] of the `cipher` crate for additional information.
+
+## Minimum Supported Rust Version
+
+Rust **1.41** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be
+done with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+Licensed under either of:
+
+ * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+ * [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/pcbc.svg
+[crate-link]: https://crates.io/crates/pcbc
+[docs-image]: https://docs.rs/pcbc/badge.svg
+[docs-link]: https://docs.rs/pcbc/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260039-block-ciphers
+[build-image]: https://github.com/RustCrypto/block-ciphers/workflows/pcbc/badge.svg?branch=master&event=push
+[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%pcbc
+
+[//]: # (general links)
+
+[PCBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Propagating_cipher_block_chaining_(PCBC)
+[cipher-doc]: https://docs.rs/cipher/
diff --git a/modes/pcbc/src/lib.rs b/modes/pcbc/src/lib.rs
new file mode 100644
index 00000000..c322b08b
--- /dev/null
+++ b/modes/pcbc/src/lib.rs
@@ -0,0 +1,97 @@
+//! [Propagating Cipher Block Chaining][1] (PCBC) mode.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Propagating_cipher_block_chaining_(PCBC)
+use cipher::{
+    generic_array::{ArrayLength, GenericArray},
+    Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing, InOutVal, InnerIvInit,
+    IvState,
+};
+
+/// PCBC mode encryptor.
+#[derive(Clone)]
+pub struct Encrypt<C: BlockEncryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockEncryptMut for Encrypt<C> {
+    fn encrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = self.iv.clone();
+        xor(&mut t, block.get_in());
+        self.cipher.encrypt_block((&t, block.get_out()));
+        xor(&mut t, block.get_out());
+        self.iv = t;
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> BlockProcessing for Encrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockEncryptMut + BlockCipher> InnerIvInit for Encrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockEncryptMut + BlockCipher> IvState for Encrypt<C> {
+    #[inline]
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+/// PCBC mode decryptor.
+#[derive(Clone)]
+pub struct Decrypt<C: BlockDecryptMut + BlockCipher> {
+    cipher: C,
+    iv: Block<C>,
+}
+
+impl<C: BlockDecryptMut + BlockCipher> BlockDecryptMut for Decrypt<C> {
+    fn decrypt_block(&mut self, mut block: impl InOutVal<Block<Self>>) {
+        let mut t = Default::default();
+        self.cipher.decrypt_block((block.get_in(), &mut t));
+        xor(&mut t, &self.iv);
+        self.iv.copy_from_slice(block.get_in());
+        block.get_out().copy_from_slice(&t);
+        xor(&mut self.iv, &t);
+    }
+}
+
+impl<C: BlockDecryptMut + BlockCipher> BlockProcessing for Decrypt<C> {
+    type BlockSize = C::BlockSize;
+}
+
+impl<C: BlockDecryptMut + BlockCipher> InnerIvInit for Decrypt<C> {
+    type Inner = C;
+    type IvSize = C::BlockSize;
+
+    #[inline]
+    fn inner_iv_init(cipher: C, iv: &GenericArray<u8, Self::IvSize>) -> Self {
+        Self {
+            cipher,
+            iv: iv.clone(),
+        }
+    }
+}
+
+impl<C: BlockDecryptMut + BlockCipher> IvState for Decrypt<C> {
+    fn iv_state(&self) -> GenericArray<u8, Self::IvSize> {
+        self.iv.clone()
+    }
+}
+
+#[inline(always)]
+fn xor<N: ArrayLength<u8>>(out: &mut GenericArray<u8, N>, buf: &GenericArray<u8, N>) {
+    for (a, b) in out.iter_mut().zip(buf) {
+        *a ^= *b;
+    }
+}

From 5bced6495a70d1a4a9fa065bcf9f48398ab2d429 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?=
 =?UTF-8?q?=D0=BE=D0=B2?= <newpavlov@gmail.com>
Date: Mon, 12 Apr 2021 20:16:56 +0300
Subject: [PATCH 3/3] add crate-level attributes to the block mode crates

---
 modes/cbc/src/lib.rs      |  9 +++++++++
 modes/cfb-mode/src/lib.rs |  9 +++++++++
 modes/cfb8/src/lib.rs     |  9 +++++++++
 modes/ige/src/lib.rs      |  9 +++++++++
 modes/ofb/src/lib.rs      | 15 ++++++++++++---
 modes/pcbc/src/lib.rs     |  9 +++++++++
 6 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/modes/cbc/src/lib.rs b/modes/cbc/src/lib.rs
index 22174894..1c814793 100644
--- a/modes/cbc/src/lib.rs
+++ b/modes/cbc/src/lib.rs
@@ -1,6 +1,15 @@
 //! [Cipher Block Chaining][1] (CBC) mode.
 //!
 //! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC
+
+#![no_std]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg",
+    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg"
+)]
+#![deny(unsafe_code)]
+#![warn(missing_docs, rust_2018_idioms)]
+
 use cipher::{
     generic_array::{ArrayLength, GenericArray},
     Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing, InOutBuf, InOutVal,
diff --git a/modes/cfb-mode/src/lib.rs b/modes/cfb-mode/src/lib.rs
index 8205a799..05c7046a 100644
--- a/modes/cfb-mode/src/lib.rs
+++ b/modes/cfb-mode/src/lib.rs
@@ -1,6 +1,15 @@
 //! [Cipher feedback][1] (CFB) mode with full block feedback.
 //!
 //! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)
+
+#![no_std]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg",
+    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg"
+)]
+#![deny(unsafe_code)]
+#![warn(missing_docs, rust_2018_idioms)]
+
 use cipher::{
     generic_array::{ArrayLength, GenericArray},
     AsyncStreamCipher, Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing,
diff --git a/modes/cfb8/src/lib.rs b/modes/cfb8/src/lib.rs
index d2db7ce1..2defbd27 100644
--- a/modes/cfb8/src/lib.rs
+++ b/modes/cfb8/src/lib.rs
@@ -1,6 +1,15 @@
 //! [Cipher Feedback with eight bit feedback][1] (CFB-8) mode.
 //!
 //! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB-1,_CFB-8,_CFB-64,_CFB-128,_etc.
+
+#![no_std]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg",
+    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg"
+)]
+#![deny(unsafe_code)]
+#![warn(missing_docs, rust_2018_idioms)]
+
 use cipher::{
     generic_array::{typenum::U1, GenericArray},
     AsyncStreamCipher, Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing,
diff --git a/modes/ige/src/lib.rs b/modes/ige/src/lib.rs
index d86282be..ddbf77a1 100644
--- a/modes/ige/src/lib.rs
+++ b/modes/ige/src/lib.rs
@@ -1,6 +1,15 @@
 //! [Infinite Garble Extension][1] (IGE) block cipher mode of operation.
 //!
 //! [1]: https://www.links.org/files/openssl-ige.pdf
+
+#![no_std]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg",
+    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg"
+)]
+#![deny(unsafe_code)]
+#![warn(missing_docs, rust_2018_idioms)]
+
 use cipher::{
     generic_array::{
         sequence::Concat,
diff --git a/modes/ofb/src/lib.rs b/modes/ofb/src/lib.rs
index a8be6305..c9ca9020 100644
--- a/modes/ofb/src/lib.rs
+++ b/modes/ofb/src/lib.rs
@@ -1,6 +1,15 @@
-/// [Output feedback][1] (OFB) mode.
-///
-/// [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB)
+//! [Output feedback][1] (OFB) mode.
+//!
+//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB)
+
+#![no_std]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg",
+    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg"
+)]
+#![deny(unsafe_code)]
+#![warn(missing_docs, rust_2018_idioms)]
+
 use cipher::{
     errors::LoopError,
     generic_array::{ArrayLength, GenericArray},
diff --git a/modes/pcbc/src/lib.rs b/modes/pcbc/src/lib.rs
index c322b08b..4afbe47d 100644
--- a/modes/pcbc/src/lib.rs
+++ b/modes/pcbc/src/lib.rs
@@ -1,6 +1,15 @@
 //! [Propagating Cipher Block Chaining][1] (PCBC) mode.
 //!
 //! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Propagating_cipher_block_chaining_(PCBC)
+
+#![no_std]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg",
+    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/master/logo.svg"
+)]
+#![deny(unsafe_code)]
+#![warn(missing_docs, rust_2018_idioms)]
+
 use cipher::{
     generic_array::{ArrayLength, GenericArray},
     Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockProcessing, InOutVal, InnerIvInit,
