1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Copyright (c) 2022 MASSA LABS <info@massa.net>

//! massa-cipher encryption module.
//!
//! Read `lib.rs` module documentation for more information.

use aes_gcm::aead::Aead;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use pbkdf2::password_hash::{Salt, SaltString};
use pbkdf2::{password_hash::PasswordHasher, Pbkdf2};
use rand::{thread_rng, RngCore};

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

pub struct CipherData {
    pub salt: [u8; SALT_SIZE],
    pub nonce: [u8; NONCE_SIZE],
    pub encrypted_bytes: Vec<u8>,
}

/// Encryption function using AES-GCM cipher.
///
/// Read `lib.rs` module documentation for more information.
pub fn encrypt(password: &str, data: &[u8]) -> Result<CipherData, CipherError> {
    // generate the PBKDF2 salt
    // Re-implementation of the SaltString::generate function (allowing to control the SALT_SIZE here)
    let mut rng = thread_rng();
    let mut raw_salt = [0u8; SALT_SIZE];
    rng.fill_bytes(&mut raw_salt);
    let salt = SaltString::encode_b64(&raw_salt)
        .map_err(|e| CipherError::EncryptionError(format!("Failed to encode salt: {e:?}")))?;

    // compute PBKDF2 password hash
    let password_hash = Pbkdf2
        .hash_password_customized(
            password.as_bytes(),
            None,
            None,
            HASH_PARAMS,
            Salt::from(&salt),
        )
        .map_err(|e| CipherError::EncryptionError(e.to_string()))?
        .hash
        .expect("content is missing after a successful hash");

    // generate the AES-GCM nonce
    let mut nonce_bytes = [0u8; NONCE_SIZE];
    thread_rng().fill_bytes(&mut nonce_bytes);
    let nonce = Nonce::from_slice(&nonce_bytes);

    // encrypt the data
    let cipher = Aes256Gcm::new_from_slice(password_hash.as_bytes()).expect("invalid key length");
    let encrypted_bytes = cipher
        .encrypt(nonce, data.as_ref())
        .map_err(|e| CipherError::EncryptionError(e.to_string()))?;

    // build the encryption result
    let result = CipherData {
        salt: raw_salt,
        nonce: nonce_bytes,
        encrypted_bytes,
    };
    Ok(result)
}