From 19eb1b66ab5ef7d93deeb79cf37c8fcbd02b39ec Mon Sep 17 00:00:00 2001 From: Sosthene00 <674694@protonmail.ch> Date: Mon, 25 Mar 2024 23:02:04 +0100 Subject: [PATCH] encryption refactoring --- crates/sp_client/src/aesgcm.rs | 295 ++++++++++++++++----------------- crates/sp_client/src/user.rs | 118 ++++++------- 2 files changed, 195 insertions(+), 218 deletions(-) diff --git a/crates/sp_client/src/aesgcm.rs b/crates/sp_client/src/aesgcm.rs index 20882cf..e0e2b13 100644 --- a/crates/sp_client/src/aesgcm.rs +++ b/crates/sp_client/src/aesgcm.rs @@ -1,173 +1,168 @@ -/* This module is temporary. We'll use the module described in key_encription -module defined in sdk_common repository! Some of the methods there were copied here. -*/ -use core::result::Result as CoreResult; -use rand::RngCore; +use std::collections::HashMap; + +use anyhow::{Error, Result}; +use sp_backend::{ + bitcoin::{ + consensus::serde::hex, + hex::DisplayHex, + key::constants::SECRET_KEY_SIZE, + secp256k1::{ecdh::SharedSecret, SecretKey}, + Txid, + }, + silentpayments::sending::SilentPaymentAddress, +}; use wasm_bindgen::JsValue; -use web_sys::console; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -use aes::cipher::consts::U32; use aes::cipher::generic_array::GenericArray; -use aes_gcm::{ - aead::{AeadInPlace, KeyInit}, - Aes256Gcm, +use aes::{ + cipher::consts::{U32, U8}, + Aes256, }; -use hex; -use hex::FromHexError; -use rand::rngs::OsRng; +use aes_gcm::{ + aead::{Aead, AeadInPlace, KeyInit, Nonce}, + AeadCore, Aes256Gcm, AesGcm, Key, TagSize, +}; +use rand::{thread_rng, RngCore}; -pub struct Aes256GcmIv96Bit { - pub key: GenericArray, +const HALFKEYSIZE: usize = SECRET_KEY_SIZE / 2; + +pub type HalfKey = [u8; HALFKEYSIZE]; + +pub enum EncryptionTarget { + Login(HalfKey), } -impl Aes256GcmIv96Bit { - pub fn new() -> Self { - let mut key_bytes = [0u8; 32]; - OsRng.fill_bytes(&mut key_bytes); - let key = GenericArray::from_slice(&key_bytes); - Aes256GcmIv96Bit { key: key.clone() } - } - - pub fn encrypt(&self, data: &[u8]) -> CoreResult, aes_gcm::Error> { - let cipher = Aes256Gcm::new(&self.key); - let mut nonce = [0u8; 12]; - OsRng.fill_bytes(&mut nonce); - - let mut buffer = data.to_vec(); - cipher.encrypt_in_place(GenericArray::from_slice(&nonce), b"", &mut buffer)?; - - Ok([nonce.to_vec(), buffer].concat()) - } - - pub fn decrypt(&self, data: &[u8]) -> CoreResult, aes_gcm::Error> { - if data.len() < 12 { - return Err(aes_gcm::Error); // Remplacer par une erreur appropriée - } - - let (nonce, encrypted_data) = data.split_at(12); - let mut buffer = encrypted_data.to_vec(); - let cipher = Aes256Gcm::new(&self.key); - cipher.decrypt_in_place(GenericArray::from_slice(nonce), b"", &mut buffer)?; - - Ok(buffer) - } - - pub fn encrypt_string(&self, data: &str) -> CoreResult { - match self.encrypt(data.as_bytes()) { - Ok(encrypted_data) => Ok(base64::encode(encrypted_data)), - Err(_) => Err("Erreur de chiffrement".to_string()), - } - } - - pub fn decrypt_string(&self, data: &str) -> CoreResult { - let decoded_data = match base64::decode(data) { - Ok(data) => data, - Err(_) => return Err("Erreur de décodage Base64".to_string()), - }; - - match self.decrypt(&decoded_data) { - Ok(decrypted_data) => match String::from_utf8(decrypted_data) { - Ok(text) => Ok(text), - Err(_) => Err("Erreur de conversion UTF-8".to_string()), - }, - Err(_) => Err("Erreur de déchiffrement".to_string()), - } - } - - pub fn export_key(&self) -> String { - base64::encode(&self.key) - } - - pub fn import_key(encoded_key: &str) -> CoreResult { - match base64::decode(encoded_key) { - Ok(decoded_key) => { - if decoded_key.len() == 32 { - let key = GenericArray::from_slice(&decoded_key); - Ok(Aes256GcmIv96Bit { key: key.clone() }) - } else { - Err("La taille de la clé n'est pas valide".to_string()) - } - } - Err(_) => Err("Échec du décodage de la clé".to_string()), - } - } +pub enum DecryptionTarget { + Login, } -#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct KeyEncryption { - pub attribute_name: Option, - pub key: Option, - pub algorithm: Option, +pub enum PlainText { + Login(HalfKey), } -impl KeyEncryption { +pub type CipherText = Vec; + +pub struct Aes256Decryption { + target: DecryptionTarget, + aes_key: [u8; 32], + nonce: [u8; 12], + cipher_text: CipherText, +} + +impl Aes256Decryption { pub fn new( - attribute_name: Option, - key: Option, - algorithm: Option, - ) -> Self { - KeyEncryption { - attribute_name, - key, - algorithm, + target: DecryptionTarget, + encrypted_aes_key: Vec, + shared_secret: SharedSecret, + cipher_text: CipherText, + ) -> Result { + if encrypted_aes_key.len() <= 12 { + return Err(Error::msg("encrypted_aes_key is shorter than nonce length")); + } + // take the first 12 bytes form encrypted_aes_key as nonce + let (decrypt_key_nonce, encrypted_key) = encrypted_aes_key.split_at(12); + // decrypt key with shared_secret obtained from transaction + let decrypt_key_cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref()) + .map_err(|e| Error::msg(format!("{}", e)))?; + let aes_key_plain = decrypt_key_cipher + .decrypt(decrypt_key_nonce.into(), encrypted_key) + .map_err(|e| Error::msg(format!("{}", e)))?; + if aes_key_plain.len() != 32 { + return Err(Error::msg("Invalid length for decrypted key")); + } + let mut aes_key = [0u8; 32]; + aes_key.copy_from_slice(&aes_key_plain); + if cipher_text.len() <= 12 { + return Err(Error::msg("cipher_text is shorter than nonce lenght")); + } + let (message_nonce, message_cipher) = cipher_text.split_at(12); + let mut nonce = [0u8; 12]; + nonce.copy_from_slice(message_nonce); + Ok(Self { + target, + aes_key, + nonce, + cipher_text: message_cipher.to_vec(), + }) + } + + pub fn decrypt_with_key(&self) -> Result { + match self.target { + DecryptionTarget::Login => self.decrypt_login() } } - pub fn encode(&self, data: String) -> CoreResult<String, String> { - if let Some(ref key) = self.key { - let decoded_key = Aes256GcmIv96Bit::import_key(key)?; - let encrypted_data = decoded_key.encrypt_string(&data)?; - Ok(encrypted_data) - } else { - Err("Aucune clé n'est définie".to_string()) + fn decrypt_login(&self) -> Result<PlainText> { + let cipher = Aes256Gcm::new(&self.aes_key.into()); + let plain = cipher + .decrypt(&self.nonce.into(), &*self.cipher_text) + .map_err(|e| Error::msg(format!("{}", e)))?; + if plain.len() != SECRET_KEY_SIZE / 2 { + return Err(Error::msg("Plain text of invalid lenght for a login")); } - } - - pub fn decode(&self, encrypted_data: String) -> CoreResult<String, String> { - if let Some(ref key) = self.key { - let decoded_key = Aes256GcmIv96Bit::import_key(key)?; - let decrypted_data = decoded_key.decrypt_string(&encrypted_data)?; - Ok(decrypted_data) - } else { - Err("Aucune clé n'est définie".to_string()) - } - } - - pub fn enc(&self, data: Value) -> String { - let data_string = serde_json::to_string(&data).unwrap_or_else(|_| "".to_string()); - self.encode(data_string).unwrap_or_else(|_| "".to_string()) - } - pub fn enc_string(&self, data: String) -> String { - self.enc(Value::String(data)) - } - pub fn enc_i64(&self, data: i64) -> String { - self.enc(Value::Number(data.into())) - } - pub fn enc_u64(&self, data: u64) -> String { - self.enc(Value::Number(data.into())) - } - pub fn enc_u32(&self, data: u32) -> String { - self.enc(Value::Number(data.into())) - } - pub fn enc_vec_string(&self, list: Vec<String>) -> String { - self.enc(Value::Array(list.into_iter().map(Value::String).collect())) - } - pub fn enc_vec_key_encryption(&self, list: Vec<KeyEncryption>) -> String { - // Utilisez `serde_json::to_value` pour convertir chaque `KeyEncryption` en `Value` - let json_list: Vec<Value> = list - .into_iter() - .map(|key_enc| serde_json::to_value(key_enc).unwrap_or_else(|_| json!({}))) - .collect(); - - self.enc(Value::Array(json_list)) + let mut key_half = [0u8; SECRET_KEY_SIZE / 2]; + key_half.copy_from_slice(&plain); + Ok(PlainText::Login(key_half)) } } -fn hex_to_generic_array(hex_string: &str) -> Result<GenericArray<u8, U32>, FromHexError> { - let byte_vec = hex::decode(hex_string)?; - let array = GenericArray::clone_from_slice(&byte_vec[..32]); - Ok(array) +pub struct Aes256Encryption { + pub target: EncryptionTarget, + aes_key: [u8; 32], + nonce: [u8; 12], + shared_secrets: HashMap<Txid, HashMap<SilentPaymentAddress, SharedSecret>>, +} + +impl Aes256Encryption { + pub fn new(target: EncryptionTarget) -> Result<Self> { + let mut rng = thread_rng(); + let aes_key: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into(); + let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into(); + Ok(Self { + target, + aes_key, + nonce, + shared_secrets: HashMap::new(), + }) + } + + pub fn set_shared_secret( + &mut self, + shared_secrets: HashMap<Txid, HashMap<SilentPaymentAddress, SharedSecret>>, + ) -> Result<()> { + unimplemented!(); + } + + pub fn import_key( + target: EncryptionTarget, + aes_key: [u8; 32], + nonce: [u8; 12], + ) -> Result<Self> { + Ok(Self { + target, + aes_key, + nonce, + shared_secrets: HashMap::new(), + }) + } + + pub fn encrypt_with_aes_key(&self) -> Result<CipherText> { + match self.target { + EncryptionTarget::Login(half_key) => self.encrypt_login(half_key), + } + } + + fn encrypt_login(&self, plaintext: HalfKey) -> Result<CipherText> { + let cipher = Aes256Gcm::new(&self.aes_key.into()); + let cipher_text = cipher + .encrypt(&self.nonce.into(), plaintext.as_slice()) + .map_err(|e| Error::msg(format!("{}", e)))?; + let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len()); + res.extend_from_slice(&self.nonce); + res.extend_from_slice(&cipher_text); + Ok(res) + } } diff --git a/crates/sp_client/src/user.rs b/crates/sp_client/src/user.rs index be1c61b..3c9e9c2 100644 --- a/crates/sp_client/src/user.rs +++ b/crates/sp_client/src/user.rs @@ -5,6 +5,7 @@ use aes_gcm::KeyInit; use aes_gcm::{aead::Buffer, Aes256Gcm, Key}; use anyhow::{Error, Result}; use bytes::Buf; +use js_sys::JsString; use rand::{self, thread_rng, Rng, RngCore}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -15,21 +16,14 @@ use sp_backend::bitcoin::secp256k1::SecretKey; use tsify::Tsify; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; -use web_sys::console; -use web_sys::js_sys::JsString; -use crate::aesgcm::{Aes256GcmIv96Bit, KeyEncryption}; -// use crate::secretdata::SecretData; use bytes::Bytes; -use hex; -use sha2::{Digest, Sha256}; use shamir::SecretData; use std::fs::File; use std::io::Read; use std::io::Write; use std::str::FromStr; -use crate::api::{generate_sp_wallet, generate_sp_wallet_return}; use sp_backend::bitcoin::secp256k1::constants::SECRET_KEY_SIZE; use sp_backend::silentpayments::bitcoin_hashes::sha256; use sp_backend::silentpayments::sending::SilentPaymentAddress; @@ -39,10 +33,8 @@ use sp_backend::spclient::{OutputList, SpClient}; use img_parts::jpeg::Jpeg; use img_parts::{ImageEXIF, ImageICC}; -use scrypt::{ - password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, - Scrypt, -}; +use crate::aesgcm::HalfKey; +use crate::aesgcm::{Aes256Encryption, EncryptionTarget}; //extern crate shamir; //use shamir::SecretData; @@ -66,6 +58,8 @@ impl User { recover_image: &[u8], revoke_image: &[u8], ) -> Result<Self> { + let mut rng = thread_rng(); + // image revoke // We just take the 2 revoke keys and put it in the revoke_img file let mut revoke_data = [0u8; SECRET_KEY_SIZE * 2]; @@ -76,20 +70,18 @@ impl User { // split recover spend key let (part1_key, part2_key) = recover_spend_key.as_ref().split_at(SECRET_KEY_SIZE / 2); - let mut recover_data = Vec::<u8>::with_capacity(88); + let mut recover_data = Vec::<u8>::with_capacity(64); // 32 * 2 // generate 2 tokens of 32B entropy - let mut entropy_1 = [0u8; 32]; - entropy_1.copy_from_slice(&generate_random_key(32)); - let mut entropy_2 = [0u8; 32]; - entropy_2.copy_from_slice(&generate_random_key(32)); + let mut entropy_1: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into(); + let mut entropy_2: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into(); recover_data.extend_from_slice(&entropy_1); recover_data.extend_from_slice(&entropy_2); - // convert the password in a Vec<u8> + // convert the password in a String, i.e. a Vec<u8> // Be careful of javascript strings: https://rustwasm.github.io/wasm-bindgen/reference/types/str.html#utf-16-vs-utf-8 - assert!(user_password.is_valid_utf16()); + assert!(user_password.is_valid_utf16()); // we can think better than panicking in this case let password: String = user_password.into(); // hash the concatenation @@ -99,20 +91,17 @@ impl User { let hash1 = sha256::Hash::from_engine(engine); // take it as a AES key - let key1: &Key<Aes256Gcm> = hash1.as_byte_array().try_into()?; - let cipher = Aes256Gcm::new(&key1); + let part1_encryption = Aes256Encryption::import_key( + EncryptionTarget::Login(part1_key.try_into()?), + hash1.to_byte_array(), + Aes256Gcm::generate_nonce(&mut rng).into(), + )?; // encrypt the part1 of the key - let nonce1 = Aes256Gcm::generate_nonce(&mut thread_rng()); - let nonce2 = Aes256Gcm::generate_nonce(&mut thread_rng()); - let cipher_recover_part1 = cipher - .encrypt(&nonce1, part1_key) - .map_err(|e| anyhow::Error::msg(format!("{}", e)))?; + let cipher_recover_part1 = part1_encryption.encrypt_with_aes_key()?; log::debug!("cipher_part1 length: {}", cipher_recover_part1.len()); - recover_data.extend_from_slice(&nonce1); - recover_data.extend_from_slice(&nonce2); recover_data.extend_from_slice(&cipher_recover_part1); //image recover @@ -125,13 +114,14 @@ impl User { let hash2 = sha256::Hash::from_engine(engine); // take it as a AES key - let key2: &Key<Aes256Gcm> = hash2.as_byte_array().try_into()?; - let cipher = Aes256Gcm::new(&key2); + let part2_encryption = Aes256Encryption::import_key( + EncryptionTarget::Login(part2_key.try_into()?), + hash2.to_byte_array(), + Aes256Gcm::generate_nonce(&mut rng).into(), + )?; // encrypt the part2 of the key - let cipher_recover_part2 = cipher - .encrypt(&nonce2, part2_key) - .map_err(|e| anyhow::Error::msg(format!("{}", e)))?; + let cipher_recover_part2 = part2_encryption.encrypt_with_aes_key()?; //create shardings let sharding = Sharding::new(&cipher_recover_part2.to_lower_hex_string(), 10u8); //nMembers = 10 for testing, need to recover nmember elsewhere @@ -341,14 +331,6 @@ impl Sharding { } } -//associated functions -pub fn generate_random_key(length: usize) -> Vec<u8> { - let mut rng = rand::thread_rng(); - let mut entropy = Vec::<u8>::with_capacity(length); - rng.fill_bytes(&mut entropy); - entropy -} - pub fn write_exif(image_to_recover: &[u8], data: &[u8]) -> Result<Vec<u8>> { let mut jpeg = Jpeg::from_bytes(Bytes::from(image_to_recover.to_vec()))?; let data_bytes = Bytes::from(data.to_owned()); @@ -373,32 +355,32 @@ pub fn read_exif(image: &[u8]) -> Result<Vec<u8>, String> { Ok(exif_bytes.to_vec()) } -//change for return Result? -pub fn from_hex_to_b58(hex_string: &str) -> String { - let decoded_data = hex::decode(hex_string).expect("Failed to decode hex string"); - let base58_string = bs58::encode(decoded_data).into_string(); - base58_string -} -//change for return Result? -pub fn from_b58_to_hex(base58_string: &str) -> String { - let decoded_data = bs58::decode(base58_string.to_owned()).into_vec().unwrap(); - let hex_string = decoded_data - .iter() - .map(|b| format!("{:02x}", b)) - .collect::<String>(); - hex_string -} +// //change for return Result? +// pub fn from_hex_to_b58(hex_string: &str) -> String { +// let decoded_data = hex::decode(hex_string).expect("Failed to decode hex string"); +// let base58_string = bs58::encode(decoded_data).into_string(); +// base58_string +// } +// //change for return Result? +// pub fn from_b58_to_hex(base58_string: &str) -> String { +// let decoded_data = bs58::decode(base58_string.to_owned()).into_vec().unwrap(); +// let hex_string = decoded_data +// .iter() +// .map(|b| format!("{:02x}", b)) +// .collect::<String>(); +// hex_string +// } -fn from_b64_to_hex(base64_string: &str) -> String { - let decoded_data = base64::decode(base64_string).unwrap(); - let hex_string = decoded_data - .iter() - .map(|b| format!("{:02x}", b)) - .collect::<String>(); - hex_string -} -fn from_hex_to_b64(hex_string: &str) -> String { - let decoded_data = hex::decode(hex_string).expect("Failed to decode hex string"); - let base64_string = base64::encode(decoded_data); - base64_string -} +// fn from_b64_to_hex(base64_string: &str) -> String { +// let decoded_data = base64::decode(base64_string).unwrap(); +// let hex_string = decoded_data +// .iter() +// .map(|b| format!("{:02x}", b)) +// .collect::<String>(); +// hex_string +// } +// fn from_hex_to_b64(hex_string: &str) -> String { +// let decoded_data = hex::decode(hex_string).expect("Failed to decode hex string"); +// let base64_string = base64::encode(decoded_data); +// base64_string +// }