Skip to content

Commit

Permalink
make derive_key take a key of any length
Browse files Browse the repository at this point in the history
The previous version of this API called for a key of exactly 256 bits.
That's good for optimal performance, but it would mean losing the
use-with-other-algorithms property for applications whose input keys are
a different size. There's no way for an abstraction over the previous
version to provide reliable domain separation for the "extract" step.
  • Loading branch information
oconnor663 committed Dec 28, 2019
1 parent ba28064 commit 2fac744
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 91 deletions.
9 changes: 5 additions & 4 deletions b3sum/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ fn clap_parse_argv() -> clap::ArgMatches<'static> {
Arg::with_name(DERIVE_KEY_ARG)
.long(DERIVE_KEY_ARG)
.conflicts_with(KEYED_ARG)
.requires(FILE_ARG)
.help("Uses the KDF mode, with the 32-byte key read from stdin"),
.takes_value(true)
.value_name("CONTEXT")
.help("Uses the key derivation mode, with the input as key material"),
)
.arg(
Arg::with_name(NO_NAMES_ARG)
Expand Down Expand Up @@ -160,8 +161,8 @@ fn main() -> Result<()> {
.context("Failed to parse length.")?;
let base_hasher = if args.is_present(KEYED_ARG) {
blake3::Hasher::new_keyed(&read_key_from_stdin()?)
} else if args.is_present(DERIVE_KEY_ARG) {
blake3::Hasher::new_derive_key(&read_key_from_stdin()?)
} else if let Some(context) = args.value_of(DERIVE_KEY_ARG) {
blake3::Hasher::new_derive_key(context)
} else {
blake3::Hasher::new()
};
Expand Down
11 changes: 6 additions & 5 deletions b3sum/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@ fn test_keyed() {

#[test]
fn test_derive_key() {
let key = [99; blake3::KEY_LEN];
let context = "BLAKE3 2019-12-28 10:28:41 example context";
let f = tempfile::NamedTempFile::new().unwrap();
f.as_file().write_all(b"context").unwrap();
f.as_file().write_all(b"key material").unwrap();
f.as_file().flush().unwrap();
let expected = hex::encode(blake3::derive_key(&key, b"context"));
let output = cmd!(b3sum_exe(), "--derive-key", "--no-names", f.path())
.stdin_bytes(&key[..])
let mut derive_key_out = [0; blake3::OUT_LEN];
blake3::derive_key(context, b"key material", &mut derive_key_out);
let expected = hex::encode(&derive_key_out);
let output = cmd!(b3sum_exe(), "--derive-key", context, "--no-names", f.path())
.read()
.unwrap();
assert_eq!(&*expected, &*output);
Expand Down
18 changes: 12 additions & 6 deletions reference_impl/reference_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const CHUNK_END: u32 = 1 << 1;
const PARENT: u32 = 1 << 2;
const ROOT: u32 = 1 << 3;
const KEYED_HASH: u32 = 1 << 4;
const DERIVE_KEY: u32 = 1 << 5;
const DERIVE_KEY_CONTEXT: u32 = 1 << 5;
const DERIVE_KEY_MATERIAL: u32 = 1 << 6;

const IV: [u32; 8] = [
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
Expand Down Expand Up @@ -272,11 +273,16 @@ impl Hasher {
Self::new_internal(key_words, KEYED_HASH)
}

/// Construct a new `Hasher` for the key derivation function.
pub fn new_derive_key(key: &[u8; KEY_LEN]) -> Self {
let mut key_words = [0; 8];
words_from_litte_endian_bytes(key, &mut key_words);
Self::new_internal(key_words, DERIVE_KEY)
/// Construct a new `Hasher` for the key derivation function. The context
/// string should be hardcoded, globally unique, and application-specific.
pub fn new_derive_key(context: &str) -> Self {
let mut context_hasher = Hasher::new_internal(IV, DERIVE_KEY_CONTEXT);
context_hasher.update(context.as_bytes());
let mut context_key = [0; KEY_LEN];
context_hasher.finalize(&mut context_key);
let mut context_key_words = [0; 8];
words_from_litte_endian_bytes(&context_key, &mut context_key_words);
Self::new_internal(context_key_words, DERIVE_KEY_MATERIAL)
}

fn push_stack(&mut self, cv: [u32; 8]) {
Expand Down
72 changes: 39 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ const CHUNK_END: u8 = 1 << 1;
const PARENT: u8 = 1 << 2;
const ROOT: u8 = 1 << 3;
const KEYED_HASH: u8 = 1 << 4;
const DERIVE_KEY: u8 = 1 << 5;
const DERIVE_KEY_CONTEXT: u8 = 1 << 5;
const DERIVE_KEY_MATERIAL: u8 = 1 << 6;

fn counter_low(counter: u64) -> u32 {
counter as u32
Expand Down Expand Up @@ -663,29 +664,32 @@ pub fn keyed_hash(key: &[u8; KEY_LEN], input: &[u8]) -> Hash {

/// The key derivation function.
///
/// Given a 32-byte (256-bit) cryptographic key and a context string, this
/// function returns a 32-byte derived subkey. Key derivation is important when
/// you want to use the same key in multiple algorithms or use cases. Deriving
/// a separate subkey for each use case protects you from bad interactions
/// between algorithms. (For example, one algorithm might publish some value
/// that another algorithm considers secret.) Derived keys also mitigate the
/// damage from one part of your application accidentally leaking its key.
/// Given cryptographic key material of any length and a context string of any
/// length, this function outputs a derived subkey of any length. **The context
/// string should be hardcoded, globally unique, and application-specific.** A
/// good default format for such strings is `"[application] [commit timestamp]
/// [purpose]"`, e.g., `"example.com 2019-12-25 16:18:03 session tokens v1"`.
///
/// The context string should be globally unique, application-specific, and
/// hardcoded. A good default format for such strings is `"[application]
/// [commit timestamp] [purpose]"`, e.g., `"example.com 2019-12-25 16:18:03
/// session tokens v1"`.
/// Key derivation is important when you want to use the same key in multiple
/// algorithms or use cases. Using the same key with different cryptographic
/// algorithms is generally forbidden, and deriving a separate subkey for each
/// use case protects you from bad interactions. Derived keys also mitigate the
/// damage from one part of your application accidentally leaking its key.
///
/// If your input key is some size other than 32 bytes, you can convert it to
/// 32 bytes using [`hash`]. This is similar to the "extract" stage in the
/// "extract-then-expand" paradigm of HKDF.
/// As a rare exception to that general rule, however, it is possible to use
/// `derive_key` with key material that you are already using with another
/// algorithm. You might need to do this if you're adding features to an
/// existing application, which does not yet use key derivation internally.
/// However, you still must not share key material with algorithms that forbid
/// key reuse entirely, like a one-time pad.
///
/// [`hash`]: fn.hash.html
pub fn derive_key(key: &[u8; KEY_LEN], context: &[u8]) -> [u8; OUT_LEN] {
let key_words = platform::words_from_le_bytes_32(key);
hash_all_at_once(context, &key_words, DERIVE_KEY)
.root_hash()
.into()
/// [`Hasher::new_derive_key`]: struct.Hasher.html#method.new_derive_key
/// [`Hasher::finalize_xof`]: struct.Hasher.html#method.finalize_xof
pub fn derive_key(context: &str, key_material: &[u8], output: &mut [u8]) {
let context_key = hash_all_at_once(context.as_bytes(), IV, DERIVE_KEY_CONTEXT).root_hash();
let context_key_words = platform::words_from_le_bytes_32(context_key.as_bytes());
let inner_output = hash_all_at_once(key_material, &context_key_words, DERIVE_KEY_MATERIAL);
OutputReader::new(inner_output).fill(output);
}

fn parent_node_output(
Expand Down Expand Up @@ -741,16 +745,14 @@ impl Hasher {
}

/// Construct a new `Hasher` for the key derivation function. See
/// [`derive_key`].
///
/// Note that the input to [`derive_key`] should be a hardcoded context
/// string. Most callers that don't need extended output should prefer the
/// standalone function for that reason.
/// [`derive_key`]. The context string should be hardcoded, globally
/// unique, and application-specific.
///
/// [`derive_key`]: fn.derive_key.html
pub fn new_derive_key(key: &[u8; KEY_LEN]) -> Self {
let key_words = platform::words_from_le_bytes_32(key);
Self::new_internal(&key_words, DERIVE_KEY)
pub fn new_derive_key(context: &str) -> Self {
let context_key = hash_all_at_once(context.as_bytes(), IV, DERIVE_KEY_CONTEXT).root_hash();
let context_key_words = platform::words_from_le_bytes_32(context_key.as_bytes());
Self::new_internal(&context_key_words, DERIVE_KEY_MATERIAL)
}

// See comment in push_cv.
Expand Down Expand Up @@ -990,10 +992,7 @@ impl Hasher {
///
/// [`OutputReader`]: struct.OutputReader.html
pub fn finalize_xof(&self) -> OutputReader {
OutputReader {
inner: self.final_output(),
position_within_block: 0,
}
OutputReader::new(self.final_output())
}
}

Expand Down Expand Up @@ -1030,6 +1029,13 @@ pub struct OutputReader {
}

impl OutputReader {
fn new(inner: Output) -> Self {
Self {
inner,
position_within_block: 0,
}
}

/// Fill a buffer with output bytes and advance the position of the
/// `OutputReader`. This is equivalent to [`Read::read`], except that it
/// doesn't return a `Result`. Both methods always fill the entire buffer.
Expand Down
20 changes: 11 additions & 9 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub fn test_hash_many_fn(
&TEST_KEY_WORDS,
counter,
IncrementCounter::Yes,
crate::DERIVE_KEY,
crate::KEYED_HASH,
crate::CHUNK_START,
crate::CHUNK_END,
&mut portable_chunks_out,
Expand All @@ -128,7 +128,7 @@ pub fn test_hash_many_fn(
&TEST_KEY_WORDS,
counter,
IncrementCounter::Yes,
crate::DERIVE_KEY,
crate::KEYED_HASH,
crate::CHUNK_START,
crate::CHUNK_END,
&mut test_chunks_out,
Expand All @@ -154,7 +154,7 @@ pub fn test_hash_many_fn(
&TEST_KEY_WORDS,
0,
IncrementCounter::No,
crate::DERIVE_KEY | crate::PARENT,
crate::KEYED_HASH | crate::PARENT,
0,
0,
&mut portable_parents_out,
Expand All @@ -167,7 +167,7 @@ pub fn test_hash_many_fn(
&TEST_KEY_WORDS,
0,
IncrementCounter::No,
crate::DERIVE_KEY | crate::PARENT,
crate::KEYED_HASH | crate::PARENT,
0,
0,
&mut test_parents_out,
Expand Down Expand Up @@ -298,17 +298,19 @@ fn test_compare_reference_impl() {

// derive_key
{
let mut reference_hasher = reference_impl::Hasher::new_derive_key(&TEST_KEY);
let context = "BLAKE3 2019-12-27 16:13:59 example context";
let mut reference_hasher = reference_impl::Hasher::new_derive_key(context);
reference_hasher.update(input);
let mut expected_out = [0; OUT];
reference_hasher.finalize(&mut expected_out);

let test_out = crate::derive_key(&TEST_KEY, input);
assert_eq!(&test_out, array_ref!(expected_out, 0, 32));
let mut hasher = crate::Hasher::new_derive_key(&TEST_KEY);
let mut test_out = [0; OUT];
crate::derive_key(context, input, &mut test_out);
assert_eq!(&test_out[..], &expected_out[..]);
let mut hasher = crate::Hasher::new_derive_key(context);
hasher.update(input);
assert_eq!(&hasher.finalize(), array_ref!(expected_out, 0, 32));
assert_eq!(&hasher.finalize(), &test_out);
assert_eq!(&hasher.finalize(), array_ref!(test_out, 0, 32));
let mut extended = [0; OUT];
hasher.finalize_xof().fill(&mut extended);
assert_eq!(&extended[..], &expected_out[..]);
Expand Down
40 changes: 28 additions & 12 deletions test_vectors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,21 @@ pub const TEST_CASES: &[usize] = &[
31 * CHUNK_LEN, // 16 + 8 + 4 + 2 + 1
];

pub const TEST_KEY: &[u8; blake3::KEY_LEN] = b"whats the Elvish word for friend";
pub const TEST_CONTEXT: &str = "BLAKE3 2019-12-27 16:29:52 test vectors context";

const COMMENT: &str = r#"
Each test is an input length and three outputs, one for each of the hash,
keyed_hash, and derive_key modes. The input in each case is filled with a
251-byte-long repeating pattern: 0, 1, 2, ..., 249, 250, 0, 1, ... The key used
with keyed_hash is the 32-byte ASCII string given in the 'key' field below. For
derive_key, the test input is used as the input key, and the context string is
'BLAKE3 2019-12-27 16:29:52 example context'. (As good practice for following
the security requirements of derive_key, test runners should make that context
string a hardcoded constant, and we do not provided it in machine-readable
form.) Outputs are encoded as hexadecimal. Each case is an extended output, and
implementations should also check that the first 32 bytes match their
default-length output.
"#;

// Paint the input with a repeating byte pattern. We use a cycle length of 251,
// because that's the largets prime number less than 256. This makes it
Expand Down Expand Up @@ -57,6 +71,8 @@ pub struct Case {
}

pub fn generate_json() -> String {
const TEST_KEY: &[u8; blake3::KEY_LEN] = b"whats the Elvish word for friend";

let mut cases = Vec::new();
for &input_len in TEST_CASES {
let mut input = vec![0; input_len];
Expand All @@ -75,7 +91,7 @@ pub fn generate_json() -> String {
.fill(&mut keyed_hash_out);

let mut derive_key_out = [0; OUTPUT_LEN];
blake3::Hasher::new_derive_key(TEST_KEY)
blake3::Hasher::new_derive_key(TEST_CONTEXT)
.update(&input)
.finalize_xof()
.fill(&mut derive_key_out);
Expand All @@ -89,10 +105,11 @@ pub fn generate_json() -> String {
}

let mut json = serde_json::to_string_pretty(&Cases {
_comment: "Each test is an input length and three outputs, one for each of the hash, keyed_hash, and derive_key modes. The input in each case is filled with a 251-byte-long repeating pattern: 0, 1, 2, ..., 249, 250, 0, 1, ... The key used with keyed_hash and derive_key is the 32-byte ASCII string given below. Outputs are encoded as hexadecimal. Each case is an extended output, and implementations should also check that the first 32 bytes match their default-length output.".to_string(),
_comment: COMMENT.trim().replace("\n", " "),
key: std::str::from_utf8(TEST_KEY).unwrap().to_string(),
cases,
}).unwrap();
})
.unwrap();

// Add a trailing newline.
json.push('\n');
Expand Down Expand Up @@ -134,7 +151,7 @@ mod tests {
assert_eq!(expected_keyed_hash, &out[..]);

let mut out = vec![0; expected_derive_key.len()];
let mut hasher = reference_impl::Hasher::new_derive_key(key);
let mut hasher = reference_impl::Hasher::new_derive_key(TEST_CONTEXT);
hasher.update(input);
hasher.finalize(&mut out);
assert_eq!(expected_derive_key, &out[..]);
Expand Down Expand Up @@ -164,7 +181,7 @@ mod tests {
assert_eq!(expected_keyed_hash, &out[..]);

let mut out = vec![0; expected_derive_key.len()];
let mut hasher = reference_impl::Hasher::new_derive_key(key);
let mut hasher = reference_impl::Hasher::new_derive_key(TEST_CONTEXT);
for &b in input {
hasher.update(&[b]);
}
Expand Down Expand Up @@ -194,7 +211,7 @@ mod tests {
assert_eq!(&expected_keyed_hash[..32], hasher.finalize().as_bytes());

let mut out = vec![0; expected_derive_key.len()];
let mut hasher = blake3::Hasher::new_derive_key(key);
let mut hasher = blake3::Hasher::new_derive_key(TEST_CONTEXT);
hasher.update(input);
hasher.finalize_xof().fill(&mut out);
assert_eq!(expected_derive_key, &out[..]);
Expand Down Expand Up @@ -227,7 +244,7 @@ mod tests {
assert_eq!(&expected_keyed_hash[..32], hasher.finalize().as_bytes());

let mut out = vec![0; expected_derive_key.len()];
let mut hasher = blake3::Hasher::new_derive_key(key);
let mut hasher = blake3::Hasher::new_derive_key(TEST_CONTEXT);
for &b in input {
hasher.update(&[b]);
}
Expand All @@ -248,10 +265,9 @@ mod tests {
&expected_keyed_hash[..32],
&blake3::keyed_hash(key, input).as_bytes()[..],
);
assert_eq!(
&expected_derive_key[..32],
&blake3::derive_key(key, input)[..],
);
let mut derive_key_out = vec![0; expected_derive_key.len()];
blake3::derive_key(TEST_CONTEXT, input, &mut derive_key_out);
assert_eq!(expected_derive_key, &derive_key_out[..],);
}

#[test]
Expand Down
Loading

0 comments on commit 2fac744

Please sign in to comment.