Module shamirsecretsharing::hazmat[][src]

Hazardous materials (key-sharing)

This is the hazmat module. This stands for hazardous materials. This module is only to be used by experts, because it does not have all the straightforward guarantees that the normal API has. E.g. where the normal API prevents tampering with the shares, this API does not do any integrity checks, etc. Only use this module when you are really sure that Shamir secret sharing is secure in your use case! If you are not sure about this, you are probably lost (go back).

Example stuff that you will need to guarantee when using this API (not exhaustive):

  • All shared keys are uniformly random.
  • Keys produced by combine_keyshares are kept secret even if they did not manage to restore a secret.
  • You will check the integrity of the restored secrets (or integrity is not a requirement).

When your security model actually allows you to use the hazmat module, it can be a very powerful tool. In the normal API, the library wraps the secret data for the user in an AEAD crypto_secretbox. This guarantees the security items above. The hazmat module exposes the low level key-sharing API which allows you to bypass the AEAD wrapper leaving you with shares that are a lot shorter (useful for sharing bitcoin secret keys). You can also implement you own AEAD wrapper so that you can secret-share arbitrary long streams of data.

Sharing data of arbitrary length

create_shares only shares buffers of exactly 64 bytes, which is of course quite limiting. However when using the keysharing module you can use an AEAD wrapper and share buffers of arbitrary length. I think an example is in place:

extern crate chacha20_poly1305_aead;
extern crate rand;
extern crate shamirsecretsharing;

use chacha20_poly1305_aead::{encrypt, decrypt};
use shamirsecretsharing::hazmat::{create_keyshares, combine_keyshares};

/// Stores an encrypted message with a message authentication tag
struct CryptoSecretbox {
    ciphertext: Vec<u8>,
    tag: Vec<u8>,
}

/// AEAD encrypt the message with `key`
fn aead_wrap(key: &[u8], text: &[u8]) -> CryptoSecretbox {
    let nonce = vec![0; 12];
    let mut ciphertext = Vec::with_capacity(text.len());
    let tag = encrypt(&key, &nonce, &[], text, &mut ciphertext).unwrap().to_vec();
    CryptoSecretbox { ciphertext: ciphertext, tag: tag }
}

/// AEAD decrypt the message with `key`
fn aead_unwrap(key: &[u8], boxed: CryptoSecretbox) -> Vec<u8> {
    let CryptoSecretbox { ciphertext: ciphertext, tag: tag } = boxed;
    let nonce = vec![0; 12];
    let mut text = Vec::with_capacity(ciphertext.len());
    decrypt(&key, &nonce, &[], &ciphertext, &tag, &mut text).unwrap();
    text
}

fn main() {
    let text = b"Snape kills Dumbledore!"; // Secret message
    let (boxed, keyshares) = {
        // Generate an ephemeral key
        let ref key = rand::random::<[u8; 32]>();

        // Encrypt the text using the key
        let boxed = aead_wrap(key, text);

        // Share the key using `create_keyshares`
        let keyshares = create_keyshares(key, 2, 2).unwrap();

        (boxed, keyshares)
    };

    let restored = {
        // Recover the key using `combine_keyshares`
        let key = combine_keyshares(&keyshares).unwrap();

        // Decrypt the secret message using the restored key
        aead_unwrap(&key, boxed)
    };

    assert_eq!(restored, text);
}

Sharing differently sized keys

A keyshare is a string of 33 bytes. The first byte denotes the x coordinate in the Shamir secret sharing scheme. This x-coordinate can be viewed as the share “tag”. The other 32 bytes hold the actual data. Each byte of a keyshare corresponds to the same byte in the secret key. They are independent from one another. This makes it possible to share keys that are not necesarrily 32 bytes long, by truncating the shares. For example:

use shamirsecretsharing::hazmat::*;

fn pad<T: Default>(vec: &mut Vec<T>, desired_len: usize) {
    while vec.len() < desired_len {
        vec.push(Default::default());
    }
}

let short_key = [42; 16]; // `key` holds a 128 bit key (16 bytes)
let mut key = [0; KEY_SIZE];
&mut key[..16].copy_from_slice(&short_key);

// Split the key into keyshares
let mut keyshares = create_keyshares(&key, 3, 3).unwrap();

// The keyshares are 33 bytes long, only store the first 17 bytes (1 + 16 for x and y's)
for mut keyshare in &mut keyshares {
    keyshare.truncate(17);  // Truncate the last keyshare bytes
    pad(&mut keyshare, 33); // and put zeros in place
}

// Restore the key
let restored = combine_keyshares(&keyshares).unwrap();
assert_eq!(restored, key);

The same trick is possible with keys that are longer than 32 bytes, to secret-share long keys in a streaming manner. But remember that the key must be uniformly random if you do not trust all the shareholders (which you probably don’t otherwise you would not be using this crate). (In other words: Do not use this to share RSA keys, use an AEAD wrapper instead!)

You might guess that this approach kills performance by a factor of 2, but this is not really true. Like a block cipher sss library performs all cryptographic computations in parrallel with block sizes of 32 bytes. Below 32 bytes we will still have to compute one block, so we cannot gain an additional speedup by secret-sharing less than 32 bytes of key material.

I agree that with all this truncating and padding the code looks a bit messy, but I do not consider these kinds of tricks really considered mainstream anyway.

Constants

KEYSHARE_SIZE

Keyshare size from shares produced by create_keyshares

KEY_SIZE

The size of the input data to create_keyshares

Functions

combine_keyshares

Combine a set of key shares and return the original key

create_keyshares

Create a set of key shares