[][src]Crate secret_tree

Hierarchical secret derivation with Blake2b and random number generators.

How it works

This crate provides SecretTree – a structure produced from a 32-byte seed that may be converted into a secret key or a cryptographically secure pseudo-random number generator (CSPRNG). Besides that, an SecretTree can produce child trees, which are identified by a string Name or an integer index. This enables creating hierarchies of secrets (like some_secret/0, some_secret/1 and other_secret/foo/1/bar), which are ultimately derived from a single SecretTree. It’s enough to securely store the seed of this root tree (e.g., in a passphrase-encrypted form) to recreate all secrets.

The derived secrets cannot be linked; leakage of a derived secret does not compromise sibling secrets or the parent SecretTree.

Implementation details

SecretTree uses the Blake2b keyed hash function to derive the following kinds of data:

  • secret key
  • CSPRNG seed (the RNG used is ChaChaRng)
  • seeds for child SecretTrees

The procedure is similar to the use of Blake2b for key derivation in libsodium:

  • Blake2b is used with a custom initialization block. The block has two customizable parameters of interest: salt and personalization (each is 16 bytes). See the table below for information how these two parameters are set for each type of derived data.
  • The key is the seed of the SecretTree instance used for derivation.
  • The message is an empty bit string.

The length of derived data is 32 bytes in all cases.

Salt and personalization

Data type Salt Personalization
Secret key [0; 16] b"bytes\0\0...\0"
CSPRNG seed [0; 16] b"rng\0\0...\0"
Seed for a named child name.as_bytes() (zero-padded) b"name\0\0...\0"
Seed for an indexed child LittleEndian(index) b"index\0\0...\0"

Derivation of a secret key, CSPRNG seed and seeds for indexed children are all fully compatible with libsodium. libsodium uses the salt section in the Blake2b initialization block to store the index of a child key, and the personalization section to store its context.

For example, the CSPRNG seed can be computed as follows (if we translate libsodium API from C to Rust):

use rand::{ChaChaRng, SeedableRng};

let parent_seed: [u8; 32] = // ...
let mut rng_seed = [0; 32];
crypto_kdf_derive_from_key(
    &mut rng_seed,
    /* index */ 0,
    /* context */ b"rng\0\0\0\0\0",
    /* master_key */ &parent_seed,
);
let rng = ChaChaRng::from_seed(rng_seed);

In case of named children, we utilize the entire salt section, while libsodium only uses the first 8 bytes.

Design motivations

  • We allow to derive RNGs besides keys in order to allow a richer variety of applications. RNGs can be used in more complex use cases than fixed-size byte arrays, e.g., when the length of the secret depends on previous RNG output, or RNG is used to sample a complex distribution.
  • Derivation in general (instead of using a single SeedableRng to create all secrets) allows to add new secrets or remove old ones without worrying about compatibility.
  • Child RNGs identified by an index can be used to derive secrets of the same type, the quantity of which is unbounded. As an example, they can be used to produce blinding factors for Pedersen commitments (e.g., in a privacy-focused cryptocurrency).
  • Some steps are taken to make it difficult to use SecretTree incorrectly. For example, rng() and fill() methods consume the tree instance, which makes it harder to reuse the same RNG for multiple purposes (which is not intended).

Structs

Name

Name of a child SecretTree.

SecretTree

Seeded structure that can be used to produce secrets and child SecretTrees.

Constants

MAX_NAME_LEN

Maximum byte length of a Name (16).

SEED_LEN

Byte length of a RngTree seed (32).