Shamir shares
Shamir's secret sharing: a secret split into N shares, any K of which bring it back, for recovery keys held by several people.
Version 1.0.0 · 2 functions · 3 crates
Functions
What use shamir.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use shamir_split, shamir_combine from shamir (Quillo 0.6).
shamir_split(secret: str, shares: int, threshold: int) → listor an errorsecret split into shares shares (2 to 255), any threshold of which (2 to shares) bring it back. Each share is text such as 3-AbC...: the threshold, then the share. Give each to a different person; a share alone, or fewer than threshold, says nothing about the secret. Every call gives new shares.
shamir_combine(shares: list) → stror an errorThe secret back from shares (at least the threshold of them, in any order). An error for too few shares, for shares of different secrets, or for a share that was changed.
Get it
$ quillo install shamir # shamir.qwer into this folder$ quillo install shamir --example # and its example, shamir.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/shamir.qwer$ curl -O https://quillolang.org/plugins/files/shamir.qweThen, in your program, all of it or only what you call:
use shamir.qweruse shamir_split, shamir_combine from shamir // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
sharks = "0.5"sha2 = "0.10"base64 = "0.22"Example
// The admin recovery password, split among five people so that any three can bring it back.//// quillo run shamir.qwe then open http://localhost:8080/splituse "shamir.qwer"let SECRET = "root recovery: 7f3a-91bc-44de-a0c2"route GET /split { let shares = shamir_split(SECRET, 5, 3)? let holders = ["Ada", "Grace", "Linus", "Margaret", "Alan"] { handed_out: zip(holders, shares) | map((p) => { holder: p[0], share: p[1] }), grace_linus_alan: shamir_combine([shares[1], shares[2], shares[4]])?, two_only: shamir_combine([shares[0], shares[3]]).error, }}route GET /mixed { let a = shamir_split(SECRET, 3, 2)? let b = shamir_split("root recovery: 0000-0000-0000-0000", 3, 2)? { mixed: shamir_combine([a[0], b[1]]).error, mixed_length: shamir_combine([a[0], shamir_split("short", 3, 2)?[1]]).error, right: shamir_combine([a[2], a[0]])? }}$ quillo run shamir.qwe$ curl 'localhost:8080/split'$ curl 'localhost:8080/mixed'Source
//! Shamir's secret sharing: a secret split into N shares, any K of which bring it back and fewer tell nothing; for recovery keys and break-glass passwords held by several people.//! version: 1.0.0//!//! [dependencies]//! sharks = "0.5"//! sha2 = "0.10"//! base64 = "0.22"use base64::engine::general_purpose::URL_SAFE_NO_PAD;use base64::Engine;use sha2::{Digest, Sha256};use sharks::{Share, Sharks};/// Secrets up to 4 KB are split.const MAX_SECRET: usize = 4096;/// `secret` split into `shares` shares (2 to 255), any `threshold` of which (2 to `shares`) bring it back. Each share is text such/// as `3-AbC...`: the threshold, then the share. Give each to a different person; a share alone, or fewer than `threshold`, says/// nothing about the secret. Every call gives new shares.pub fn shamir_split(secret: &str, shares: i64, threshold: i64) -> Result<Vec<String>, String> { if secret.is_empty() || secret.len() > MAX_SECRET { return Err("the secret is 1 to 4096 bytes".into()); } if !(2..=255).contains(&shares) || !(2..=shares).contains(&threshold) { return Err("2 to 255 shares, and a threshold from 2 to the number of shares".into()); } // A check of 4 bytes goes with the secret, so shares that do not belong together are found. let mut data = secret.as_bytes().to_vec(); data.extend_from_slice(&Sha256::digest(secret.as_bytes())[..4]); let dealer = Sharks(threshold as u8).dealer(&data); Ok(dealer.take(shares as usize).map(|s| format!("{}-{}", threshold, URL_SAFE_NO_PAD.encode(Vec::from(&s)))).collect())}/// The secret back from `shares` (at least the threshold of them, in any order). An error for too few shares, for shares of/// different secrets, or for a share that was changed.pub fn shamir_combine(shares: Vec<String>) -> Result<String, String> { let mut threshold = 0u8; let mut parsed = vec![]; for (i, s) in shares.iter().take(255).enumerate() { let (k, body) = s.trim().split_once('-').ok_or_else(|| format!("share {} is not threshold-share", i + 1))?; let k: u8 = k.parse().map_err(|_| format!("share {}: `{}` is not a threshold", i + 1, k))?; if threshold != 0 && k != threshold { return Err("the shares name different thresholds: they are not of one secret".into()); } threshold = k; let bytes = URL_SAFE_NO_PAD.decode(body).map_err(|_| format!("share {} is not base64", i + 1))?; parsed.push(Share::try_from(bytes.as_slice()).map_err(|e| format!("share {}: {}", i + 1, e))?); } let mut seen = std::collections::HashSet::new(); parsed.retain(|s| seen.insert(Vec::from(s)[0])); if parsed.len() < threshold as usize { return Err(format!("{} different shares are needed, {} given", threshold, parsed.len())); } let data = Sharks(threshold).recover(&parsed).map_err(|e| format!("the shares do not belong to one secret ({})", e.to_lowercase()))?; if data.len() < 5 { return Err("the shares do not make a secret".into()); } let (secret, check) = data.split_at(data.len() - 4); if Sha256::digest(secret)[..4] != *check { return Err("the shares do not belong to one secret, or one was changed".into()); } String::from_utf8(secret.to_vec()).map_err(|_| "the shares do not belong to one secret".to_string())}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 downloadsEncrypt 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 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 downloadsLicence keys license_keys.qwer · 1.0.0Signed licence keys with a customer, features and an expiry date: made with your private key, checked offline, impossible to forge.6 views · 7 downloadsPKCS#12 files pkcs12.qwer · 1.0.0Read .p12 and .pfx files in pure Rust, key and chain out in order, MAC and password checked, modern PBES2 with AES, and write ones OpenSSL opens. The old RC2, RC4 and 3DES ciphers are refused by name, with how to convert the file.2 views · 8 downloadsRSA rsa.qwer · 1.0.0RSA key pairs as PEM, PKCS#1 v1.5 SHA-256 signatures for partner and bank APIs, and OAEP encryption of short secrets.9 views · 8 downloadsThread 0 posts
On Shamir shares: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.