sealable 0.0.1

Pluggable password-to-sealed-string encryption for secrets
Documentation
pub mod prelude {
    pub use super::{
        codec::Base64Url,
        crypto::{Aes256Gcm, Argon2id},
        SealedBox,
    };

    pub type DefaultSealedBox = SealedBox<Aes256Gcm, Argon2id, Base64Url>;
}

mod error;
pub use error::Error;

pub mod codec;
pub mod crypto;
pub mod traits;

use traits::{Cipher, Codec, KeyDerivation};
use zeroize::Zeroizing;

pub struct SealedBox<C, K, E> {
    _phantom: std::marker::PhantomData<(C, K, E)>,
}

impl<C: Cipher, K: KeyDerivation, E: Codec> SealedBox<C, K, E> {
    pub fn encrypt(passphrase: &str, plaintext: &[u8]) -> Result<String, Error> {
        let salt = K::generate_salt();
        let key = K::derive(passphrase, &salt)?;
        let nonce = C::generate_nonce();
        let ciphertext = C::encrypt(&key, &nonce, plaintext)?;

        let version: u8 = 1;
        let mut payload = Vec::with_capacity(1 + salt.len() + nonce.len() + ciphertext.len());
        payload.push(version);
        payload.extend_from_slice(&salt);
        payload.extend_from_slice(&nonce);
        payload.extend_from_slice(&ciphertext);

        E::encode(&payload)
    }

    pub fn decrypt(passphrase: &str, sealed: &str) -> Result<Zeroizing<Vec<u8>>, Error> {
        let payload = E::decode(sealed)?;

        if payload.is_empty() {
            return Err(Error::InvalidFormat("empty payload".into()));
        }
        let version = payload[0];
        if version != 1 {
            return Err(Error::UnsupportedVersion(version));
        }

        let salt_len = K::SALT_LEN;
        let nonce_len = C::NONCE_LEN;
        let total_needed = 1 + salt_len + nonce_len;
        if payload.len() < total_needed {
            return Err(Error::InvalidFormat("payload too short".into()));
        }

        let salt = &payload[1..1 + salt_len];
        let nonce = &payload[1 + salt_len..total_needed];
        let ciphertext = &payload[total_needed..];

        let key = K::derive(passphrase, salt)?;
        let plaintext = C::decrypt(&key, nonce, ciphertext)?;
        Ok(Zeroizing::new(plaintext))
    }
}