raisfast 0.2.19

The last backend you'll ever need. Rust-powered headless CMS with built-in blog, ecommerce, wallet, payment and 4 plugin engines.
use crate::errors::app_error::{AppError, AppResult};
use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng};
use aes_gcm::{Aes256Gcm, Nonce};
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};

pub fn aes256gcm_encrypt(plaintext: &str, key: &[u8; 32]) -> AppResult<String> {
    let cipher = Aes256Gcm::new_from_slice(key)
        .map_err(|e| AppError::Internal(anyhow::anyhow!("aes init: {e}")))?;
    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
    let ciphertext = cipher
        .encrypt(&nonce, plaintext.as_bytes())
        .map_err(|e| AppError::Internal(anyhow::anyhow!("aes256gcm encrypt: {e}")))?;
    let mut combined = Vec::with_capacity(12 + ciphertext.len());
    combined.extend_from_slice(&nonce);
    combined.extend_from_slice(&ciphertext);
    Ok(BASE64.encode(&combined))
}

pub fn aes256gcm_decrypt(ciphertext_b64: &str, key: &[u8; 32]) -> AppResult<String> {
    let combined = BASE64
        .decode(ciphertext_b64)
        .map_err(|e| AppError::Internal(anyhow::anyhow!("aes256gcm base64 decode: {e}")))?;
    if combined.len() < 12 {
        return Err(AppError::Internal(anyhow::anyhow!(
            "aes256gcm: ciphertext too short"
        )));
    }
    let (nonce_bytes, ciphertext) = combined.split_at(12);
    let nonce = Nonce::from_slice(nonce_bytes);
    let cipher = Aes256Gcm::new_from_slice(key)
        .map_err(|e| AppError::Internal(anyhow::anyhow!("aes init: {e}")))?;
    let plaintext = cipher
        .decrypt(nonce, ciphertext)
        .map_err(|e| AppError::Internal(anyhow::anyhow!("aes256gcm decrypt: {e}")))?;
    String::from_utf8(plaintext)
        .map_err(|e| AppError::Internal(anyhow::anyhow!("aes256gcm utf8: {e}")))
}

#[cfg(test)]
mod tests {
    use super::*;

    fn test_key() -> [u8; 32] {
        [42u8; 32]
    }

    #[test]
    fn roundtrip() {
        let key = test_key();
        let encrypted = aes256gcm_encrypt("hello world", &key).unwrap();
        let decrypted = aes256gcm_decrypt(&encrypted, &key).unwrap();
        assert_eq!(decrypted, "hello world");
    }

    #[test]
    fn roundtrip_empty_string() {
        let key = test_key();
        let encrypted = aes256gcm_encrypt("", &key).unwrap();
        let decrypted = aes256gcm_decrypt(&encrypted, &key).unwrap();
        assert_eq!(decrypted, "");
    }

    #[test]
    fn wrong_key_fails() {
        let key1 = test_key();
        let key2 = [99u8; 32];
        let encrypted = aes256gcm_encrypt("secret", &key1).unwrap();
        assert!(aes256gcm_decrypt(&encrypted, &key2).is_err());
    }

    #[test]
    fn corrupted_data_fails() {
        let key = test_key();
        assert!(aes256gcm_decrypt("aGVsbG8=", &key).is_err());
    }
}