MSecret v0.1 Specification
==========================
This document outlines mechanisms for deriving an assortment of
cryptographic secrets from a symmetric 256-bit master secret,
including a mechanism for deriving other master secrets to facilitate
domain separation.
There isn't anything terribly clever going on here, but if you want
repeatability you need to spell out all of the details explicitly.
That's what this document is about.
## Copyright and License
Copyright (C) 2023 Robert Quattlebaum. All rights reserved.
This use of this document is hereby granted under the terms of the
Creative Commons International Attribution 4.0 Public License, as
published by Creative Commons.
*
This work is provided as-is. Unless otherwise provided in writing, the
authors make no representations or warranties of any kind concerning
this work, express, implied, statutory or otherwise, including without
limitation warranties of title, merchantability, fitness for a
particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether
or not discoverable, all to the greatest extent permissible under
applicable law.
## Disclaimer
This document is an informal work-in-progress, may
contain errors, and is subject to change at any time.
## Secrets
In this document, a "Secret" refers specifically to a 256-bit
master secret from which the rest of the algorithms described in
this specification may be performed on. It is defined to be created
in one of the following ways:
1. Randomly from a cryptographic random number generator.
2. From a passphrase or other low-entropy source using Argon2.
3. From a high-entropy source using `HKDF-Extract`.
The value of a Secret is intended to be suitable for direct use
as the PRK to HKDF-Expand.
## Mutating Secrets
In order to facilitate domain separation, Secrets may be mutated
with a salt or "label" using `HKDF-Extract`, where the
`IKM` is the original Secret and the `salt` is supplied as a
parameter of the mutation.
## Deriving Pseudo-Random Byte Strings
* Input
* `secret`: Secret
* `len`: Number of Bytes
* Output
* An array of `len` pseudorandom bytes.
Pseudorandom bytes may be derived from a Secret using
HKDF-Expand with `info` set to the byte string `"\x00Bytes_v1"`,
where `\x00` is interpreted as a zero byte.
Domain separation is achieved by mutating the Secret using a label
or salt value.
## Deriving Pseudo-Random Integers
* Input
* `secret`: Secret
* `max`: Maximum integer value
* Output
* A pseudo-random integer between 0 and `max`.
The algorithm internally uses big-endian representations.
Let's start out with the value `0x001337FF` as `max`.
First, we transform `max` into a big-endian array of bytes.
So `max` becomes `[00 13 37 FF]`.
We then strip all the leading zero bytes. This makes
`max` become `[13 37 FF]`.
Next we calculate a value named `enclosing_mask`, which is
calculated from the first (most-significant) byte in `max`:
fn enclosing_mask_u8(mut x: u8) -> u8 {
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x
}
let enclosing_mask = enclosing_mask_u8(max[0]);
Because the first byte in `max` has the value 0x13,
`enclosing_mask` is set to the value 0x1f.
We then start our loop. In our calculation loop, we do the
following:
1. Mutate `secret` to be the result of `HKDF-Extract` using the
trimmed bytes in `max` as the salt and the previous value of
`secret` as the `IKM`.
2. Derive the same number of bytes in `max` (using the procedure outlined
above) to the byte array `out`.
3. Apply `enclosing_mask` to the first byte of `out`.
(i.e.: `out[0] &= enclosing_mask`).
4. Perform a lexicographical comparison between `out` and `max`. If `out`
comes after `max`, then go to step 1.
At this out `out` contains our derived integer in big-endian byte form,
and may then be converted to whatever other sort of integer representation
you might need.
## Deriving Pseudo-Random Primes
* Input
* `secret`: Secret
* `bit_length`: the number of bits that this prime should have.
* Output
* A pseudo-random prime between 0 and `max`.
1. Ensure that `bit_length` is greater than or equal to `4`.
2. The underlying secret is first mutated with the salt `"\x00Prime_v1"`.
3. Calculate the integer `max` so that `max = (1<= 256`, return an error, since this should basically never happen.
* If `mod_length < 256`, recalculate `prime2` until it does not. Note that this
will only happen when generating "toy" keys that offer no security.
By convention, the larger prime is considered `P` and the smaller prime is
considered `Q`. These primes may then be used to calculate the other
components of the RSA key as described in other literature.
The exponent of the key is always assumed be 65537.
### RSA Security Analysis
The algorithm is largely based on the implementation
from OpenSSL's `RSA_generate_key()` method, as seen
[here](https://opensource.apple.com/source/OpenSSL097/OpenSSL097-16/openssl/crypto/rsa/rsa_gen.c).
This is a straightforward algorithm that avoids doing the
sorts of checks that are extremely unlikely to ever be
triggered assuming modern (>2048 bits) key lengths are
used, such as:
* Determining that P and Q differ by at least 2^100
* Ensuring that neither (P-1) nor (Q-1) have of lots of small factors
When generating large keys, it is far more likely that
the source keying material has been compromised than
randomly generating a large key that happens to not also
satisfy those sorts of constraints. So the only check we
make against P and Q is ensuring that E is not a factor
of (P-1) or (Q-1), which is required for RSA to work at
all.
As a result, the security of RSA keys generated by this
algorithm for smaller key sizes (<=1024 bits) *may* be suspect.
However, you shouldn't be using 1024-bit keys anyway.
## Deriving ECC Private Keys
* Input
* `secret`: Secret
* `order`: The prime order of the curve.
* Output
* `out`: The private key scalar for the given prime order.
> **NOTE**: This process MUST NOT be used to generate the private keys
> for the following: `Ed25519`, `X25519`, `Ed448`, or `X448`.
> The private key values for these are calculated differently.
Steps:
1. The underlying secret is first mutated with the salt `"\x00EC_v1"`.
2. Derive a pseudo-random integer between 0 and `order` using the algorithm
described above and assign that value to `out`.
3. Assert that `out` is not zero. (This is, of course, extremely unlikely)
The value `out` may now be used as the scalar for the private key
on the given curve.
## Deriving `Ed25519`, `X25519`, `Ed448`, or `X448` private keys
* Input
* `secret`: Secret
* Output
* `out`: The private key bytes
Steps:
1. The underlying secret is first mutated with a salt dependent on
the type of key desired:
* Ed25519: `"\x00ED25519"`
* X25519: `"\x00X25519"`
* Ed448: `"\x00ED448"`
* X448: `"\x00X448"`
2. Derive a byte string `out` with the appropriate number of bytes
for the given system:
* Ed25519/X25519: 32
* Ed448/X448: 56
The byte array `out` may now be used as the private key.
## Deriving Passwords
TODO: Writeme!
## Deriving Secrets from Passphrases
Secrets are derived from passphrases using [ARGON2][]
with the following parameters:
* Version: 1.3
* Variant: ARGON2ID
* Iterations: 3
* Memory Size: 262,144kiB (256MiB)
* Lanes: 4
* Output Tag Length: 32 Bytes
* Salt: ASCII String "MSecret_Passphrase_v1"
The output tag is used directly as the Secret.
[ARGON2]: https://www.rfc-editor.org/rfc/rfc9106.html
## Splitting Secrets into Shares Using SSS-GF(256)
TODO: Writeme!
## Test Vectors
See the [test vector document](TEST_VECTORS.md).
## References
TODO: Writeme!