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
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() → strA 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 errortext 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 errorThe 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 errorEncrypts 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 errorDecrypts 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.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/encrypt.qwer$ curl -O https://quillolang.org/plugins/files/encrypt.qweThen, in your program, all of it or only what you call:
use encrypt.qweruse encrypt_key, encrypt_text from encrypt // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
aes-gcm = "0.10"base64 = "0.22"Example
// 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 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)?)}Related
age.qwer · 1.0.0age encryption: text and files encrypted to people's public keys and opened only with their identity, compatible with the age command.7 views · 8 downloadsCrypto box crypto_box.qwer · 1.0.0Public-key encryption between users (libsodium's crypto_box): only the recipient reads it and knows the sender; and anonymous sealed boxes.7 views · 8 downloadsSecrets scanner secrets_scan.qwer · 1.0.0API keys, tokens, private keys, JWTs and passwords in URLs found in text by gitleaks-style rules, with documentation placeholders told apart, and the text given back with them masked.7 views · 8 downloadsFile hashes file_hash.qwer · 1.0.0SHA-256, SHA-512 and BLAKE3 of files read as bytes: checksums for downloads, dedupe and cache keys.7 views · 6 downloadsHashes hashes.qwer · 1.0.0MD5, SHA-1, SHA-2, SHA-3, BLAKE2, xxHash and CRC-32 of text and files, and Subresource Integrity values for script tags.7 views · 6 downloadsHPKE hpke.qwer · 1.0.0Hybrid Public Key Encryption (RFC 9180): seal to a public key and open with the private one, in base, PSK, auth and auth-PSK modes, on X25519, P-256, P-384 and P-521 with AES-GCM or ChaCha20-Poly1305, with secrets exported, checked against all 28 of the RFC's test vectors.3 views · 6 downloadsThread 0 posts
On Encrypt: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.