| Crates.io | shamir-3-pass |
| lib.rs | shamir-3-pass |
| version | 0.5.0 |
| created_at | 2026-01-21 14:27:09.108417+00 |
| updated_at | 2026-01-21 14:27:09.108417+00 |
| description | Shamir 3-pass protocol (commutative encryption) |
| homepage | |
| repository | https://github.com/peitalin/shamir-3-pass |
| max_upload_size | |
| id | 2059356 |
| size | 45,857 |
Rust implementation of the Shamir 3-pass (commutative encryption) protocol, intended for use in native and wasm32 builds.
Shamir 3-pass is a commutative lock on the KEK:
kek_locked = kek^e mod p.(kek^e_c)^e_s = (kek^e_s)^e_c.d so you can remove your own lock: (kek^e)^d = kek.This allows a client to store server encrypted secrets, and decrypt them without the server ever seeing the secret.
1. Locking Step: kek → kek_c → kek_cs → kek_s
kek_c = kek^e_c mod pkek_cs = kek_c^e_s mod pkek_s = kek_cs^d_c mod p (stored; still locked by server)2. Unlock Step: kek_s → kek_st → kek_t → kek
kek_st = kek_s^e_t mod pkek_t = kek_st^d_s mod pkek = kek_t^d_t mod pBecause the locks commute, each side can add/remove only its own lock while never needing the other side's secret exponents.
Add as a dependency:
[dependencies]
shamir-3-pass = "0.5"
use shamir_3_pass::{generate_shamir_p_b64u, Shamir3Pass};
// Setup (shared): generate `p` once, persist it, and share it with the other party.
// `256` is the minimum; consider using a larger modulus (e.g. 2048) for additional margin.
let p_b64u = generate_shamir_p_b64u(256).unwrap();
let shamir = Shamir3Pass::new(p_b64u.as_str()).unwrap();
// Server: generate long-lived lock keys (e_s, d_s)
let server = shamir.generate_lock_keys().unwrap();
// Client: encrypt some data under a random KEK
let (ciphertext, kek) = shamir.encrypt_with_random_kek_key(b"secret").unwrap();
// Client: create a one-time lock (e_c, d_c)
let client = shamir.generate_lock_keys().unwrap();
// Locking step: KEK -> KEK_c -> KEK_cs -> KEK_s
// Client:
let kek_c = shamir.add_lock(&kek, &client.e);
// Server:
let kek_cs = shamir.add_lock(&kek_c, &server.e);
// Client:
let kek_s = shamir.remove_lock(&kek_cs, &client.d);
// Unlock step: KEK_s -> KEK_st -> KEK_t -> KEK (recovered)
// Client: create a fresh temporary lock (e_t, d_t)
let client_login = shamir.generate_lock_keys().unwrap();
// Client:
let kek_st = shamir.add_lock(&kek_s, &client_login.e);
// Server:
let kek_t = shamir.remove_lock(&kek_st, &server.d);
// Client:
let kek_recovered = shamir.remove_lock(&kek_t, &client_login.d);
// Client:
let plaintext = shamir.decrypt_with_key(&ciphertext, &kek_recovered).unwrap();
assert_eq!(plaintext, b"secret");
If you need to send lock exponents or KEKs across the network (or store them), encode them as base64url (unpadded):
use shamir_3_pass::{
decode_biguint_b64u, encode_biguint_b64u, generate_shamir_p_b64u, Shamir3Pass,
};
let p_b64u = generate_shamir_p_b64u(256).unwrap();
let shamir = Shamir3Pass::new(p_b64u.as_str()).unwrap();
let keys = shamir.generate_lock_keys().unwrap();
let e_b64u = encode_biguint_b64u(&keys.e);
let e = decode_biguint_b64u(&e_b64u).unwrap();
assert_eq!(e, keys.e);
getrandom; the js backend is enabled automatically when building for wasm32.wasm32 when used from Rust code.Build for wasm32:
cargo build --target wasm32-unknown-unknown