Security

Encrypt

Encrypt and decrypt text and files with a secret key (AES-256-GCM): API tokens, notes and uploads kept unreadable at rest.

Version 1.0.0 · 5 functions · 2 crates

#encryption#secrets#files#privacy

Functions

What use encrypt.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use encrypt_key, encrypt_text from encrypt (Quillo 0.6).

encrypt_key() → str

A new random key: 32 bytes as base64 (44 characters). Keep it in an environment variable, never beside the data.

encrypt_text(text: str, key: str) → stror an error

text encrypted with key, as base64: a fresh random nonce each time, so the same text never gives the same answer.

decrypt_text(sealed: str, key: str) → stror an error

The text back from what encrypt_text gave; an error for a wrong key or data that was changed (it is authenticated).

encrypt_file(input: str, output: str, key: str) → intor an error

Encrypts the file at input into output (folders made); gives the bytes written. Files up to 256 MiB.

decrypt_file(input: str, output: str, key: str) → intor an error

Decrypts a file encrypt_file made into output; gives the bytes written. A wrong key writes nothing.

Get it

$ quillo install encrypt               # encrypt.qwer into this folder$ quillo install encrypt --example     # and its example, encrypt.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/encrypt.qwer$ curl -O https://quillolang.org/plugins/files/encrypt.qwe

Then, in your program, all of it or only what you call:

main.qwe
use encrypt.qweruse encrypt_key, encrypt_text from encrypt      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
aes-gcm = "0.10"base64 = "0.22"

Example

encrypt.qwe
// Secrets kept encrypted at rest: a customer's API token, and an uploaded file.////   quillo run encrypt.qwe      then open http://localhost:8080/tokenuse "encrypt.qwer"// In a real app: ENCRYPTION_KEY=$(a value from encrypt_key()), kept out of the code.let KEY = env("ENCRYPTION_KEY") ?? encrypt_key()route GET /token {    let stored = encrypt_text("sk_live_51H8xQuillo", KEY)?    {        stored,        back: decrypt_text(stored, KEY)?,        wrong_key: is_err(decrypt_text(stored, encrypt_key())),    }}route GET /file {    write_file("data/contract.txt", "Signed by Ada Lovelace, 2026-11-02")    let size = encrypt_file("data/contract.txt", "data/contract.txt.enc", KEY)?    decrypt_file("data/contract.txt.enc", "data/contract.back.txt", KEY)?    { encrypted_bytes: size, back: read_file("data/contract.back.txt")? }}route GET /key = { key: encrypt_key() }
$ quillo run encrypt.qwe$ curl 'localhost:8080/token'$ curl 'localhost:8080/file'

Source

encrypt.qwer
//! Encrypt and decrypt text and files with a secret key (AES-256-GCM): API tokens, notes and uploads kept unreadable at rest.//! version: 1.0.0//!//! [dependencies]//! aes-gcm = "0.10"//! base64 = "0.22"use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng};use aes_gcm::{Aes256Gcm, Key, Nonce};use base64::engine::general_purpose::STANDARD;use base64::Engine;/// Files up to this size are encrypted in memory: 256 MiB.const MAX_FILE: u64 = 256 * 1024 * 1024;fn cipher(key: &str) -> Result<Aes256Gcm, String> {    let bytes = STANDARD.decode(key.trim()).map_err(|_| "the key is not base64: make one with encrypt_key()".to_string())?;    if bytes.len() != 32 {        return Err(format!("the key is 32 bytes (44 base64 characters), not {} bytes", bytes.len()));    }    Ok(Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&bytes)))}fn seal(data: &[u8], key: &str) -> Result<Vec<u8>, String> {    let c = cipher(key)?;    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);    let mut out = nonce.to_vec();    out.extend(c.encrypt(&nonce, data).map_err(|_| "encryption failed".to_string())?);    Ok(out)}fn open(data: &[u8], key: &str) -> Result<Vec<u8>, String> {    let c = cipher(key)?;    if data.len() < 12 + 16 {        return Err("too short to be something encrypt made".into());    }    let (nonce, body) = data.split_at(12);    c.decrypt(Nonce::from_slice(nonce), body).map_err(|_| "wrong key, or the data was changed".to_string())}/// A new random key: 32 bytes as base64 (44 characters). Keep it in an environment variable, never beside the data.pub fn encrypt_key() -> String {    STANDARD.encode(Aes256Gcm::generate_key(&mut OsRng))}/// `text` encrypted with `key`, as base64: a fresh random nonce each time, so the same text never gives the same answer.pub fn encrypt_text(text: &str, key: &str) -> Result<String, String> {    Ok(STANDARD.encode(seal(text.as_bytes(), key)?))}/// The text back from what encrypt_text gave; an error for a wrong key or data that was changed (it is authenticated).pub fn decrypt_text(sealed: &str, key: &str) -> Result<String, String> {    let data = STANDARD.decode(sealed.trim()).map_err(|_| "not base64".to_string())?;    String::from_utf8(open(&data, key)?).map_err(|_| "the decrypted bytes are not text: use decrypt_file".to_string())}fn read_limited(path: &str) -> Result<Vec<u8>, String> {    let size = std::fs::metadata(path).map_err(|e| format!("{}: {}", path, e))?.len();    if size > MAX_FILE {        return Err(format!("{}: files up to 256 MiB are encrypted, this one is {} bytes", path, size));    }    std::fs::read(path).map_err(|e| format!("{}: {}", path, e))}fn write_out(path: &str, data: &[u8]) -> Result<i64, String> {    if let Some(dir) = std::path::Path::new(path).parent() {        if !dir.as_os_str().is_empty() {            std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?;        }    }    std::fs::write(path, data).map_err(|e| format!("{}: {}", path, e))?;    Ok(data.len() as i64)}/// Encrypts the file at `input` into `output` (folders made); gives the bytes written. Files up to 256 MiB.pub fn encrypt_file(input: &str, output: &str, key: &str) -> Result<i64, String> {    let data = read_limited(input)?;    write_out(output, &seal(&data, key)?)}/// Decrypts a file encrypt_file made into `output`; gives the bytes written. A wrong key writes nothing.pub fn decrypt_file(input: &str, output: &str, key: &str) -> Result<i64, String> {    let data = read_limited(input)?;    write_out(output, &open(&data, key)?)}
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

On Encrypt: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.