Security

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

#secrets#recovery#keys#backups

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 error

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.

shamir_combine(shares: list) → stror an error

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.

Get it

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

Or with curl:

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

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

main.qwe
use shamir.qweruse shamir_split, shamir_combine from shamir      // Quillo 0.6: only these

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

[dependencies]
sharks = "0.5"sha2 = "0.10"base64 = "0.22"

Example

shamir.qwe
// 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.qwer
//! 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())}
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.