diff --git a/Cargo.lock b/Cargo.lock
index 5b071e7547513ba25f339c3c80c3ea962264e89e..2070f461a5bc3eedf1eb52b934f7ab464356b699 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -38,6 +38,12 @@ version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
 
+[[package]]
+name = "arrayvec"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
+
 [[package]]
 name = "base64"
 version = "0.11.0"
@@ -144,6 +150,7 @@ name = "dup-crypto"
 version = "0.8.0"
 dependencies = [
  "aes",
+ "arrayvec",
  "base64",
  "bincode",
  "bs58",
diff --git a/Cargo.toml b/Cargo.toml
index fdad2fef16838d265192b173e9ada262992df257..877d1f3d4bc08b711a6ef3fe0c69ca971b7f5d3c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ path = "src/lib.rs"
 
 [dependencies]
 aes = { version = "0.3.2", optional = true }
+arrayvec = { version = "0.5.1", features = ["array-sizes-33-128", "array-sizes-129-255"], optional = true }
 base64 = "0.11.0"
 bs58 = "0.3.0"
 byteorder = "1.3.2"
@@ -28,6 +29,8 @@ zeroize = { version = "1.1.0", features = ["zeroize_derive"] }
 bincode = "1.2.0"
 
 [features]
-default = ["ser"]
+default = ["dewip", "ser"]
+
 aes256 = ["aes"]
+dewip = ["aes256", "arrayvec"]
 ser = ["serde"]
diff --git a/src/dewip.rs b/src/dewip.rs
new file mode 100644
index 0000000000000000000000000000000000000000..74babd59b015604f993ba8f0cdf9771cecc1c631
--- /dev/null
+++ b/src/dewip.rs
@@ -0,0 +1,204 @@
+//  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/>.
+
+//! Handle [DEWIP](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) format
+//!
+//! # Write ed25519 key-pair in DEWIF file
+//!
+//! ```
+//! use dup_crypto::dewip::write_dewif_v1_content;
+//! use dup_crypto::keys::ed25519::{KeyPairFromSaltedPasswordGenerator, SaltedPassword};
+//!
+//! // Get user credentials (from cli prompt or gui)
+//! let credentials = SaltedPassword::new("user salt".to_owned(), "user password".to_owned());
+//!
+//! // Generate ed25519 keypair
+//! let keypair = KeyPairFromSaltedPasswordGenerator::with_default_parameters().generate(credentials);
+//!
+//! // Get user passphrase for DEWIF encryption
+//! let encryption_passphrase = "toto titi tata";
+//!
+//! // Serialize keypair in DEWIF format
+//! let dewif_content = write_dewif_v1_content(&keypair, encryption_passphrase);
+//!
+//! assert_eq!(
+//!     "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=",
+//!     dewif_content
+//! )
+//! ```
+//!
+//! # Read DEWIF file
+//!
+//! ```
+//! use dup_crypto::dewip::read_dewip_file_content;
+//! use dup_crypto::keys::{KeyPair, Signator};
+//!
+//! // Get DEWIP file content (Usually from disk)
+//! let dewip_file_content = "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=";
+//!
+//! // Get user passphrase for DEWIF decryption (from cli prompt or gui)
+//! let encryption_passphrase = "toto titi tata";
+//!
+//! // Read DEWIP file content
+//! // If the file content is correct, we get a key-pair iterator.
+//! let mut key_pair_iter = read_dewip_file_content(dewip_file_content, encryption_passphrase)
+//!     .expect("invalid DEWIF file.")
+//!     .into_iter();
+//!
+//! // Get first key-pair
+//! let key_pair = key_pair_iter
+//!     .next()
+//!     .expect("DEWIF file must contain at least one keypair");
+//!
+//! assert_eq!(
+//!     "2cC9FrvRiN3uHHcd8S7wuureDS8CAmD5y4afEgSCLHtU",
+//!     &key_pair.public_key().to_string()
+//! );
+//!
+//! // Generate signator
+//! // `Signator` is a non-copiable and non-clonable type,
+//! // so only generate it when you are in the scope where you effectively sign.
+//! let signator = key_pair.generate_signator();
+//!
+//! // Sign a message with keypair
+//! let sig = signator.sign(b"message");
+//!
+//! assert_eq!(
+//!     "nCWl7jtCa/nCMKKnk2NJN7daVxd/ER+e1wsFbofdh/pUvDuHxFaa7S5eUMGiqPTJ4uJQOvrmF/BOfOsYIoI2Bg==",
+//!     &sig.to_string()
+//! )
+//! ```
+//!
+
+mod read;
+mod write;
+
+pub use read::{read_dewip_file_content, DewipReadError};
+pub use write::{write_dewif_v1_content, write_dewif_v2_content};
+
+use crate::hashs::Hash;
+use crate::seeds::Seed32;
+use arrayvec::ArrayVec;
+use unwrap::unwrap;
+
+const VERSION_BYTES: usize = 4;
+
+// v1
+static VERSION_V1: &[u8] = &[0, 0, 0, 1];
+const V1_BYTES_LEN: usize = 68;
+const V1_ENCRYPTED_BYTES_LEN: usize = 64;
+const V1_AES_BLOCKS_COUNT: usize = 4;
+
+// v2
+static VERSION_V2: &[u8] = &[0, 0, 0, 2];
+const V2_BYTES_LEN: usize = 132;
+const V2_ENCRYPTED_BYTES_LEN: usize = 128;
+
+fn gen_aes_seed(passphrase: &str) -> Seed32 {
+    let mut salt = ArrayVec::<[u8; 37]>::new();
+    unwrap!(salt.try_extend_from_slice(b"dewif"));
+    let hash = Hash::compute(passphrase.as_bytes());
+    unwrap!(salt.try_extend_from_slice(hash.as_ref()));
+
+    let mut aes_seed_bytes = [0u8; 32];
+    scrypt::scrypt(
+        passphrase.as_bytes(),
+        salt.as_ref(),
+        &scrypt::ScryptParams::new(12, 16, 1).expect("dev error: invalid scrypt params"),
+        &mut aes_seed_bytes,
+    )
+    .expect("dev error: invalid seed len");
+    Seed32::new(aes_seed_bytes)
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::keys::ed25519::KeyPairFromSeed32Generator;
+    use crate::keys::KeyPairEnum;
+    use crate::seeds::Seed32;
+
+    #[test]
+    fn dewip_v1() {
+        let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
+
+        let dewif_content = write_dewif_v1_content(&written_keypair, "toto");
+
+        let mut keypairs_iter = read_dewip_file_content(&dewif_content, "toto")
+            .expect("dewip content must be readed successfully")
+            .into_iter();
+        let keypair_read = keypairs_iter.next().expect("Must read one keypair");
+
+        assert_eq!(KeyPairEnum::Ed25519(written_keypair), keypair_read,)
+    }
+
+    #[test]
+    fn dewip_v1_corrupted() -> Result<(), ()> {
+        let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
+
+        let mut dewif_content = write_dewif_v1_content(&written_keypair, "toto");
+
+        // Corrupt one byte in dewif_content
+        let dewif_bytes_mut = unsafe { dewif_content.as_bytes_mut() };
+        dewif_bytes_mut[13] = 0x52;
+
+        if let Err(DewipReadError::CorruptedContent) =
+            read_dewip_file_content(&dewif_content, "toto")
+        {
+            Ok(())
+        } else {
+            panic!("dewif content must be corrupted.")
+        }
+    }
+
+    #[test]
+    fn dewip_v2() {
+        let written_keypair1 = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
+        let written_keypair2 = KeyPairFromSeed32Generator::generate(Seed32::new([1u8; 32]));
+
+        let dewif_content = write_dewif_v2_content(&written_keypair1, &written_keypair2, "toto");
+
+        let mut keypairs_iter = read_dewip_file_content(&dewif_content, "toto")
+            .expect("dewip content must be readed successfully")
+            .into_iter();
+        let keypair1_read = keypairs_iter.next().expect("Must read one keypair");
+        let keypair2_read = keypairs_iter.next().expect("Must read one keypair");
+
+        assert_eq!(KeyPairEnum::Ed25519(written_keypair1), keypair1_read,);
+        assert_eq!(KeyPairEnum::Ed25519(written_keypair2), keypair2_read,);
+    }
+
+    #[test]
+    fn dewip_v2_corrupted() -> Result<(), ()> {
+        let written_keypair1 = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
+        let written_keypair2 = KeyPairFromSeed32Generator::generate(Seed32::new([1u8; 32]));
+
+        let mut dewif_content =
+            write_dewif_v2_content(&written_keypair1, &written_keypair2, "toto");
+
+        // Corrupt one byte in dewif_content
+        let dewif_bytes_mut = unsafe { dewif_content.as_bytes_mut() };
+        dewif_bytes_mut[13] = 0x52;
+
+        if let Err(DewipReadError::CorruptedContent) =
+            read_dewip_file_content(&dewif_content, "toto")
+        {
+            Ok(())
+        } else {
+            panic!("dewif content must be corrupted.")
+        }
+    }
+}
diff --git a/src/dewip/read.rs b/src/dewip/read.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6f190a67bad62134f351c6c1067a5f0ad903c2b2
--- /dev/null
+++ b/src/dewip/read.rs
@@ -0,0 +1,202 @@
+//  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/>.
+
+//! Read [DEWIP](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content
+
+use crate::keys::ed25519::{KeyPairFromSeed32Generator, PublicKey, PUBKEY_SIZE_IN_BYTES};
+use crate::keys::KeyPairEnum;
+use crate::seeds::Seed32;
+use arrayvec::ArrayVec;
+use byteorder::ByteOrder;
+use std::convert::{TryFrom, TryInto};
+use thiserror::Error;
+
+const MAX_KEYPAIRS_COUNT: usize = 2;
+
+/// Error when try to read DEWIP file content
+#[derive(Clone, Debug, Error)]
+pub enum DewipReadError {
+    /// DEWIP file content is corrupted
+    #[error("DEWIP file content is corrupted")]
+    CorruptedContent,
+    /// Invalid base 64 string
+    #[error("Invalid base 64 string: {0}")]
+    InvalidBase64Str(base64::DecodeError),
+    /// Invalid format
+    #[error("Invalid format")]
+    InvalidFormat,
+    /// Too short content
+    #[error("Too short content")]
+    TooShortContent,
+    /// Too long content
+    #[error("Too long content")]
+    TooLongContent,
+    /// Unsupported version
+    #[error("Version {actual:?} is not supported. Supported versions: [1, 2].")]
+    UnsupportedVersion {
+        /// Actual version
+        actual: u32,
+    },
+}
+
+/// read dewip file content with user passphrase
+pub fn read_dewip_file_content(
+    file_content: &str,
+    passphrase: &str,
+) -> Result<impl IntoIterator<Item = KeyPairEnum>, DewipReadError> {
+    let mut bytes = base64::decode(file_content).map_err(DewipReadError::InvalidBase64Str)?;
+
+    if bytes.len() < 4 {
+        return Err(DewipReadError::TooShortContent);
+    }
+
+    let version = byteorder::BigEndian::read_u32(&bytes[0..4]);
+
+    match version {
+        1 => Ok({
+            let mut array_keypairs = ArrayVec::new();
+            array_keypairs.push(read_dewip_v1(&mut bytes[4..], passphrase)?);
+            array_keypairs
+        }),
+        2 => read_dewip_v2(&mut bytes[4..], passphrase),
+        other_version => Err(DewipReadError::UnsupportedVersion {
+            actual: other_version,
+        }),
+    }
+}
+
+fn read_dewip_v1(bytes: &mut [u8], passphrase: &str) -> Result<KeyPairEnum, DewipReadError> {
+    match bytes.len() {
+        len if len < super::V1_ENCRYPTED_BYTES_LEN => return Err(DewipReadError::TooShortContent),
+        len if len > super::V1_ENCRYPTED_BYTES_LEN => return Err(DewipReadError::TooLongContent),
+        _ => (),
+    }
+
+    // Decrypt bytes
+    let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
+    crate::aes256::decrypt::decrypt_n_blocks(&cipher, bytes, super::V1_AES_BLOCKS_COUNT);
+
+    // Get checked keypair
+    bytes_to_checked_keypair(bytes)
+}
+
+fn read_dewip_v2(
+    bytes: &mut [u8],
+    passphrase: &str,
+) -> Result<ArrayVec<[KeyPairEnum; MAX_KEYPAIRS_COUNT]>, DewipReadError> {
+    let mut array_keypairs = ArrayVec::new();
+
+    match bytes.len() {
+        len if len < super::V2_ENCRYPTED_BYTES_LEN => return Err(DewipReadError::TooShortContent),
+        len if len > super::V2_ENCRYPTED_BYTES_LEN => return Err(DewipReadError::TooLongContent),
+        _ => (),
+    }
+
+    // Decrypt bytes
+    let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
+    crate::aes256::decrypt::decrypt_8_blocks(&cipher, bytes);
+
+    array_keypairs.push(bytes_to_checked_keypair(&bytes[..64])?);
+    array_keypairs.push(bytes_to_checked_keypair(&bytes[64..])?);
+
+    Ok(array_keypairs)
+}
+
+fn bytes_to_checked_keypair(bytes: &[u8]) -> Result<KeyPairEnum, DewipReadError> {
+    // Wrap bytes into Seed32 and PublicKey
+    let seed = Seed32::new(
+        (&bytes[..PUBKEY_SIZE_IN_BYTES])
+            .try_into()
+            .expect("dev error"),
+    );
+    let expected_pubkey = PublicKey::try_from(&bytes[PUBKEY_SIZE_IN_BYTES..]).expect("dev error");
+
+    // Get keypair
+    let keypair = KeyPairFromSeed32Generator::generate(seed);
+
+    // Check pubkey
+    if keypair.pubkey() != expected_pubkey {
+        Err(DewipReadError::CorruptedContent)
+    } else {
+        Ok(KeyPairEnum::Ed25519(keypair))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn read_unsupported_version() -> Result<(), ()> {
+        if let Err(DewipReadError::UnsupportedVersion { .. }) = read_dewip_file_content(
+            "ABAAAfKjMzOFfhwgypF3mAx0QDXyozMzhX4cIMqRd5gMdEA1WZwQjCR49iZDK2QhYfdTbPz9AGB01edt4iRSzdTp3c4=",
+            "toto"
+        ) {
+            Ok(())
+        } else {
+            panic!("Read must be fail with error UnsupportedVersion.")
+        }
+    }
+
+    #[test]
+    fn read_too_short_content() -> Result<(), ()> {
+        if let Err(DewipReadError::TooShortContent) = read_dewip_file_content("AAA", "toto") {
+            Ok(())
+        } else {
+            panic!("Read must be fail with error TooShortContent.")
+        }
+    }
+
+    #[test]
+    fn tmp() {
+        use crate::keys::{KeyPair, Signator};
+
+        // Get DEWIP file content (Usually from disk)
+        let dewip_file_content = "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=";
+
+        // Get user passphrase for DEWIF decryption (from cli prompt or gui)
+        let encryption_passphrase = "toto titi tata";
+
+        // Read DEWIP file content
+        // If the file content is correct, we get a key-pair iterator.
+        let mut key_pair_iter = read_dewip_file_content(dewip_file_content, encryption_passphrase)
+            .expect("invalid DEWIF file.")
+            .into_iter();
+
+        // Get first key-pair
+        let key_pair = key_pair_iter
+            .next()
+            .expect("DEWIF file must contain at least one keypair");
+
+        assert_eq!(
+            "2cC9FrvRiN3uHHcd8S7wuureDS8CAmD5y4afEgSCLHtU",
+            &key_pair.public_key().to_string()
+        );
+
+        // Generate signator
+        // `Signator` is a non-copiable and non-clonable type,
+        // so only generate it when you are in the scope where you effectively sign.
+        let signator = key_pair.generate_signator();
+
+        // Sign a message with keypair
+        let sig = signator.sign(b"message");
+
+        assert_eq!(
+            "nCWl7jtCa/nCMKKnk2NJN7daVxd/ER+e1wsFbofdh/pUvDuHxFaa7S5eUMGiqPTJ4uJQOvrmF/BOfOsYIoI2Bg==",
+            &sig.to_string()
+        )
+    }
+}
diff --git a/src/dewip/write.rs b/src/dewip/write.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4ce1408021bb602b4ea21382a50100f9ebf16ae4
--- /dev/null
+++ b/src/dewip/write.rs
@@ -0,0 +1,72 @@
+//  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/>.
+
+//! Write [DEWIP](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content
+
+use crate::keys::ed25519::Ed25519KeyPair;
+use arrayvec::ArrayVec;
+use unwrap::unwrap;
+
+/// Write dewip v1 file content with user passphrase
+pub fn write_dewif_v1_content(keypair: &Ed25519KeyPair, passphrase: &str) -> String {
+    let mut bytes = ArrayVec::<[u8; super::V1_BYTES_LEN]>::new();
+    unwrap!(bytes.try_extend_from_slice(super::VERSION_V1));
+    unwrap!(bytes.try_extend_from_slice(keypair.seed().as_ref()));
+    unwrap!(bytes.try_extend_from_slice(keypair.pubkey().as_ref()));
+
+    let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
+    crate::aes256::encrypt::encrypt_n_blocks(&cipher, &mut bytes[4..], super::V1_AES_BLOCKS_COUNT);
+
+    base64::encode(bytes.as_ref())
+}
+
+/// Write dewip v2 file content with user passphrase
+pub fn write_dewif_v2_content(
+    keypair1: &Ed25519KeyPair,
+    keypair2: &Ed25519KeyPair,
+    passphrase: &str,
+) -> String {
+    let mut bytes = ArrayVec::<[u8; super::V2_BYTES_LEN]>::new();
+    unwrap!(bytes.try_extend_from_slice(super::VERSION_V2));
+    unwrap!(bytes.try_extend_from_slice(keypair1.seed().as_ref()));
+    unwrap!(bytes.try_extend_from_slice(keypair1.pubkey().as_ref()));
+    unwrap!(bytes.try_extend_from_slice(keypair2.seed().as_ref()));
+    unwrap!(bytes.try_extend_from_slice(keypair2.pubkey().as_ref()));
+
+    let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
+    crate::aes256::encrypt::encrypt_8_blocks(&cipher, &mut bytes[super::VERSION_BYTES..]);
+
+    base64::encode(bytes.as_ref())
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::keys::ed25519::KeyPairFromSeed32Generator;
+    use crate::seeds::Seed32;
+
+    #[test]
+    fn write_dewif_v1() {
+        let keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
+
+        let dewif_content = write_dewif_v1_content(&keypair, "toto");
+        println!("{}", dewif_content);
+        assert_eq!(
+            "AAAAAb30ng3kI9QGMbR7TYCqPhS99J4N5CPUBjG0e02Aqj4UElionaHOt0kv+eaWgGSGkrP1LQfuwivuvg7+9n0gd18=",
+            dewif_content
+        )
+    }
+}
diff --git a/src/keys/ed25519.rs b/src/keys/ed25519.rs
index f5018fbaa2d3499bea703de69466bc8f1487b5b9..c2697484d3b4e660e2959aa20ed3eef8d185ad92 100644
--- a/src/keys/ed25519.rs
+++ b/src/keys/ed25519.rs
@@ -286,6 +286,9 @@ impl Ed25519KeyPair {
     pub fn pubkey(&self) -> PublicKey {
         self.pubkey
     }
+    pub(crate) fn seed(&self) -> &Seed32 {
+        &self.seed
+    }
 }
 
 impl Display for Ed25519KeyPair {
diff --git a/src/lib.rs b/src/lib.rs
index 6bed05f234db5e19fbc16b4f78b37aaa0c5fed49..f1204d24c6f172e899d5aa57d2e31e2093955d8e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -22,7 +22,6 @@
     missing_copy_implementations,
     trivial_casts,
     trivial_numeric_casts,
-    unsafe_code,
     unstable_features,
     unused_import_braces,
     unused_qualifications
@@ -32,6 +31,8 @@
 #[cfg(feature = "aes256")]
 pub mod aes256;
 pub mod bases;
+#[cfg(feature = "dewip")]
+pub mod dewip;
 pub mod hashs;
 pub mod keys;
 pub mod rand;