Skip to content

Commit

Permalink
Add PBKDF2 key derivation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Eitu33 committed Jun 30, 2022
1 parent 6b2836a commit 420b9b4
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 26 deletions.
3 changes: 2 additions & 1 deletion massa-cipher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ serde_json = "1.0"
serde_qs = "0.9"
thiserror = "1.0"
aes-gcm-siv = "0.10"
pbkdf2 = "0.10"
rand = "0.8"
rand_core = { version = "0.6", features = ["std"] }

# custom modules
massa_hash = { path = "../massa-hash" }
massa_models = { path = "../massa-models" }
massa_signature = { path = "../massa-signature" }
7 changes: 6 additions & 1 deletion massa-cipher/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// Copyright (c) 2022 MASSA LABS <[email protected]>

/// Nonce size
/// AES-GCM-SIV nonce size.
///
/// Read `lib.rs` module documentation for more information.
pub const NONCE_SIZE: usize = 12;

/// PBKDF2 salt size.
///
/// Read `lib.rs` module documentation for more information.
pub const SALT_SIZE: usize = 22;
42 changes: 23 additions & 19 deletions massa-cipher/src/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,38 @@

use aes_gcm_siv::aead::{Aead, NewAead};
use aes_gcm_siv::{Aes256GcmSiv, Key, Nonce};
use massa_hash::Hash;
use pbkdf2::{
password_hash::{PasswordHasher, SaltString},
Pbkdf2,
};

use crate::constants::NONCE_SIZE;
use crate::constants::{NONCE_SIZE, SALT_SIZE};
use crate::error::CipherError;

/// Decryption function using AES-GCM-SIV cipher.
///
/// Read `lib.rs` module documentation for more information.
pub fn decrypt(password: &str, data: &[u8]) -> Result<Vec<u8>, CipherError> {
let cipher = Aes256GcmSiv::new(Key::from_slice(
Hash::compute_from(password.as_bytes()).to_bytes(),
));
let nonce = Nonce::from_slice(data.get(..NONCE_SIZE).ok_or_else(|| {
pub fn decrypt(password: &str, mut data: &[u8]) -> Result<Vec<u8>, CipherError> {
let salt_data = data.take(..SALT_SIZE).ok_or_else(|| {
CipherError::DecryptionError(
"wallet file truncated: salt missing or incomplete".to_string(),
)
})?;
let salt = SaltString::new(std::str::from_utf8(salt_data)?)
.map_err(|e| CipherError::DecryptionError(e.to_string()))?;
let password_hash = Pbkdf2
.hash_password(password.as_bytes(), &salt)
.map_err(|e| CipherError::DecryptionError(e.to_string()))?
.hash
.unwrap();
let cipher = Aes256GcmSiv::new(Key::from_slice(password_hash.as_bytes()));
let nonce = Nonce::from_slice(data.take(..NONCE_SIZE).ok_or_else(|| {
CipherError::DecryptionError(
"wallet file truncated: nonce missing or incomplete".to_string(),
)
})?);
let decrypted_bytes = cipher
.decrypt(
nonce,
data.get(NONCE_SIZE..).ok_or_else(|| {
CipherError::DecryptionError(
"wallet file truncated: encrypted data missing or incomplete".to_string(),
)
})?,
)
.map_err(|_| {
CipherError::DecryptionError("wrong password or corrupted data".to_string())
})?;
let decrypted_bytes = cipher.decrypt(nonce, data).map_err(|_| {
CipherError::DecryptionError("wrong password or corrupted data".to_string())
})?;
Ok(decrypted_bytes)
}
19 changes: 14 additions & 5 deletions massa-cipher/src/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

use aes_gcm_siv::aead::{Aead, NewAead};
use aes_gcm_siv::{Aes256GcmSiv, Key, Nonce};
use massa_hash::Hash;
use pbkdf2::{
password_hash::{PasswordHasher, SaltString},
Pbkdf2,
};
use rand::{thread_rng, RngCore};
use rand_core::OsRng;

use crate::constants::NONCE_SIZE;
use crate::error::CipherError;
Expand All @@ -12,16 +16,21 @@ use crate::error::CipherError;
///
/// Read `lib.rs` module documentation for more information.
pub fn encrypt(password: &str, data: &[u8]) -> Result<Vec<u8>, CipherError> {
let cipher = Aes256GcmSiv::new(Key::from_slice(
Hash::compute_from(password.as_bytes()).to_bytes(),
));
let salt = SaltString::generate(&mut OsRng);
let password_hash = Pbkdf2
.hash_password(password.as_bytes(), &salt)
.map_err(|e| CipherError::EncryptionError(e.to_string()))?
.hash
.unwrap();
let cipher = Aes256GcmSiv::new(Key::from_slice(password_hash.as_bytes()));
let mut nonce_bytes = [0u8; NONCE_SIZE];
thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let encrypted_bytes = cipher
.encrypt(nonce, data.as_ref())
.map_err(|e| CipherError::EncryptionError(e.to_string()))?;
let mut content = nonce_bytes.to_vec();
let mut content = salt.as_bytes().to_vec();
content.extend(nonce_bytes);
content.extend(encrypted_bytes);
Ok(content)
}
2 changes: 2 additions & 0 deletions massa-cipher/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ pub enum CipherError {
EncryptionError(String),
/// Decryption error: {0}
DecryptionError(String),
/// Utf8 error: {0}
Utf8Error(#[from] std::str::Utf8Error),
}
4 changes: 4 additions & 0 deletions massa-cipher/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@
//! A nonce is a single-use value which enables securely encrypting multiple messages under the same key.
//! Nonces need not be random: a counter can be used, so long as the values are never repeated under the same key.
//!
//! To hash the password before using it as a cipher key, we use the PBKDF2 key derivation function as specified in [RFC 2898](https://datatracker.ietf.org/doc/html/rfc2898).
//!
//! No complete security audits of the crate we use has been performed.
//! But some of this crate's dependencies were audited by by NCC Group as part of an audit of the AES-GCM crate
#![feature(slice_take)]

mod constants;
mod decrypt;
mod encrypt;
Expand Down

0 comments on commit 420b9b4

Please sign in to comment.