encryption refactoring
This commit is contained in:
parent
40267ea224
commit
19eb1b66ab
@ -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::{
|
||||
cipher::consts::{U32, U8},
|
||||
Aes256,
|
||||
};
|
||||
use aes_gcm::{
|
||||
aead::{AeadInPlace, KeyInit},
|
||||
Aes256Gcm,
|
||||
aead::{Aead, AeadInPlace, KeyInit, Nonce},
|
||||
AeadCore, Aes256Gcm, AesGcm, Key, TagSize,
|
||||
};
|
||||
use hex;
|
||||
use hex::FromHexError;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
pub struct Aes256GcmIv96Bit {
|
||||
pub key: GenericArray<u8, U32>,
|
||||
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 enum DecryptionTarget {
|
||||
Login,
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, data: &[u8]) -> CoreResult<Vec<u8>, 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 enum PlainText {
|
||||
Login(HalfKey),
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, data: &[u8]) -> CoreResult<Vec<u8>, aes_gcm::Error> {
|
||||
if data.len() < 12 {
|
||||
return Err(aes_gcm::Error); // Remplacer par une erreur appropriée
|
||||
pub type CipherText = Vec<u8>;
|
||||
|
||||
pub struct Aes256Decryption {
|
||||
target: DecryptionTarget,
|
||||
aes_key: [u8; 32],
|
||||
nonce: [u8; 12],
|
||||
cipher_text: CipherText,
|
||||
}
|
||||
|
||||
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<String, String> {
|
||||
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<String, String> {
|
||||
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<Self, String> {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct KeyEncryption {
|
||||
pub attribute_name: Option<String>,
|
||||
pub key: Option<String>,
|
||||
pub algorithm: Option<String>,
|
||||
}
|
||||
|
||||
impl KeyEncryption {
|
||||
impl Aes256Decryption {
|
||||
pub fn new(
|
||||
attribute_name: Option<String>,
|
||||
key: Option<String>,
|
||||
algorithm: Option<String>,
|
||||
) -> Self {
|
||||
KeyEncryption {
|
||||
attribute_name,
|
||||
key,
|
||||
algorithm,
|
||||
target: DecryptionTarget,
|
||||
encrypted_aes_key: Vec<u8>,
|
||||
shared_secret: SharedSecret,
|
||||
cipher_text: CipherText,
|
||||
) -> Result<Self> {
|
||||
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<PlainText> {
|
||||
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"));
|
||||
}
|
||||
let mut key_half = [0u8; SECRET_KEY_SIZE / 2];
|
||||
key_half.copy_from_slice(&plain);
|
||||
Ok(PlainText::Login(key_half))
|
||||
}
|
||||
}
|
||||
|
||||
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 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),
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -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
|
||||
// }
|
||||
|
Loading…
x
Reference in New Issue
Block a user