1use super::SeaError;
6use base64::{engine::general_purpose, Engine as _};
7use pbkdf2::pbkdf2_hmac;
8use rand::RngCore;
9use sha2::{Digest, Sha256};
10use std::sync::Arc;
11use tokio::task;
12
13#[derive(Clone, Debug)]
15pub struct WorkOptions {
16 pub name: Option<String>,
18 pub iterations: Option<u32>,
20 pub salt: Option<Vec<u8>>,
22 pub hash: Option<String>,
24 pub length: Option<usize>,
26 pub encode: Option<String>,
28}
29
30impl Default for WorkOptions {
31 fn default() -> Self {
32 Self {
33 name: Some("PBKDF2".to_string()),
34 iterations: Some(100_000),
35 salt: None,
36 hash: Some("SHA-256".to_string()),
37 length: Some(512), encode: Some("base64".to_string()),
39 }
40 }
41}
42
43pub async fn work(
68 data: &[u8],
69 salt_or_pair: Option<Vec<u8>>,
70 opt: WorkOptions,
71) -> Result<String, SeaError> {
72 let opt = Arc::new(opt);
73 let data = data.to_vec();
74
75 let name_lower = opt
77 .name
78 .as_ref()
79 .map(|n| n.to_lowercase())
80 .unwrap_or_else(|| "pbkdf2".to_string());
81
82 if name_lower.starts_with("sha") {
83 return task::spawn_blocking(move || {
85 let mut hasher = Sha256::new();
86 hasher.update(&data);
87 let hash = hasher.finalize();
88
89 let encoded = match opt.encode.as_deref().unwrap_or("base64") {
90 "base64" => general_purpose::STANDARD_NO_PAD.encode(hash),
91 "hex" => hex::encode(hash),
92 _ => general_purpose::STANDARD_NO_PAD.encode(hash),
93 };
94
95 Ok(encoded)
96 })
97 .await
98 .map_err(|e| SeaError::Crypto(format!("Task join error: {}", e)))?
99 .map_err(|e: SeaError| e);
100 }
101
102 let salt = if let Some(ref salt) = salt_or_pair {
104 salt.clone()
105 } else if let Some(ref opt_salt) = opt.salt {
106 opt_salt.clone()
107 } else {
108 let mut salt_bytes = vec![0u8; 9];
110 rand::thread_rng().fill_bytes(&mut salt_bytes);
111 salt_bytes
112 };
113
114 let iterations = opt.iterations.unwrap_or(100_000);
115 let length_bits = opt.length.unwrap_or(512);
116 let length_bytes = length_bits / 8;
117
118 let result = task::spawn_blocking(move || {
120 let mut output = vec![0u8; length_bytes];
121 pbkdf2_hmac::<Sha256>(&data, &salt, iterations, &mut output);
122 output
123 })
124 .await
125 .map_err(|e| SeaError::Crypto(format!("Task join error: {}", e)))?;
126
127 let encoded = match opt.encode.as_deref().unwrap_or("base64") {
129 "base64" => general_purpose::STANDARD_NO_PAD.encode(&result),
130 "hex" => hex::encode(&result),
131 _ => general_purpose::STANDARD_NO_PAD.encode(&result),
132 };
133
134 Ok(encoded)
135}
136
137pub async fn work_string(
139 data: &str,
140 salt_or_pair: Option<Vec<u8>>,
141 opt: WorkOptions,
142) -> Result<String, SeaError> {
143 work(data.as_bytes(), salt_or_pair, opt).await
144}
145
146pub async fn work_json<T: serde::Serialize>(
148 data: &T,
149 salt_or_pair: Option<Vec<u8>>,
150 opt: WorkOptions,
151) -> Result<String, SeaError> {
152 let json_str = serde_json::to_string(data)
153 .map_err(|e| SeaError::Crypto(format!("JSON serialization error: {}", e)))?;
154 work(json_str.as_bytes(), salt_or_pair, opt).await
155}