whisky_wallet/encryption/
cipher.rs

1use aes_gcm::{aead::AeadMut, Aes256Gcm, KeyInit, Nonce}; // AES-GCM encryption
2use base64::{engine::general_purpose, Engine as _};
3use pbkdf2::pbkdf2_hmac; // PBKDF2 key derivation
4use rand::RngCore; // Random number generation
5use serde_json::json;
6use sha2::Sha256;
7use whisky_common::WError; // New base64 encoding
8
9const IV_LENGTH: usize = 16;
10
11pub fn encrypt_with_cipher(
12    data: &str,
13    key: &str,
14    initialization_vector_size: Option<usize>,
15) -> Result<String, WError> {
16    // Validate the initialization vector size
17    let initialization_vector_size = initialization_vector_size.unwrap_or(IV_LENGTH);
18
19    // Derive a cryptographic key from the input key using PBKDF2 and SHA-256
20    let salt = vec![0u8; initialization_vector_size]; // Using a fixed salt (empty for simplicity)
21    let mut derived_key = vec![0u8; 32]; // AES-256 requires a 256-bit key (32 bytes)
22
23    // PBKDF2 key derivation (HMAC-SHA-256)
24    pbkdf2_hmac::<Sha256>(key.as_bytes(), &salt, 100_000, &mut derived_key);
25
26    // Initialize AES-GCM cipher
27    let mut cipher = Aes256Gcm::new_from_slice(&derived_key).map_err(WError::from_err(
28        "encrypt_with_cipher - Aes256Gcm::new_from_slice",
29    ))?;
30
31    // Generate a random IV
32    let mut iv = vec![0u8; initialization_vector_size];
33    rand::thread_rng().fill_bytes(&mut iv);
34    let nonce = Nonce::from_slice(&iv); // AES-GCM requires a 12-byte nonce
35
36    // Encrypt the data
37    let ciphertext = cipher
38        .encrypt(nonce, data.as_bytes())
39        .map_err(WError::from_err("encrypt_with_cipher - cipher.encrypt"))?;
40
41    // Return the encrypted data as a JSON-like string (base64 encoding)
42    let iv_base64 = general_purpose::STANDARD.encode(&iv); // Use Engine for encoding
43    let ciphertext_base64 = general_purpose::STANDARD.encode(&ciphertext); // Use Engine for encoding
44
45    let result = json!({
46        "iv": iv_base64,
47        "ciphertext": ciphertext_base64,
48    });
49
50    Ok(result.to_string())
51}
52
53pub fn decrypt_with_cipher(encrypted_data_json: &str, key: &str) -> Result<String, WError> {
54    // Parse the encrypted data from JSON
55    let encrypted_data: serde_json::Value = serde_json::from_str(encrypted_data_json).map_err(
56        WError::from_err("decrypt_with_cipher - JSON parsing failed"),
57    )?;
58
59    let iv_base64 = encrypted_data["iv"]
60        .as_str()
61        .ok_or_else(WError::from_opt("decrypt_with_cipher", "Missing IV"))?;
62    let ciphertext_base64 = encrypted_data["ciphertext"]
63        .as_str()
64        .ok_or_else(WError::from_opt(
65            "decrypt_with_cipher",
66            "Missing ciphertext",
67        ))?;
68
69    // Decode the IV and ciphertext from base64
70    let iv = general_purpose::STANDARD
71        .decode(iv_base64)
72        .map_err(WError::from_err(
73            "decrypt_with_cipher - Base64 decode of IV failed",
74        ))?;
75    let ciphertext = general_purpose::STANDARD
76        .decode(ciphertext_base64)
77        .map_err(WError::from_err(
78            "decrypt_with_cipher - Base64 decode of ciphertext failed",
79        ))?;
80
81    // Derive a cryptographic key from the input key using PBKDF2 and SHA-256
82    let salt = vec![0u8; iv.len()]; // Using the same length as the IV
83    let mut derived_key = vec![0u8; 32]; // AES-256 requires a 256-bit key (32 bytes)
84
85    // PBKDF2 key derivation (HMAC-SHA-256)
86    pbkdf2_hmac::<Sha256>(key.as_bytes(), &salt, 100_000, &mut derived_key);
87
88    // Initialize AES-GCM cipher for decryption
89    let mut cipher = Aes256Gcm::new_from_slice(&derived_key).map_err(WError::from_err(
90        "decrypt_with_cipher - Aes256Gcm::new_from_slice",
91    ))?;
92
93    // Create a nonce from the IV
94    let nonce = Nonce::from_slice(&iv);
95
96    // Decrypt the data
97    let decrypted_data = cipher
98        .decrypt(nonce, ciphertext.as_ref())
99        .map_err(WError::from_err("decrypt_with_cipher - Decryption failed"))?;
100
101    // Convert the decrypted data back to a string
102    let decrypted_str = String::from_utf8(decrypted_data).map_err(WError::from_err(
103        "decrypt_with_cipher - Failed to convert decrypted data to UTF-8",
104    ))?;
105
106    Ok(decrypted_str)
107}