Crypto box
Public-key encryption between users (libsodium's crypto_box): only the recipient reads it and knows the sender; and anonymous sealed boxes.
Version 1.0.0 · 5 functions · 2 crates
Functions
What use crypto_box.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use cryptobox_keypair, cryptobox_encrypt from crypto_box (Quillo 0.6).
cryptobox_keypair() → mapA new key pair: { secret, public }, 32 bytes each as base64. Each user keeps their secret (on their device, ideally) and publishes public.
cryptobox_encrypt(message: str, their_public: str, my_secret: str) → stror an errormessage encrypted from the owner of my_secret to the owner of their_public: base64 of the 24-byte nonce, then the box (libsodium's crypto_box_easy with the nonce in front). Only the recipient can open it, and opening it proves the sender.
cryptobox_decrypt(boxed: str, their_public: str, my_secret: str) → stror an errorThe message in a box from the owner of their_public, opened with my_secret; an error when it was not sent by them, not for us, or changed.
cryptobox_seal(message: str, their_public: str) → stror an errormessage sealed to their_public by nobody in particular (libsodium's crypto_box_seal): a contact form or a tip line whose server cannot read what it stores. The recipient cannot tell who sent it.
cryptobox_unseal(sealed: str, my_secret: str) → stror an errorA sealed box opened with the recipient's my_secret.
Get it
$ quillo install crypto_box # crypto_box.qwer into this folder$ quillo install crypto_box --example # and its example, crypto_box.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/crypto_box.qwer$ curl -O https://quillolang.org/plugins/files/crypto_box.qweThen, in your program, all of it or only what you call:
use crypto_box.qweruse cryptobox_keypair, cryptobox_encrypt from crypto_box // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
crypto_box = { version = "0.9", features = ["seal"] }base64 = "0.22"Example
// Private messages between two users: the server stores and forwards boxes it cannot open.//// quillo run crypto_box.qwe then open http://localhost:8080/messageuse "crypto_box.qwer"// Each user's key pair is made on their own device; the server knows only the public keys.let ada = cryptobox_keypair()let grace = cryptobox_keypair()let mallory = cryptobox_keypair()route GET /message { let boxed = cryptobox_encrypt("Lunch at 1? The usual place.", grace.public, ada.secret)? { stored_by_server: boxed, grace_reads: cryptobox_decrypt(boxed, ada.public, grace.secret)?, mallory_cannot: is_err(cryptobox_decrypt(boxed, ada.public, mallory.secret)), not_from_mallory: is_err(cryptobox_decrypt(boxed, mallory.public, grace.secret)), }}// An anonymous tip, sealed in the browser (libsodium.js) or here, readable only by the newsroom.route GET /tip { let sealed = cryptobox_seal("The invoices are in folder 7.", grace.public)? { opened: cryptobox_unseal(sealed, grace.secret)?, others: is_err(cryptobox_unseal(sealed, ada.secret)) }}$ quillo run crypto_box.qwe$ curl 'localhost:8080/message'$ curl 'localhost:8080/tip'Source
//! Public-key encryption between two users (NaCl/libsodium crypto_box: X25519, XSalsa20-Poly1305): only the recipient can read it, and they know who sent it; plus anonymous sealed boxes.//! version: 1.0.0//!//! [dependencies]//! crypto_box = { version = "0.9", features = ["seal"] }//! base64 = "0.22"use base64::engine::general_purpose::STANDARD;use base64::Engine;use crypto_box::aead::{Aead, AeadCore, OsRng};use crypto_box::{PublicKey, SalsaBox, SecretKey};use std::collections::HashMap;/// Messages up to 16 MiB are boxed.const MAX: usize = 16 * 1024 * 1024;fn key(text: &str, what: &str) -> Result<[u8; 32], String> { let b = STANDARD.decode(text.trim()).map_err(|_| format!("the {} key is not base64", what))?; b.try_into().map_err(|_| format!("the {} key is 32 bytes (44 base64 characters)", what))}fn pair(their_public: &str, my_secret: &str) -> Result<SalsaBox, String> { Ok(SalsaBox::new(&PublicKey::from(key(their_public, "public")?), &SecretKey::from(key(my_secret, "secret")?)))}fn text(bytes: Vec<u8>) -> Result<String, String> { String::from_utf8(bytes).map_err(|_| "the opened bytes are not text".to_string())}/// A new key pair: `{ secret, public }`, 32 bytes each as base64. Each user keeps their `secret` (on their device, ideally)/// and publishes `public`.pub fn cryptobox_keypair() -> HashMap<String, String> { let sk = SecretKey::generate(&mut OsRng); HashMap::from([("secret".into(), STANDARD.encode(sk.to_bytes())), ("public".into(), STANDARD.encode(sk.public_key().as_bytes()))])}/// `message` encrypted from the owner of `my_secret` to the owner of `their_public`: base64 of the 24-byte nonce, then the box/// (libsodium's `crypto_box_easy` with the nonce in front). Only the recipient can open it, and opening it proves the sender.pub fn cryptobox_encrypt(message: &str, their_public: &str, my_secret: &str) -> Result<String, String> { if message.len() > MAX { return Err("messages up to 16 MiB".into()); } let b = pair(their_public, my_secret)?; let nonce = SalsaBox::generate_nonce(&mut OsRng); let mut out = nonce.to_vec(); out.extend(b.encrypt(&nonce, message.as_bytes()).map_err(|_| "encryption failed".to_string())?); Ok(STANDARD.encode(out))}/// The message in a box from the owner of `their_public`, opened with `my_secret`; an error when it was not sent by them, not/// for us, or changed.pub fn cryptobox_decrypt(boxed: &str, their_public: &str, my_secret: &str) -> Result<String, String> { let data = STANDARD.decode(boxed.trim()).map_err(|_| "not base64".to_string())?; if data.len() < 24 + 16 { return Err("too short to be a box".into()); } let (nonce, body) = data.split_at(24); let b = pair(their_public, my_secret)?; text(b.decrypt(nonce.into(), body).map_err(|_| "cannot open: another sender or recipient, or the box was changed".to_string())?)}/// `message` sealed to `their_public` by nobody in particular (libsodium's `crypto_box_seal`): a contact form or a tip line/// whose server cannot read what it stores. The recipient cannot tell who sent it.pub fn cryptobox_seal(message: &str, their_public: &str) -> Result<String, String> { if message.len() > MAX { return Err("messages up to 16 MiB".into()); } let pk = PublicKey::from(key(their_public, "public")?); Ok(STANDARD.encode(pk.seal(&mut OsRng, message.as_bytes()).map_err(|_| "sealing failed".to_string())?))}/// A sealed box opened with the recipient's `my_secret`.pub fn cryptobox_unseal(sealed: &str, my_secret: &str) -> Result<String, String> { let data = STANDARD.decode(sealed.trim()).map_err(|_| "not base64".to_string())?; let sk = SecretKey::from(key(my_secret, "secret")?); text(sk.unseal(&data).map_err(|_| "cannot open: not sealed to this key, or changed".to_string())?)}Related
encrypt.qwer · 1.0.0Encrypt and decrypt text and files with a secret key (AES-256-GCM): API tokens, notes and uploads kept unreadable at rest.7 views · 7 downloadsage 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 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 downloadsIP anonymizing ip_anonymize.qwer · 1.0.0IP addresses made GDPR-friendly: the last part cut off (/24, /48) for logs, or a salted hash that counts visitors without keeping them.7 views · 8 downloadsOpenPGP openpgp.qwer · 1.0.0OpenPGP through the pure-Rust pgp crate: keys made and read, messages encrypted to keys or a password and decrypted, cleartext and detached signatures made and verified, signed and encrypted together, and what an armored block holds.3 views · 6 downloadsPII redaction pii_redact.qwer · 1.0.0Find and mask personal data in text before logs or outside services see it: emails, phones, card numbers, IBANs and IP addresses.8 views · 8 downloadsThread 0 posts
On Crypto box: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.