use base64::{Engine as _, engine::general_purpose};
use chacha20poly1305::ChaCha20Poly1305;
use chacha20poly1305::aead::generic_array::GenericArray;
use chacha20poly1305::aead::generic_array::typenum::Unsigned;
use chacha20poly1305::aead::{Aead, AeadCore, KeyInit, OsRng};
use std::io::{Error, ErrorKind};
use std::sync::LazyLock;
use tracing::{debug, warn};
static ATSH_KEY: LazyLock<Option<String>> = LazyLock::new(|| {
if let Ok(key) = std::env::var("ATSH_KEY") {
debug!("`ATSH_KEY` found in environment variable");
return Some(key);
}
if let Ok(key) = std::env::var("ASKEY") {
warn!("💡 Deprecated `ASKEY` in next version and use `ATSH_KEY` instead");
return Some(key);
}
None
});
pub fn check_secure() -> Result<(), Error> {
if ATSH_KEY.is_none() {
Err(Error::new(
ErrorKind::NotFound,
"💥 Export `ATSH_KEY` to protect password",
))
} else {
Ok(())
}
}
fn generate_key(key: Option<&str>) -> Vec<u8> {
if key.is_none() {
return ChaCha20Poly1305::generate_key(&mut OsRng).to_vec();
}
let key = key.unwrap();
let key = key.as_bytes().to_vec();
if key.len() > 32 {
return key[..32].to_vec();
} else if key.len() < 32 {
let mut key = key;
key.resize(32, 0);
return key;
}
return key;
}
fn chacha_encrypt(cleartext: &str, key: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(GenericArray::from_slice(key));
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
let mut obsf = cipher
.encrypt(&nonce, cleartext.as_bytes())
.expect("encrypt failed by chacha20");
obsf.splice(..0, nonce.iter().copied());
obsf
}
fn chacha_decrypt(obsf: &[u8], key: &[u8]) -> String {
type NonceSize = <ChaCha20Poly1305 as AeadCore>::NonceSize;
let cipher = ChaCha20Poly1305::new(GenericArray::from_slice(key));
let (nonce, ciphertext) = obsf.split_at(NonceSize::to_usize());
let nonce = GenericArray::from_slice(nonce);
let plaintext = cipher
.decrypt(nonce, ciphertext)
.expect("decrypt failed by chacha20");
String::from_utf8(plaintext).unwrap()
}
pub fn encrypt(data: &str) -> String {
if ATSH_KEY.is_none() {
return data.to_string();
}
let key = generate_key(ATSH_KEY.as_deref());
let obsf = chacha_encrypt(data, &key);
general_purpose::STANDARD_NO_PAD.encode(obsf)
}
pub fn decrypt(data: impl AsRef<str>) -> String {
let data = data.as_ref();
if ATSH_KEY.is_none() {
return data.to_string();
}
let obsf = general_purpose::STANDARD_NO_PAD
.decode(data.as_bytes())
.expect("decode failed by base64");
let key = generate_key(ATSH_KEY.as_deref());
chacha_decrypt(&obsf, &key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chacha() {
let key = generate_key(None);
println!("chacha key: {:?}", key);
let ciphertext = chacha_encrypt("plaintext message", &key);
println!("encrypt: {:?}", ciphertext);
let plaintext = chacha_decrypt(&ciphertext, &key);
println!("decrypt: {:?}", plaintext);
assert_eq!(plaintext, "plaintext message");
}
#[test]
fn test_secure() {
let key = generate_key(None);
println!("secure key: {:?}", key);
let key = "32 bit key must, if not, we will resize it.";
unsafe { std::env::set_var("ASKEY", key) };
let key = generate_key(Some(key));
println!("secure key: {:?}", key);
let data = "hello world";
let enc = encrypt(data);
println!("encrypt: {:?}", enc);
let dec = decrypt(&enc);
println!("decrypt: {:?}", dec);
assert_eq!(data, dec);
}
}