diff --git a/Cargo.lock b/Cargo.lock
index b3d5fe3681baa74fc71ae3ce5dca9dd37e98f015..5b071e7547513ba25f339c3c80c3ea962264e89e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,5 +1,37 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+[[package]]
+name = "aes"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9"
+dependencies = [
+ "aes-soft",
+ "aesni",
+ "block-cipher-trait",
+]
+
+[[package]]
+name = "aes-soft"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d"
+dependencies = [
+ "block-cipher-trait",
+ "byteorder",
+ "opaque-debug",
+]
+
+[[package]]
+name = "aesni"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100"
+dependencies = [
+ "block-cipher-trait",
+ "opaque-debug",
+]
+
 [[package]]
 name = "anyhow"
 version = "1.0.26"
@@ -34,6 +66,15 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "block-cipher-trait"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "block-padding"
 version = "0.1.5"
@@ -102,6 +143,7 @@ dependencies = [
 name = "dup-crypto"
 version = "0.8.0"
 dependencies = [
+ "aes",
  "base64",
  "bincode",
  "bs58",
diff --git a/Cargo.toml b/Cargo.toml
index 7d67f1192edbdb92ea2ea3395755771e5099c68f..c79644f8f5a46ec91a0c43d567ddd86b9af64094 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ edition = "2018"
 path = "src/lib.rs"
 
 [dependencies]
+aes = { version = "0.3.2", optional = true }
 base64 = "0.11.0"
 bs58 = "0.3.0"
 byteorder = "1.3.2"
@@ -28,4 +29,5 @@ bincode = "1.2.0"
 
 [features]
 default = ["ser"]
+aes256 = ["aes"]
 ser = ["serde"]
diff --git a/src/aes256.rs b/src/aes256.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d3b5c0bd794f06cdadca5e7f3a4e53d516f4923a
--- /dev/null
+++ b/src/aes256.rs
@@ -0,0 +1,57 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Aes256 encryption/decryption
+
+pub(crate) mod decrypt;
+pub(crate) mod encrypt;
+
+pub use aes::Aes256;
+pub use decrypt::decrypt_bytes;
+pub use encrypt::encrypt_bytes;
+
+use crate::seeds::Seed32;
+use aes::block_cipher_trait::generic_array::GenericArray;
+use aes::block_cipher_trait::BlockCipher;
+
+type Block = GenericArray<u8, <Aes256 as BlockCipher>::BlockSize>;
+type ParBlocks = <Aes256 as BlockCipher>::ParBlocks;
+
+/// Create cipher from seed of 32 bytes
+pub fn new_cipher(seed: Seed32) -> Aes256 {
+    Aes256::new(GenericArray::from_slice(seed.as_ref()))
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn encrypt_and_decrypt_128_bytes() {
+        let cipher = new_cipher(Seed32::default());
+
+        let bytes = [3u8; 128];
+        let mut encrypted_bytes = bytes;
+
+        encrypt_bytes(&cipher, &mut encrypted_bytes);
+
+        decrypt_bytes(&cipher, &mut encrypted_bytes);
+
+        for i in 0..128 {
+            assert_eq!(bytes[i], encrypted_bytes[i]);
+        }
+    }
+}
diff --git a/src/aes256/decrypt.rs b/src/aes256/decrypt.rs
new file mode 100644
index 0000000000000000000000000000000000000000..90bab47c54c1ce0c8c37902ef38b6e44e548b835
--- /dev/null
+++ b/src/aes256/decrypt.rs
@@ -0,0 +1,70 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Aes256 decryption
+
+use super::{Aes256, Block, ParBlocks};
+use aes::block_cipher_trait::generic_array::GenericArray;
+use aes::block_cipher_trait::BlockCipher;
+
+/// Decrypt bytes.
+/// The length of the bytes slice must be a multiple of 16 !
+/// Panics if the length of the bytes slice is not a multiple of 16.
+pub fn decrypt_bytes(cipher: &Aes256, bytes: &mut [u8]) {
+    assert!(bytes.len() % 16 == 0);
+
+    let mut remaining_len = bytes.len();
+    let par_len = bytes.len() / 128;
+    if par_len > 0 {
+        decrypt_par_n_blocks(cipher, &mut bytes[..par_len], par_len / 8);
+        remaining_len -= par_len;
+    }
+    if remaining_len > 0 {
+        decrypt_n_blocks(cipher, &mut bytes[par_len..], remaining_len / 16);
+    }
+}
+
+fn decrypt_par_n_blocks(cipher: &Aes256, bytes: &mut [u8], n: usize) {
+    for i in (0..n).step_by(8) {
+        decrypt_8_blocks(cipher, &mut bytes[i..i + 128]);
+    }
+}
+
+pub(crate) fn decrypt_8_blocks(cipher: &Aes256, bytes: &mut [u8]) {
+    let mut blocks: GenericArray<Block, ParBlocks> = (0..8)
+        .map(|i| {
+            let begin = i * 16;
+            let end = begin + 16;
+            GenericArray::clone_from_slice(&bytes[begin..end])
+        })
+        .collect();
+
+    cipher.decrypt_blocks(&mut blocks);
+
+    for (i, block) in blocks.into_iter().enumerate() {
+        let begin = i * 16;
+        let end = (i + 1) * 16;
+        bytes[begin..end].copy_from_slice(block.as_slice());
+    }
+}
+
+pub(crate) fn decrypt_n_blocks(cipher: &Aes256, bytes: &mut [u8], n: usize) {
+    for i in 0..n {
+        let begin = i * 16;
+        let end = (i + 1) * 16;
+        let mut block = GenericArray::from_mut_slice(&mut bytes[begin..end]);
+        cipher.decrypt_block(&mut block);
+    }
+}
diff --git a/src/aes256/encrypt.rs b/src/aes256/encrypt.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b3995f6d102ce95b082d4cfcbf2936181f3f83f5
--- /dev/null
+++ b/src/aes256/encrypt.rs
@@ -0,0 +1,70 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Aes256 encryption
+
+use super::{Aes256, Block, ParBlocks};
+use aes::block_cipher_trait::generic_array::GenericArray;
+use aes::block_cipher_trait::BlockCipher;
+
+/// Encrypt bytes.
+/// The length of the bytes slice must be a multiple of 16 !
+/// Panics if the length of the bytes slice is not a multiple of 16.
+pub fn encrypt_bytes(cipher: &Aes256, bytes: &mut [u8]) {
+    assert!(bytes.len() % 16 == 0);
+
+    let mut remaining_len = bytes.len();
+    let par_len = bytes.len() / 128;
+    if par_len > 0 {
+        encrypt_par_n_blocks(cipher, &mut bytes[..par_len], par_len / 8);
+        remaining_len -= par_len;
+    }
+    if remaining_len > 0 {
+        encrypt_n_blocks(cipher, &mut bytes[par_len..], remaining_len / 16);
+    }
+}
+
+fn encrypt_par_n_blocks(cipher: &Aes256, bytes: &mut [u8], n: usize) {
+    for i in (0..n).step_by(8) {
+        encrypt_8_blocks(cipher, &mut bytes[i..i + 128]);
+    }
+}
+
+pub(crate) fn encrypt_8_blocks(cipher: &Aes256, bytes: &mut [u8]) {
+    let mut blocks: GenericArray<Block, ParBlocks> = (0..8)
+        .map(|i| {
+            let begin = i * 16;
+            let end = begin + 16;
+            GenericArray::clone_from_slice(&bytes[begin..end])
+        })
+        .collect();
+
+    cipher.encrypt_blocks(&mut blocks);
+
+    for (i, block) in blocks.into_iter().enumerate() {
+        let begin = i * 16;
+        let end = (i + 1) * 16;
+        bytes[begin..end].copy_from_slice(block.as_slice());
+    }
+}
+
+pub(crate) fn encrypt_n_blocks(cipher: &Aes256, bytes: &mut [u8], n: usize) {
+    for i in 0..n {
+        let begin = i * 16;
+        let end = (i + 1) * 16;
+        let mut block = GenericArray::from_mut_slice(&mut bytes[begin..end]);
+        cipher.encrypt_block(&mut block);
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 4c36431b691ce4533866f4d9716c21a975d0adc1..e8a5ed6390c88c01280b2a63eee5f022202b85a8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,6 +29,8 @@
 )]
 #![allow(non_camel_case_types)]
 
+#[cfg(feature = "aes256")]
+pub mod aes256;
 pub mod bases;
 pub mod hashs;
 pub mod keys;