From 9059e067351f0a913728e509b82b019ca1450122 Mon Sep 17 00:00:00 2001 From: royb Date: Sun, 25 Oct 2026 08:21:01 -0400 Subject: [PATCH] Add BLAKE2b-512, BLAKE2s-256 (RFC 7693) and BLAKE3 (#123) Adds the three BLAKE hashes requested in #123 for OpenSSL provider provisioning, on the key-object API of feature/key-rework-memory (#172): two new crates wired into the factories, the CLI and the stack-memory benches. Other BLAKE2 digest lengths, salt/personalisation, BLAKE2bp/sp/X and tree mode are out of scope. blake2 (new crate bouncycastle-blake2): - BLAKE2b_512 and BLAKE2s_256 implement Hash and Suspendable; G, F, the parameter block and the final-t rule are commented against RFC 7693 s. 3.1-3.3. blake2b.rs and blake2s.rs are kept structurally identical. - The native keyed mode (not HMAC): key objects BLAKE2b_512_MACKey and BLAKE2s_256_MACKey (FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey) hand out BLAKE2b_512_MAC / BLAKE2s_256_MAC workers. Key checks run type, then strength, then length (1..=64 / 1..=32). - AlgorithmOID: id-blake2b512 and id-blake2s256 (RFC 7693 s. 4), on the hash and MAC types. blake3 (new crate bouncycastle-blake3): - BLAKE3 implements Hash and XOF in hash mode (new) and derive_key mode (new_derive_key); BLAKE3Squeezer streams the extended output. - keyed_hash: key object BLAKE3KeyedHashKey (exactly 32 bytes) hands out BLAKE3KeyedHash workers, which keep an inherent into_squeezer for extended output. BLAKE3KeyedSqueezer is deliberately not Suspendable. - The engine follows the reference implementation's chunk state and 54-entry CV stack (spec s. 2.4-2.6, 5.1). Both crates: - MAC output truncates to the caller's buffer with a 4-byte floor (MIN_MAC_OUTPUT_LEN); every _out function zeroizes the buffer before any error check. Partial-bit input: 0 bits is a whole-byte message, 1..=7 returns InvalidInput, more returns InvalidLength. - Keyed workers implement SuspendableKeyed with the key object as the key. Suspended states hold no key bytes, and resume rebuilds the key-derived fields from the key object. - Design note: a suspended keyed state still lets its holder compute tags for extensions of the absorbed prefix (BLAKE2 once the key block is compressed; BLAKE3 only within the first chunk). HMAC does not have this property. It is documented and pinned by tests. factory: HashFactory gains BLAKE2b-512, BLAKE2s-256 and BLAKE3; XOFFactory gains BLAKE3; MACFactory gains the three BLAKE MAC keys and workers. cli: blake2b-512, blake2s-256, blake2b-512-mac, blake2s-256-mac, blake3, blake3-keyed-hash and blake3-derive-key, all streaming. cli/tests/ test_blake.sh adds 16 tests on the shell harness. tests: RFC 7693 Appendix A/B, selections from the official BLAKE2 KAT, the bc-test-data Blake2KAT.txt sweep (512 + 512 records), all 35 official BLAKE3 vectors in all three modes, and the core-test-framework suites for Hash, XOF, MAC keys, key construction and suspend/resume. Wycheproof has no BLAKE vectors. cargo mutants: blake2 378 tested, 10 missed; blake3 384 tested, 9 missed, 8 timeouts (non-terminating loops); every miss is an equivalent rewrite with a "Mutants note:" at the site. #![no_std] remains a todo in both crates, as in every hash crate, blocked by the Vec return types in core::traits::Hash. Assisted-by: Claude:claude-mythos-5-1 Assisted-by: Claude:claude-fable-5-1 Assisted-by: Claude:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 --- Cargo.toml | 4 + cli/src/blake2_cmd.rs | 52 + cli/src/blake3_cmd.rs | 84 ++ cli/src/helpers/mod.rs | 50 +- cli/src/mac_cmd.rs | 2 +- cli/src/main.rs | 153 +++ cli/tests/test_blake.sh | 279 ++++ crypto/blake2/Cargo.toml | 18 + crypto/blake2/benches/blake2_benches.rs | 56 + crypto/blake2/src/blake2b.rs | 701 ++++++++++ crypto/blake2/src/blake2s.rs | 700 ++++++++++ crypto/blake2/src/lib.rs | 199 +++ crypto/blake2/tests/bc_test_data.rs | 152 +++ crypto/blake2/tests/blake2b_tests.rs | 711 ++++++++++ crypto/blake2/tests/blake2s_tests.rs | 711 ++++++++++ crypto/blake3/Cargo.toml | 18 + crypto/blake3/benches/blake3_benches.rs | 56 + crypto/blake3/src/blake3.rs | 1173 +++++++++++++++++ crypto/blake3/src/compress.rs | 159 +++ crypto/blake3/src/lib.rs | 166 +++ crypto/blake3/tests/blake3_tests.rs | 566 ++++++++ crypto/blake3/tests/keyed_hash_tests.rs | 389 ++++++ crypto/blake3/tests/vectors/mod.rs | 245 ++++ crypto/factory/Cargo.toml | 2 + crypto/factory/src/hash_factory.rs | 54 + crypto/factory/src/mac_factory.rs | 36 + crypto/factory/src/xof_factory.rs | 28 + crypto/factory/tests/hash_factory_tests.rs | 135 +- crypto/factory/tests/mac_factory_tests.rs | 158 ++- crypto/factory/tests/xof_factory_tests.rs | 111 +- mem_usage_benches/Cargo.toml | 8 + .../src/bench_blake2_mem_usage.rs | 170 +++ .../src/bench_blake3_mem_usage.rs | 166 +++ mem_usage_benches/src/lib.rs | 2 + src/lib.rs | 2 + 35 files changed, 7510 insertions(+), 6 deletions(-) create mode 100644 cli/src/blake2_cmd.rs create mode 100644 cli/src/blake3_cmd.rs create mode 100755 cli/tests/test_blake.sh create mode 100644 crypto/blake2/Cargo.toml create mode 100644 crypto/blake2/benches/blake2_benches.rs create mode 100644 crypto/blake2/src/blake2b.rs create mode 100644 crypto/blake2/src/blake2s.rs create mode 100644 crypto/blake2/src/lib.rs create mode 100644 crypto/blake2/tests/bc_test_data.rs create mode 100644 crypto/blake2/tests/blake2b_tests.rs create mode 100644 crypto/blake2/tests/blake2s_tests.rs create mode 100644 crypto/blake3/Cargo.toml create mode 100644 crypto/blake3/benches/blake3_benches.rs create mode 100644 crypto/blake3/src/blake3.rs create mode 100644 crypto/blake3/src/compress.rs create mode 100644 crypto/blake3/src/lib.rs create mode 100644 crypto/blake3/tests/blake3_tests.rs create mode 100644 crypto/blake3/tests/keyed_hash_tests.rs create mode 100644 crypto/blake3/tests/vectors/mod.rs create mode 100644 mem_usage_benches/src/bench_blake2_mem_usage.rs create mode 100644 mem_usage_benches/src/bench_blake3_mem_usage.rs diff --git a/Cargo.toml b/Cargo.toml index 11e1f13cc..7793e275d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ bouncycastle = { path = "./" } bouncycastle-aes = { path = "./crypto/aes" } bouncycastle-ascon = { path = "./crypto/ascon" } bouncycastle-base64 = { path = "./crypto/base64" } +bouncycastle-blake2 = { path = "./crypto/blake2" } +bouncycastle-blake3 = { path = "./crypto/blake3" } bouncycastle-cipher = { path = "./crypto/cipher" } bouncycastle-core = { path = "crypto/core" } bouncycastle-core-test-framework = { path = "./crypto/core-test-framework" } @@ -50,6 +52,8 @@ edition.workspace = true bouncycastle-aes.workspace = true bouncycastle-ascon.workspace = true bouncycastle-base64.workspace = true +bouncycastle-blake2.workspace = true +bouncycastle-blake3.workspace = true bouncycastle-cipher.workspace = true bouncycastle-core.workspace = true bouncycastle-factory.workspace = true diff --git a/cli/src/blake2_cmd.rs b/cli/src/blake2_cmd.rs new file mode 100644 index 000000000..813a37371 --- /dev/null +++ b/cli/src/blake2_cmd.rs @@ -0,0 +1,52 @@ +//! BLAKE2b-512 and BLAKE2s-256 (RFC 7693): the unkeyed hashes and their built-in keyed (MAC) mode. + +use bouncycastle::blake2::{BLAKE2b_512, BLAKE2b_512_MACKey, BLAKE2s_256, BLAKE2s_256_MACKey}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; + +use crate::helpers::{load_blake_mac_key, stream_hash}; +use crate::mac_cmd::do_mac; + +#[allow(non_camel_case_types)] +pub(crate) enum BLAKE2Variant { + BLAKE2b_512, + BLAKE2s_256, +} + +/// BLAKE2b-512 / BLAKE2s-256 of stdin, streamed in 1 KiB pieces. +pub(crate) fn blake2_cmd(variant: BLAKE2Variant, output_hex: bool) { + match variant { + BLAKE2Variant::BLAKE2b_512 => stream_hash(BLAKE2b_512::new(), output_hex), + BLAKE2Variant::BLAKE2s_256 => stream_hash(BLAKE2s_256::new(), output_hex), + } +} + +/// Keyed BLAKE2b-512 / BLAKE2s-256 (RFC 7693 s. 3.3) of stdin: compute a tag, or with `--verify` +/// check one and report the result in the exit status. +pub(crate) fn blake2_mac_cmd( + variant: BLAKE2Variant, + key: &Option, + key_file: &Option, + verify_val: &Option, + output_hex: bool, +) { + // RFC 7693 s. 2.1 table, "Key bytes": "0 <= kk <= 64" (BLAKE2b) / "0 <= kk <= 32" (BLAKE2s). + // kk = 0 is the unkeyed hash, which is the other command, so a MAC key is at least 1 byte. + // + // The allow-weak form, as the HMAC and KMAC commands use: a CLI is used for test vectors and + // scripting, where a short or all-zero key is a legitimate thing to want. + match variant { + BLAKE2Variant::BLAKE2b_512 => { + let key = load_blake_mac_key(key, key_file, "BLAKE2b-512-MAC", 1, 64); + // infallible: a 1..=64-byte MACKey (or Zeroized) key is exactly what the key object + // accepts once the strength check is skipped. + let mac_key = BLAKE2b_512_MACKey::from_key_material_allow_weak(&key).unwrap(); + do_mac(mac_key.create_generator(), verify_val, output_hex); + } + BLAKE2Variant::BLAKE2s_256 => { + let key = load_blake_mac_key(key, key_file, "BLAKE2s-256-MAC", 1, 32); + // infallible: as above, with the 1..=32-byte range of BLAKE2s. + let mac_key = BLAKE2s_256_MACKey::from_key_material_allow_weak(&key).unwrap(); + do_mac(mac_key.create_generator(), verify_val, output_hex); + } + } +} diff --git a/cli/src/blake3_cmd.rs b/cli/src/blake3_cmd.rs new file mode 100644 index 000000000..e800b18d7 --- /dev/null +++ b/cli/src/blake3_cmd.rs @@ -0,0 +1,84 @@ +//! BLAKE3 (BLAKE3 specification, version 20211102173700): the `hash`, `keyed_hash` and +//! `derive_key` modes, each with output of any length. + +use std::io::{self, Read}; +use std::process::exit; + +use bouncycastle::blake3::{BLAKE3, BLAKE3KeyedHashKey}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle::core::traits::{MAC, XOFSqueezer}; + +use crate::helpers::{load_blake_mac_key, stream_xof, write_bytes_or_hex, write_stdout}; +use crate::mac_cmd::do_mac; + +/// BLAKE3 spec s. 2.3: `keyed_hash` "takes an input of any length together with a 256-bit key". +/// The crate has no public constant for it. +const BLAKE3_KEY_LEN: usize = 32; + +/// The default-length BLAKE3 output: BLAKE3 spec s. 2.1, "The chaining value of the root node, +/// encoded as 32 bytes in little-endian order, is the default-length BLAKE3 hash of the input." +pub(crate) const BLAKE3_DEFAULT_OUT_LEN: usize = 32; + +/// BLAKE3 `hash` mode of stdin, streamed in 1 KiB pieces, squeezing `output_len` bytes. +pub(crate) fn blake3_cmd(output_len: usize, output_hex: bool) { + stream_xof(BLAKE3::new(), output_len, output_hex); +} + +/// BLAKE3 `derive_key` mode: the key material is streamed from stdin, the context string is the +/// argument, and `output_len` bytes of derived key are written out. +/// +/// BLAKE3 spec s. 6.2: the context string "should be hardcoded, globally unique, and +/// application-specific"; this command takes it as an argument only so that scripts can name the +/// application's fixed string. +pub(crate) fn blake3_derive_key_cmd(context: &str, output_len: usize, output_hex: bool) { + stream_xof(BLAKE3::new_derive_key(context.as_bytes()), output_len, output_hex); +} + +/// BLAKE3 `keyed_hash` mode of stdin: write `output_len` bytes of keyed output, or with `--verify` +/// check a 32-byte tag and report the result in the exit status. +pub(crate) fn blake3_keyed_hash_cmd( + key: &Option, + key_file: &Option, + verify_val: &Option, + output_len: usize, + output_hex: bool, +) { + // Verification goes through MAC::do_verify_final, which compares the default 32-byte tag in + // constant time and requires an exact length match; an extended output length has no + // meaning there, so refuse the combination rather than silently ignore --length. + if verify_val.is_some() && output_len != BLAKE3_DEFAULT_OUT_LEN { + eprintln!( + "Error: --verify checks the {BLAKE3_DEFAULT_OUT_LEN}-byte tag and cannot be combined \ + with --length." + ); + exit(-1); + } + + let key = load_blake_mac_key(key, key_file, "BLAKE3-KeyedHash", BLAKE3_KEY_LEN, BLAKE3_KEY_LEN); + // The allow-weak form, as the HMAC and KMAC commands use: a CLI is used for test vectors and + // scripting, where a low-entropy or all-zero key is a legitimate thing to want. + // infallible: a 32-byte MACKey (or Zeroized) key is exactly what the key object accepts once + // the strength check is skipped. + let mac_key = BLAKE3KeyedHashKey::from_key_material_allow_weak(&key).unwrap(); + let mut mac = mac_key.create_generator(); + + if verify_val.is_some() { + // do_mac streams stdin and, given a value to verify, exits with the result. + do_mac(mac, verify_val, output_hex); + return; + } + + // Hand-rolled rather than a helper: stream_xof needs an XOF and do_mac finishes with + // MAC::do_final, but extended keyed output needs the inherent into_squeezer afterwards. + let mut buf: [u8; 1024] = [0u8; 1024]; + let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin"); + while bytes_read != 0 { + mac.do_update(&buf[..bytes_read]); + bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin"); + } + + // BLAKE3 spec s. 2.6: the keyed output stream, of which the MAC tag is the first 32 bytes. + let out = mac.into_squeezer().do_output_final(output_len); + write_bytes_or_hex(&out, output_hex); + write_stdout(b"\n"); +} diff --git a/cli/src/helpers/mod.rs b/cli/src/helpers/mod.rs index eb56485d9..50e7924d2 100644 --- a/cli/src/helpers/mod.rs +++ b/cli/src/helpers/mod.rs @@ -1,5 +1,5 @@ use bouncycastle::core::hazmat::do_hazardous_operations; -use bouncycastle::core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle::core::key_material::{KeyMaterial, KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle::core::security_strength::SecurityStrength; use bouncycastle::core::traits::{Hash, XOF, XOFSqueezer}; use bouncycastle::hex; @@ -58,6 +58,54 @@ pub(crate) fn hex_or_raw(buf: Vec) -> Vec { } } +/// Loads a MAC key from `--key` (hex) or `--key-file` (raw bytes), tagged as a MAC key, for the +/// BLAKE2 and BLAKE3 keyed commands. +/// +/// `max_len` is the longest key the algorithm takes; a longer key is refused here with a message +/// naming the limit, rather than as an opaque error from the algorithm's constructor. An all-zero +/// key comes back tagged [`KeyType::Zeroized`], which the BLAKE key objects accept, as HMAC's +/// and KMAC's do. +pub(crate) fn load_blake_mac_key( + key: &Option, + key_file: &Option, + alg_name: &str, + min_len: usize, + max_len: usize, +) -> KeyMaterial512 { + // The file wins if both are given, as the HMAC/KMAC help text promises (their loader, + // `mac_cmd::load_mac_key`, currently does the opposite). + let key_bytes: Vec = if let Some(file) = key_file { + read_from_file_raw(file) + } else if let Some(k) = key { + hex::decode(k).unwrap_or_else(|_| { + eprintln!("Error: --key is not valid hex."); + exit(-1) + }) + } else { + eprintln!("Error: either `key` or `key-file` must be supplied."); + exit(-1) + }; + + if key_bytes.len() < min_len || key_bytes.len() > max_len { + if min_len == max_len { + eprintln!( + "Error: {alg_name} requires a {min_len}-byte key; got {} bytes.", + key_bytes.len() + ); + } else { + eprintln!( + "Error: {alg_name} requires a key of {min_len} to {max_len} bytes; got {} bytes.", + key_bytes.len() + ); + } + exit(-1); + } + + // infallible: the length is at most 64 (checked above, and no caller passes max_len > 64), + // which fits a KeyMaterial512, and MACKey is a valid type to load. + KeyMaterial512::from_bytes_as_type(&key_bytes, KeyType::MACKey).unwrap() +} + /// Reads either bin or hex pub(crate) fn read_from_file(filename: &str) -> Vec { let file = File::open(&filename); diff --git a/cli/src/mac_cmd.rs b/cli/src/mac_cmd.rs index a7386192c..a3031130e 100644 --- a/cli/src/mac_cmd.rs +++ b/cli/src/mac_cmd.rs @@ -109,7 +109,7 @@ pub(crate) fn kmac_cmd( } } -fn do_mac(mut mac: impl MAC, verify_val: &Option, output_hex: bool) { +pub(crate) fn do_mac(mut mac: impl MAC, verify_val: &Option, output_hex: bool) { // read the content to be MAC'd from stdin let mut buf: [u8; 1024] = [0u8; 1024]; let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin"); diff --git a/cli/src/main.rs b/cli/src/main.rs index 1377e4b33..89ed58d1f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -6,6 +6,8 @@ mod aes_ctr_cmd; mod aes_ecb_cmd; mod aes_gcm_cmd; mod ascon_cmd; +mod blake2_cmd; +mod blake3_cmd; mod encoders_cmd; mod helpers; mod hkdf_cmd; @@ -17,6 +19,7 @@ mod sha2_cmd; mod sha3_cmd; mod sm3_cmd; +use crate::blake2_cmd::BLAKE2Variant; use crate::mac_cmd::HMACVariant; use crate::mldsa_cmd::MLDSAAction; use crate::sha2_cmd::SHA2Variant; @@ -1006,6 +1009,137 @@ enum Subcommands { /// Supports streaming for low memory footprint and continuous processing from stdin to stdout. Base64Encode, + /// Perform BLAKE2b-512 (RFC 7693) of the content provided on stdin. + /// Supports streaming update for low memory footprint. + BLAKE2b_512 { + #[arg(short)] + /// Output the digest in hex format. + x: bool, + }, + + /// Perform BLAKE2s-256 (RFC 7693) of the content provided on stdin. + /// Supports streaming update for low memory footprint. + BLAKE2s_256 { + #[arg(short)] + /// Output the digest in hex format. + x: bool, + }, + + /// Perform keyed BLAKE2b-512 (RFC 7693 Sec 3.3, BLAKE2's built-in MAC; not HMAC) of the + /// content provided on stdin. The key is 1 to 64 bytes. + /// Supports streaming update for low memory footprint. + /// Note: in production uses, secrets should not be passed on the command-line because they get + /// logged in shell history. Use the file-based input instead. + BLAKE2b_512_MAC { + /// The MAC key in hex. + /// The `key_file` option is preferred to avoid leaving key material in command history. + #[arg(long)] + key: Option, + + /// A file containing the MAC key in binary. + /// If both key and key_file options are provided, the file will be used. + #[arg(short, long)] + key_file: Option, + + /// A MAC value to be verified. + /// The command will output either 0 for success or -1 for verification failure. + #[arg(short, long)] + verify: Option, + + #[arg(short)] + /// Output the tag in hex format. + x: bool, + }, + + /// Perform keyed BLAKE2s-256 (RFC 7693 Sec 3.3, BLAKE2's built-in MAC; not HMAC) of the + /// content provided on stdin. The key is 1 to 32 bytes. + /// Supports streaming update for low memory footprint. + /// Note: in production uses, secrets should not be passed on the command-line because they get + /// logged in shell history. Use the file-based input instead. + BLAKE2s_256_MAC { + /// The MAC key in hex. + /// The `key_file` option is preferred to avoid leaving key material in command history. + #[arg(long)] + key: Option, + + /// A file containing the MAC key in binary. + /// If both key and key_file options are provided, the file will be used. + #[arg(short, long)] + key_file: Option, + + /// A MAC value to be verified. + /// The command will output either 0 for success or -1 for verification failure. + #[arg(short, long)] + verify: Option, + + #[arg(short)] + /// Output the tag in hex format. + x: bool, + }, + + /// Perform BLAKE3 of the content provided on stdin, with output of any length (32 bytes by + /// default). Shorter outputs are prefixes of longer ones. + /// Supports streaming update for low memory footprint. + BLAKE3 { + /// Length of the output in bytes. + #[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)] + length: usize, + + #[arg(short)] + /// Output in hex format. + x: bool, + }, + + /// Perform BLAKE3 in keyed_hash mode (a MAC and PRF) of the content provided on stdin. The key + /// must be exactly 32 bytes. Output of any length (32 bytes by default); --verify checks the + /// 32-byte tag. + /// Supports streaming update for low memory footprint. + /// Note: in production uses, secrets should not be passed on the command-line because they get + /// logged in shell history. Use the file-based input instead. + BLAKE3_KeyedHash { + /// The 32-byte MAC key in hex. + /// The `key_file` option is preferred to avoid leaving key material in command history. + #[arg(long)] + key: Option, + + /// A file containing the 32-byte MAC key in binary. + /// If both key and key_file options are provided, the file will be used. + #[arg(short, long)] + key_file: Option, + + /// A 32-byte tag (hex) to be verified. Cannot be combined with --length. + /// The command will output either 0 for success or -1 for verification failure. + #[arg(short, long)] + verify: Option, + + /// Length of the output in bytes. + #[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)] + length: usize, + + #[arg(short)] + /// Output in hex format. + x: bool, + }, + + /// Perform BLAKE3 in derive_key mode: derive a key of any length (32 bytes by default) from + /// the key material provided on stdin, under a context string. The context string should be + /// hardcoded, globally unique and application-specific (BLAKE3 spec Sec 6.2). + /// Supports streaming update for low memory footprint. + BLAKE3_DeriveKey { + /// The context string, e.g. "example.com 2019-12-25 16:18:03 session tokens v1" (BLAKE3 + /// spec Sec 6.2). + #[arg(short, long)] + context: String, + + /// Length of the derived key in bytes. + #[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)] + length: usize, + + #[arg(short)] + /// Output in hex format. + x: bool, + }, + /// The HashML-DSA-44 signature algorithm. HashMLDSA44 { action: MLDSAAction, @@ -1894,6 +2028,25 @@ fn run() { Some(Subcommands::Base64Encode) => { encoders_cmd::base64_encode_cmd(); } + Some(Subcommands::BLAKE2b_512 { x }) => { + blake2_cmd::blake2_cmd(BLAKE2Variant::BLAKE2b_512, *x); + } + Some(Subcommands::BLAKE2s_256 { x }) => { + blake2_cmd::blake2_cmd(BLAKE2Variant::BLAKE2s_256, *x); + } + Some(Subcommands::BLAKE2b_512_MAC { key, key_file, verify, x }) => { + blake2_cmd::blake2_mac_cmd(BLAKE2Variant::BLAKE2b_512, key, key_file, verify, *x) + } + Some(Subcommands::BLAKE2s_256_MAC { key, key_file, verify, x }) => { + blake2_cmd::blake2_mac_cmd(BLAKE2Variant::BLAKE2s_256, key, key_file, verify, *x) + } + Some(Subcommands::BLAKE3 { length, x }) => blake3_cmd::blake3_cmd(*length, *x), + Some(Subcommands::BLAKE3_KeyedHash { key, key_file, verify, length, x }) => { + blake3_cmd::blake3_keyed_hash_cmd(key, key_file, verify, *length, *x) + } + Some(Subcommands::BLAKE3_DeriveKey { context, length, x }) => { + blake3_cmd::blake3_derive_key_cmd(context, *length, *x) + } Some(Subcommands::HashMLDSA44 { action, ctxfile, skfile, pkfile, sigfile, x }) => { mldsa_cmd::hash_mldsa44_sha512_cmd(action, ctxfile, skfile, pkfile, sigfile, *x); } diff --git a/cli/tests/test_blake.sh b/cli/tests/test_blake.sh new file mode 100755 index 000000000..24a6ee4ab --- /dev/null +++ b/cli/tests/test_blake.sh @@ -0,0 +1,279 @@ +#!/usr/bin/env bash +# The blake2b-512 / blake2s-256 / blake2b-512-mac / blake2s-256-mac / blake3 / blake3-keyed-hash +# / blake3-derive-key subcommands, end to end through the binary: known answers in hex and binary, +# --length, --key / --key-file loading, key-length rejection and the --verify exit codes. +# +# The known-answer values were copied by script, never retyped, from the vectors embedded in the +# algorithm crates' tests: for BLAKE2, `RFC_ABC` (RFC 7693 Appendix A / B) and the `KAT` table +# (from the official BLAKE2 KAT, github.com/BLAKE2/BLAKE2 testvectors/blake2-kat.json; message +# bytes 0, 1, ..., len - 1, key bytes 0, 1, ..., kk - 1) in crypto/blake2/tests/blake2{b,s}_tests.rs; +# for BLAKE3, crypto/blake3/tests/vectors/mod.rs (from the official +# github.com/BLAKE3-team/BLAKE3 test_vectors/test_vectors.json; input bytes i % 251). + +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +# RFC 7693 Appendix A / B: BLAKE2b-512("abc") / BLAKE2s-256("abc"). +BLAKE2B_ABC=ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923 +BLAKE2S_ABC=508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982 +# blake2-kat.json, message 0, 1, ..., 254: unkeyed, and keyed with the bytes 0, 1, ..., kk - 1. +BLAKE2B_255=5b21c5fd8868367612474fa2e70e9cfa2201ffeee8fafab5797ad58fefa17c9b5b107da4a3db6320baaf2c8617d5a51df914ae88da3867c2d41f0cc14fa67928 +BLAKE2B_255_KEYED=142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461 +BLAKE2S_255=f03f5789d3336b80d002d59fdf918bdb775b00956ed5528e86aa994acb38fe2d +BLAKE2S_255_KEYED=3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd +# blake2-kat.json, empty message, keyed. +BLAKE2B_0_KEYED=10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568 +BLAKE2S_0_KEYED=48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49 +# test_vectors.json `key` and `context_string` fields. +BLAKE3_KEY="whats the Elvish word for friend" +BLAKE3_CONTEXT="BLAKE3 2019-12-27 16:29:52 test vectors context" +# test_vectors.json, input_len 0: the 131-byte hash, keyed_hash and derive_key outputs. +BLAKE3_0_HASH=af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a26f5487789e8f660afe6c99ef9e0c52b92e7393024a80459cf91f476f9ffdbda7001c22e159b402631f277ca96f2defdf1078282314e763699a31c5363165421cce14d +BLAKE3_0_KEYED=92b2b75604ed3c761f9d6f62392c8a9227ad0ea3f09573e783f1498a4ed60d26b18171a2f22a4b94822c701f107153dba24918c4bae4d2945c20ece13387627d3b73cbf97b797d5e59948c7ef788f54372df45e45e4293c7dc18c1d41144a9758be58960856be1eabbe22c2653190de560ca3b2ac4aa692a9210694254c371e851bc8f +BLAKE3_0_DERIVE=2cc39783c223154fea8dfb7c1b1660f2ac2dcbd1c1de8277b0b0dd39b7e50d7d905630c8be290dfcf3e6842f13bddd573c098c3f17361f1f206b8cad9d088aa4a3f746752c6b0ce6a83b0da81d59649257cdf8eb3e9f7d4998e41021fac119deefb896224ac99f860011f73609e6e0e4540f93b273e56547dfd3aa1a035ba6689d89a0 +# test_vectors.json, input_len 1025: the 131-byte hash, keyed_hash and derive_key outputs. +BLAKE3_1025_HASH=d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444f4c4a22b4b399155358a994e52bf255de60035742ec71bd08ac275a1b51cc6bfe332b0ef84b409108cda080e6269ed4b3e2c3f7d722aa4cdc98d16deb554e5627be8f955c98e1d5f9565a9194cad0c4285f93700062d9595adb992ae68ff12800ab67a +BLAKE3_1025_KEYED=357dc55de0c7e382c900fd6e320acc04146be01db6a8ce7210b7189bd664ea69362396b77fdc0d2634a552970843722066c3c15902ae5097e00ff53f1e116f1cd5352720113a837ab2452cafbde4d54085d9cf5d21ca613071551b25d52e69d6c81123872b6f19cd3bc1333edf0c52b94de23ba772cf82636cff4542540a7738d5b930 +BLAKE3_1025_DERIVE=effaa245f065fbf82ac186839a249707c3bddf6d3fdda22d1b95a3c970379bcb5d31013a167509e9066273ab6e2123bc835b408b067d88f96addb550d96b6852dad38e320b9d940f86db74d398c770f462118b35d2724efa13da97194491d96dd37c3c09cbef665953f2ee85ec83d88b88d11547a6f911c8217cca46defa2751e7f3ad +# test_vectors.json, input_len 102400 (100 x the 1 KiB read buffer): the hash output. +BLAKE3_102400_HASH=bc3e3d41a1146b069abffad3c0d44860cf664390afce4d9661f7902e7943e085e01c59dab908c04c3342b816941a26d69c2605ebee5ec5291cc55e15b76146e6745f0601156c3596cb75065a9c57f35585a52e1ac70f69131c23d611ce11ee4ab1ec2c009012d236648e77be9295dd0426f29b764d65de58eb7d01dd42248204f45f8e + + +# ---- inputs --------------------------------------------------------------------------------- + +# `counting N` writes the bytes 0, 1, ..., N - 1 (mod 256): the BLAKE2 KAT message and key. +counting() { + local i out='' + for ((i = 0; i < $1; i++)); do out+=$(printf '%02x' $((i % 256))); done + unhex "$out" +} + +# `counting_hex N`: the same bytes, as hex. +counting_hex() { + counting "$1" | "$BC_RUST" hex-encode +} + +# `blake3_input N` writes the BLAKE3 test-vector input: "a repeating sequence of 251 bytes: 0, 1, +# 2, ..., 249, 250, 0, 1, ..." (test_vectors.json `_comment`). +blake3_input() { + local i block='' + for ((i = 0; i < 251; i++)); do block+=$(printf '%02x' "$i"); done + local reps=$(($1 / 251 + 1)) all='' + for ((i = 0; i < reps; i++)); do all+=$block; done + unhex "${all:0:$((2 * $1))}" +} + +# `bin_hex CMD...` runs CMD, whose binary output ends in the newline every digest command +# writes, and prints that output without the newline, as hex. +bin_hex() { + "$@" >"$TMP/.bin" + assert_eq "$(tail -c 1 "$TMP/.bin" | "$BC_RUST" hex-encode)" 0a "binary output ends in a newline" + head -c -1 "$TMP/.bin" | "$BC_RUST" hex-encode +} + +# ---- blake2b-512 / blake2s-256 -------------------------------------------------------------- + +test_blake2_hashes_match_rfc7693_appendices() { + printf abc >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake2b-512 -x <"$TMP/msg")" "$BLAKE2B_ABC" "BLAKE2b-512(abc)" + assert_eq "$(hex_out "$BC_RUST" blake2s-256 -x <"$TMP/msg")" "$BLAKE2S_ABC" "BLAKE2s-256(abc)" +} + +# A two-block (BLAKE2b) / four-block (BLAKE2s) message from the official KAT, in hex and binary. +test_blake2_hashes_match_the_official_kat_in_hex_and_binary() { + counting 255 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake2b-512 -x <"$TMP/msg")" "$BLAKE2B_255" "blake2b hex" + assert_eq "$(bin_hex "$BC_RUST" blake2b-512 <"$TMP/msg")" "$BLAKE2B_255" "blake2b binary" + assert_eq "$(hex_out "$BC_RUST" blake2s-256 -x <"$TMP/msg")" "$BLAKE2S_255" "blake2s hex" + assert_eq "$(bin_hex "$BC_RUST" blake2s-256 <"$TMP/msg")" "$BLAKE2S_255" "blake2s binary" +} + +# ---- blake2b-512-mac / blake2s-256-mac ------------------------------------------------------ + +test_blake2_macs_match_the_official_keyed_kat_in_hex_and_binary() { + local kb ks + counting 255 >"$TMP/msg" + kb=$(counting_hex 64) + ks=$(counting_hex 32) + assert_eq "$(hex_out "$BC_RUST" blake2b-512-mac --key "$kb" -x <"$TMP/msg")" \ + "$BLAKE2B_255_KEYED" "blake2b-512-mac hex" + assert_eq "$(bin_hex "$BC_RUST" blake2b-512-mac --key "$kb" <"$TMP/msg")" \ + "$BLAKE2B_255_KEYED" "blake2b-512-mac binary" + assert_eq "$(hex_out "$BC_RUST" blake2b-512-mac --key "$kb" -x "$TMP/msg" + expect_ok "the right tag verifies" "$BC_RUST" "$sub" --key "$key" -v "$tag" <"$TMP/msg" + assert_eq "$("$BC_RUST" "$sub" --key "$key" -v "$tag" <"$TMP/msg")" "" "verify writes nothing" + expect_fail "a wrong tag fails" \ + "$BC_RUST" "$sub" --key "$key" -v "$(flip_hex "$tag" 0 1)" <"$TMP/msg" + expect_fail "a truncated tag fails" \ + "$BC_RUST" "$sub" --key "$key" -v "${tag:0:$((${#tag} - 2))}" <"$TMP/msg" +} + +test_blake2b_512_mac_verify_exit_codes() { + blake2_verify blake2b-512-mac 64 "$BLAKE2B_255_KEYED" +} + +test_blake2s_256_mac_verify_exit_codes() { + blake2_verify blake2s-256-mac 32 "$BLAKE2S_255_KEYED" +} + +# The key file holds raw bytes, like the HMAC commands' --key-file; it wins over --key. +test_blake2_mac_key_file_is_loaded_as_raw_bytes() { + counting 255 >"$TMP/msg" + counting 64 >"$TMP/key64" + counting 32 >"$TMP/key32" + assert_eq "$(hex_out "$BC_RUST" blake2b-512-mac --key-file "$TMP/key64" -x <"$TMP/msg")" \ + "$BLAKE2B_255_KEYED" "blake2b-512-mac --key-file" + assert_eq "$(hex_out "$BC_RUST" blake2s-256-mac -k "$TMP/key32" -x <"$TMP/msg")" \ + "$BLAKE2S_255_KEYED" "blake2s-256-mac -k" + assert_eq "$(hex_out "$BC_RUST" blake2s-256-mac -k "$TMP/key32" --key 00 -x <"$TMP/msg")" \ + "$BLAKE2S_255_KEYED" "the key file wins over --key" +} + +# RFC 7693 s. 2.1 table, "Key bytes": "0 <= kk <= 64" (BLAKE2b) / "0 <= kk <= 32" (BLAKE2s); +# kk = 0 is the unkeyed hash, which is the other command. +test_blake2_mac_rejects_bad_keys() { + local sub + printf abc >"$TMP/msg" + expect_fail "a 33-byte key" "$BC_RUST" blake2s-256-mac --key "$(counting_hex 33)" <"$TMP/msg" + assert_stderr_has "1 to 32 bytes" + expect_fail "a 65-byte key" "$BC_RUST" blake2b-512-mac --key "$(counting_hex 65)" <"$TMP/msg" + assert_stderr_has "1 to 64 bytes" + for sub in blake2b-512-mac blake2s-256-mac; do + expect_fail "$sub: an empty key" "$BC_RUST" "$sub" --key "" <"$TMP/msg" + assert_stderr_has "got 0 bytes" + expect_fail "$sub: no key" "$BC_RUST" "$sub" <"$TMP/msg" + assert_stderr_has "must be supplied" + expect_fail "$sub: bad hex" "$BC_RUST" "$sub" --key "not hex" <"$TMP/msg" + assert_stderr_has "not valid hex" + done +} + +# ---- blake3 --------------------------------------------------------------------------------- + +test_blake3_default_length_is_the_first_32_bytes_of_the_vector() { + blake3_input 1025 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3 -x "$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3 --length 131 -x <"$TMP/msg")" "$BLAKE3_1025_HASH" \ + "131 bytes" + assert_eq "$(bin_hex "$BC_RUST" blake3 -l 131 <"$TMP/msg")" "$BLAKE3_1025_HASH" \ + "131 bytes, binary" + for len in 0 1 31 33 63 64 65 100; do + assert_eq "$(hex_out "$BC_RUST" blake3 -l "$len" -x <"$TMP/msg")" \ + "${BLAKE3_1025_HASH:0:$((2 * len))}" "length $len" + done +} + +# 100 KiB is many 1 KiB reads and many 1 KiB chunks: the streaming loop and the CV stack together. +test_blake3_streams_a_long_input() { + blake3_input 102400 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3 -x <"$TMP/msg")" "${BLAKE3_102400_HASH:0:64}" "100 KiB" +} + +# ---- blake3-keyed-hash ---------------------------------------------------------------------- + +test_blake3_keyed_hash_matches_the_vector_in_hex_and_binary() { + local key + key=$(printf '%s' "$BLAKE3_KEY" | "$BC_RUST" hex-encode) + blake3_input 1025 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3-keyed-hash --key "$key" -x <"$TMP/msg")" \ + "${BLAKE3_1025_KEYED:0:64}" "hex" + assert_eq "$(bin_hex "$BC_RUST" blake3-keyed-hash --key "$key" <"$TMP/msg")" \ + "${BLAKE3_1025_KEYED:0:64}" "binary" + assert_eq "$(hex_out "$BC_RUST" blake3-keyed-hash --key "$key" --length 131 -x <"$TMP/msg")" \ + "$BLAKE3_1025_KEYED" "131 bytes" + assert_eq "$(hex_out "$BC_RUST" blake3-keyed-hash --key "$key" -x "$TMP/msg" + expect_ok "the right tag verifies" "$BC_RUST" blake3-keyed-hash --key "$key" -v "$tag" <"$TMP/msg" + assert_eq "$("$BC_RUST" blake3-keyed-hash --key "$key" -v "$tag" <"$TMP/msg")" "" \ + "verify writes nothing" + expect_fail "a wrong tag fails" \ + "$BC_RUST" blake3-keyed-hash --key "$key" -v "$(flip_hex "$tag" 31 128)" <"$TMP/msg" + # The tag is the 32-byte default output; neither a prefix nor the extended output verifies. + expect_fail "a truncated tag fails" \ + "$BC_RUST" blake3-keyed-hash --key "$key" -v "${tag:0:62}" <"$TMP/msg" + expect_fail "a 33-byte tag fails" \ + "$BC_RUST" blake3-keyed-hash --key "$key" -v "${BLAKE3_1025_KEYED:0:66}" <"$TMP/msg" + # --verify checks the 32-byte tag, so a different --length is refused, not ignored. + expect_fail "--verify with --length" \ + "$BC_RUST" blake3-keyed-hash --key "$key" -v "$tag" -l 64 <"$TMP/msg" + assert_stderr_has "cannot be combined with --length" +} + +test_blake3_keyed_hash_key_file_is_loaded_as_raw_bytes() { + printf '%s' "$BLAKE3_KEY" >"$TMP/key" + blake3_input 1025 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3-keyed-hash -k "$TMP/key" -x <"$TMP/msg")" \ + "${BLAKE3_1025_KEYED:0:64}" "key file" +} + +# BLAKE3 spec s. 2.3: keyed_hash takes "a 256-bit key"; any other length is refused with a +# message and a non-zero exit, whether it comes from --key or --key-file. +test_blake3_keyed_hash_rejects_a_key_that_is_not_32_bytes() { + local len hex + hex=$(printf '%s%s' "$BLAKE3_KEY" "$BLAKE3_KEY" | "$BC_RUST" hex-encode) + printf abc >"$TMP/msg" + for len in 0 1 16 31 33 64; do + expect_fail "a $len-byte key" \ + "$BC_RUST" blake3-keyed-hash --key "${hex:0:$((2 * len))}" <"$TMP/msg" + assert_stderr_has "requires a 32-byte key" + done + printf '%s' "${BLAKE3_KEY:0:31}" >"$TMP/key31" + expect_fail "a 31-byte key file" "$BC_RUST" blake3-keyed-hash -k "$TMP/key31" <"$TMP/msg" + assert_stderr_has "got 31 bytes" +} + +# ---- blake3-derive-key ---------------------------------------------------------------------- + +test_blake3_derive_key_matches_the_vector() { + blake3_input 1025 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3-derive-key --context "$BLAKE3_CONTEXT" -x <"$TMP/msg")" \ + "${BLAKE3_1025_DERIVE:0:64}" "hex" + assert_eq "$(bin_hex "$BC_RUST" blake3-derive-key --context "$BLAKE3_CONTEXT" <"$TMP/msg")" \ + "${BLAKE3_1025_DERIVE:0:64}" "binary" + assert_eq "$(hex_out "$BC_RUST" blake3-derive-key -c "$BLAKE3_CONTEXT" -l 131 -x <"$TMP/msg")" \ + "$BLAKE3_1025_DERIVE" "131 bytes" + assert_eq "$(hex_out "$BC_RUST" blake3-derive-key -c "$BLAKE3_CONTEXT" -x "$TMP/msg" + other=$(hex_out "$BC_RUST" blake3-derive-key -c "another context" -x <"$TMP/msg") + [ "$other" != "${BLAKE3_1025_DERIVE:0:64}" ] || fail "the context must change the key" + [ "$other" != "${BLAKE3_1025_HASH:0:64}" ] || fail "derive_key is not the plain hash" + expect_fail "no context" "$BC_RUST" blake3-derive-key -x <"$TMP/msg" +} + +run_all diff --git a/crypto/blake2/Cargo.toml b/crypto/blake2/Cargo.toml new file mode 100644 index 000000000..ad177ed97 --- /dev/null +++ b/crypto/blake2/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bouncycastle-blake2" +version.workspace = true +edition.workspace = true + +[dependencies] +bouncycastle-core.workspace = true +bouncycastle-utils.workspace = true + +[dev-dependencies] +criterion.workspace = true +bouncycastle-core-test-framework.workspace = true +bouncycastle-hex.workspace = true +bouncycastle-rng.workspace = true + +[[bench]] +name = "blake2_benches" +harness = false diff --git a/crypto/blake2/benches/blake2_benches.rs b/crypto/blake2/benches/blake2_benches.rs new file mode 100644 index 000000000..58eb20c63 --- /dev/null +++ b/crypto/blake2/benches/blake2_benches.rs @@ -0,0 +1,56 @@ +//! Criterion benches for BLAKE2b-512 and BLAKE2s-256: 16 KiB of input per iteration. +//! +//! The keyed (MAC) types share the hash engine and differ only by one extra key block, so they are +//! not benchmarked separately (QUALITY_AND_STYLE.md "Performance Benchmarks"). + +use criterion::{Criterion, Throughput, criterion_group, criterion_main}; +use std::hint::black_box; + +use bouncycastle_blake2::{BLAKE2b_512, BLAKE2s_256}; +use bouncycastle_core::traits::{Hash, RNG}; +use bouncycastle_rng as rng; + +fn bench_blake2b_512(c: &mut Criterion) { + let mut data = [0_u8; 1024]; + rng::DefaultRNG::default().next_bytes_out(&mut data).unwrap(); + + let mut digest = vec![0; BLAKE2b_512::new().output_len()]; + + let mut group = c.benchmark_group("blake2b-512"); + group.throughput(Throughput::Bytes(16 * 1024)); + group.bench_function("16KiB", |b| { + b.iter(|| { + let mut md = BLAKE2b_512::new(); + for _ in 0..16 { + md.do_update(black_box(&data)); + } + _ = md.do_final_out(&mut digest); + black_box(&digest); + }) + }); + group.finish(); +} + +fn bench_blake2s_256(c: &mut Criterion) { + let mut data = [0_u8; 1024]; + rng::DefaultRNG::default().next_bytes_out(&mut data).unwrap(); + + let mut digest = vec![0; BLAKE2s_256::new().output_len()]; + + let mut group = c.benchmark_group("blake2s-256"); + group.throughput(Throughput::Bytes(16 * 1024)); + group.bench_function("16KiB", |b| { + b.iter(|| { + let mut md = BLAKE2s_256::new(); + for _ in 0..16 { + md.do_update(black_box(&data)); + } + _ = md.do_final_out(&mut digest); + black_box(&digest); + }) + }); + group.finish(); +} + +criterion_group!(benches, bench_blake2b_512, bench_blake2s_256); +criterion_main!(benches); diff --git a/crypto/blake2/src/blake2b.rs b/crypto/blake2/src/blake2b.rs new file mode 100644 index 000000000..6a341b956 --- /dev/null +++ b/crypto/blake2/src/blake2b.rs @@ -0,0 +1,701 @@ +//! BLAKE2b (RFC 7693) with a 64-byte digest: the unkeyed hash [`BLAKE2b_512`] and the keyed MAC +//! key [`BLAKE2b_512_MACKey`] with its worker [`BLAKE2b_512_MAC`]. +//! +//! This file and `blake2s.rs` are deliberately structurally identical -- RFC 7693 s. 2.1 describes +//! one algorithm with two parameter columns -- so that a reviewer can diff them. Keep them in step. + +use crate::MIN_MAC_OUTPUT_LEN; +use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; +use bouncycastle_core::hazmat::do_hazardous_operations; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +use bouncycastle_core::traits::{Hash, MAC, RNG, Suspendable, SuspendableKeyed}; +use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_utils::{ct, min, secret::Secret}; + +/// RFC 7693 s. 2.1: "Block bytes | bb = 128". +const BB: usize = 128; + +/// RFC 7693 s. 2.1: "Hash bytes | 1 <= nn <= 64". This crate implements only nn = 64. +const NN: usize = 64; + +/// RFC 7693 s. 2.1: "Key bytes | 0 <= kk <= 64". +const MAX_KK: usize = 64; + +/// RFC 7693 s. 2.1: "Rounds in F | r = 12". +const R: usize = 12; + +/// RFC 7693 s. 2.1: "G Rotation constants = (32, 24, 16, 63)". +const R1: u32 = 32; +const R2: u32 = 24; +const R3: u32 = 16; +const R4: u32 = 63; + +/// RFC 7693 s. 2.6: IV[i] = floor(2**w * frac(sqrt(prime(i+1)))), w = 64; values as in Appendix C.2 +/// `blake2b_iv` ("BLAKE2b IV is the same as SHA-512 IV"). +const IV: [u64; 8] = [ + 0x6A09E667F3BCC908, 0xBB67AE8584CAA73B, 0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, 0x9B05688C2B3E6C1F, 0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179, +]; + +/// RFC 7693 s. 2.7: message schedule SIGMA[0..9]. "For BLAKE2b, the two extra permutations for +/// rounds 10 and 11 are SIGMA[10..11] = SIGMA[0..1]", which F realises as `SIGMA[i mod 10]`. +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// RFC 7693 s. 3.1: mixing function G. +#[inline] +fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + // v[a] := (v[a] + v[b] + x) mod 2**w + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + // v[d] := (v[d] ^ v[a]) >>> R1 + v[d] = (v[d] ^ v[a]).rotate_right(R1); + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = v[c].wrapping_add(v[d]); + // v[b] := (v[b] ^ v[c]) >>> R2 + v[b] = (v[b] ^ v[c]).rotate_right(R2); + // v[a] := (v[a] + v[b] + y) mod 2**w + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + // v[d] := (v[d] ^ v[a]) >>> R3 + v[d] = (v[d] ^ v[a]).rotate_right(R3); + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = v[c].wrapping_add(v[d]); + // v[b] := (v[b] ^ v[c]) >>> R4 + v[b] = (v[b] ^ v[c]).rotate_right(R4); +} + +/// RFC 7693 s. 3.2: compression function F( h[0..7], m[0..15], t, f ), updating `h` in place. +/// +/// The message words `m` are read from `block` little-endian (s. 2.4). The transient work vector +/// `v` and words `m` are plain stack locals and are not zeroized; see the crate's Security +/// Considerations. +fn compress(h: &mut [u64; 8], block: &[u8; BB], t: u128, f: bool) { + // s. 2.4: "Byte (octet) streams are interpreted as words in little-endian order". + let mut m = [0u64; 16]; + let (words, _) = block.as_chunks::<8>(); + for (mi, bytes) in m.iter_mut().zip(words) { + *mi = u64::from_le_bytes(*bytes); + } + + // // Initialize local work vector v[0..15] + let mut v = [0u64; 16]; + // v[0..7] := h[0..7] // First half from state. + v[..8].copy_from_slice(h); + // v[8..15] := IV[0..7] // Second half from IV. + v[8..].copy_from_slice(&IV); + + // v[12] := v[12] ^ (t mod 2**w) // Low word of the offset. + v[12] ^= t as u64; + // v[13] := v[13] ^ (t >> w) // High word. + // Mutants note: the high word of t is zero unless 2**64 or more bytes have been processed + // (16 EiB), far beyond what a test can hash, so `^=` -> `|=` and `>>` -> `<<` here (both a + // no-op on a zero high word) survive as equivalent on every testable input. + v[13] ^= (t >> 64) as u64; + + // IF f = TRUE THEN // last block flag? + if f { + // v[14] := v[14] ^ 0xFF..FF // Invert all bits. + v[14] ^= u64::MAX; + } + + // // Cryptographic mixing + // FOR i = 0 TO r - 1 DO // Ten or twelve rounds. + for i in 0..R { + // s[0..15] := SIGMA[i mod 10][0..15] + let s = &SIGMA[i % 10]; + + // v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] ) + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + // v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] ) + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + // v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] ) + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + // v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] ) + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + // v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] ) + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + // v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] ) + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + // v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] ) + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + // v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] ) + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + // FOR i = 0 TO 7 DO // XOR the two halves. + for i in 0..8 { + // h[i] := h[i] ^ v[i] ^ v[i + 8] + h[i] ^= v[i] ^ v[i + 8]; + } +} + +/// RFC 7693 s. 3.3: the initial state for key length `kk`. +/// +/// h[0..7] := IV[0..7]; h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn (the parameter block p[0] of +/// s. 2.5, "p[0] = 0x0101kknn"). +fn initial_h(kk: usize) -> Secret<[u64; 8]> { + let mut h = Secret::<[u64; 8]>::new(); + // h[0..7] := IV[0..7] // Initialization Vector. + h.copy_from_slice(&IV); + // // Parameter block p[0] + // h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn + // Mutants note: 0x01010000, kk << 8 and nn occupy disjoint bytes of p[0] (s. 2.5 + // "0x0101kknn", with kk and nn below 256), so `^` -> `|` between them is an equivalent mutant. + h[0] ^= 0x01010000 ^ ((kk as u64) << 8) ^ (NN as u64); + h +} + +/// The BLAKE2b engine shared by the hash and the MAC: RFC 7693 s. 3.3 processed incrementally, in +/// the shape of the Appendix C reference code. +/// +/// Invariant: the buffer is compressed only when it is full *and* more input arrives, so the final +/// block d[dd-1] is always still in `buf` when [`Self::finalize`] runs and can be flagged +/// `f = TRUE`. +#[derive(Clone)] +struct Blake2bCore { + /// The chaining state h[0..7]. + h: Secret<[u64; 8]>, + /// s. 2.2: "Message byte offset at the end of the current block" -- the total of the bytes in + /// every block compressed so far (key block included). s. 3.2 calls it a "2w-bit offset + /// counter", which for w = 64 is a u128. + t: u128, + /// The current, not yet compressed, block. In keyed mode this is the padded key block `d[0]` + /// exactly when `t == 0`. + buf: Secret<[u8; BB]>, + /// Number of valid bytes in `buf`, in `0..=BB`. + buf_len: usize, +} + +impl Blake2bCore { + /// The unkeyed state (kk = 0). + fn new_unkeyed() -> Self { + Self { h: initial_h(0), t: 0, buf: Secret::new(), buf_len: 0 } + } + + /// The keyed state. `key` must be 1..=64 bytes; the callers validate it. + fn new_keyed(key: &[u8]) -> Self { + debug_assert!(!key.is_empty() && key.len() <= MAX_KK); + let mut core = Self { h: initial_h(key.len()), t: 0, buf: Secret::new(), buf_len: 0 }; + core.load_key_block(key); + core + } + + /// s. 3.3: "If a secret key is used (kk > 0), it is padded with zero bytes and set as `d[0]`." + /// `d[0]` is left in the buffer as a full block, which is compressed as soon as any message + /// byte arrives, or as the final block if none does + /// (Appendix C.2 `blake2b_init`: `ctx->c = 128`). + fn load_key_block(&mut self, key: &[u8]) { + self.buf.fill(0); + self.buf[..key.len()].copy_from_slice(key); + self.buf_len = BB; + } + + /// Absorbs `data`, compressing every block that is known not to be the last one. + fn update(&mut self, data: &[u8]) { + let mut data = data; + let fill = BB - self.buf_len; + + // Only if `data` overflows the buffer is the buffered block known not to be final. + if data.len() > fill { + self.buf[self.buf_len..].copy_from_slice(&data[..fill]); + data = &data[fill..]; + // s. 3.3: h := F( h, d[i], (i + 1) * bb, FALSE ) -- t is the byte offset at the end + // of this block. + self.add_to_t(BB); + compress(&mut self.h, &self.buf, self.t, false); + self.buf_len = 0; + + // Whole blocks straight from the input, keeping at least one byte back so that the + // final block always ends up in the buffer: (len - 1) / bb blocks are each followed by + // more input (e.g. 128 bytes -> 0 blocks, 129 -> 1, 256 -> 1). + let num_blocks = data.len().saturating_sub(1) / BB; + let (blocks, _) = data.as_chunks::(); + for block in &blocks[..num_blocks] { + self.add_to_t(BB); + compress(&mut self.h, block, self.t, false); + } + data = &data[num_blocks * BB..]; + } + + // What is left fits: data.len() <= BB - buf_len. + self.buf[self.buf_len..self.buf_len + data.len()].copy_from_slice(data); + self.buf_len += data.len(); + } + + /// `t += n`. s. 2.1 bounds the input at "0 <= ll < 2**128" bytes, which `t` (ll, plus bb in + /// keyed mode) cannot exceed in practice; overflow is asserted in debug builds rather than + /// silently wrapping into a different hash. + fn add_to_t(&mut self, n: usize) { + debug_assert!(self.t.checked_add(n as u128).is_some(), "BLAKE2b message too long"); + self.t = self.t.wrapping_add(n as u128); + } + + /// RFC 7693 s. 3.3 final block, then "RETURN first "nn" bytes from little-endian word array + /// h[]". Zeroizes all of `output`, writes `min(output.len(), nn)` bytes and returns that count. + fn finalize(mut self, output: &mut [u8]) -> usize { + // Final block: IF kk = 0 THEN h := F( h, d[dd - 1], ll, TRUE ) + // ELSE h := F( h, d[dd - 1], ll + bb, TRUE ) + // `t` already counts every compressed block (the key block included), so adding the bytes + // of the last block gives ll, or ll + bb when keyed. The unkeyed empty message is the + // s. 3.3 special case "d[0] consists of all zeros" with t = 0. + self.add_to_t(self.buf_len); + // "The final data block d[dd-1] is also padded with zero to "bb" bytes". + self.buf[self.buf_len..].fill(0); + compress(&mut self.h, &self.buf, self.t, true); + + output.fill(0); + let n = *min(&output.len(), &NN); + // Little-endian words, truncated to n bytes. + for (chunk, word) in output[..n].chunks_mut(8).zip(self.h.iter()) { + chunk.copy_from_slice(&word.to_le_bytes()[..chunk.len()]); + } + n + } + + /// Serializes `h || t || buf || buf_len` (all little-endian) into `out`, which must be + /// zeroed. `buf` is written as its first `buf_len` bytes followed by zeros, or as all zeros + /// when `zero_buf` is set. + fn write_state(&self, out: &mut [u8; CORE_STATE_LEN], zero_buf: bool) { + let (words, _) = out[..64].as_chunks_mut::<8>(); + for (bytes, word) in words.iter_mut().zip(self.h.iter()) { + *bytes = word.to_le_bytes(); + } + out[64..80].copy_from_slice(&self.t.to_le_bytes()); + // Only the valid bytes: past buf_len the buffer still holds stale bytes of earlier + // blocks -- in keyed mode, of the key block d[0] -- which must not leak into the state. + if !zero_buf { + out[80..80 + self.buf_len].copy_from_slice(&self.buf[..self.buf_len]); + } + debug_assert!(self.buf_len <= BB); + out[208] = self.buf_len as u8; + } + + /// Inverse of [`Self::write_state`]. Rejects a `buf_len` larger than a block (a full block, + /// `buf_len == bb`, is valid: it is compressed only when more input arrives) and a `t` that is + /// not a whole number of blocks. + fn read_state(input: &[u8; CORE_STATE_LEN]) -> Result { + let buf_len = input[208] as usize; + if buf_len > BB { + return Err(SuspendableError::InvalidData); + } + + let mut h = Secret::<[u64; 8]>::new(); + let (words, _) = input[..64].as_chunks::<8>(); + for (hi, bytes) in h.iter_mut().zip(words) { + *hi = u64::from_le_bytes(*bytes); + } + let mut t_bytes = [0u8; 16]; + t_bytes.copy_from_slice(&input[64..80]); + let t = u128::from_le_bytes(t_bytes); + // A compression only ever happens on a full block (s. 3.3 "(i + 1) * bb"), so at rest t is + // a multiple of bb. For the keyed type this also rules out 0 < t < bb: the key block is + // compressed first, with t = bb. + if t % (BB as u128) != 0 { + return Err(SuspendableError::InvalidData); + } + let mut buf = Secret::<[u8; BB]>::new(); + buf.copy_from_slice(&input[80..208]); + + Ok(Self { h, t, buf, buf_len }) + } +} + +/// Length of the serialized engine state, without the version header. +const CORE_STATE_LEN: usize = 64 + 16 + BB + 1; + +/// Length in bytes of the serialized state of [`BLAKE2b_512`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian): +/// [0 .. 64) h [u64; 8] +/// [64 .. 80) t u128 (RFC 7693 s. 2.2 byte offset) +/// [80 .. 208) buf [u8; 128] +/// [208 .. 209) buf_len u8 (always <= 128) +pub const SUSPENDED_BLAKE2B_512_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// Length in bytes of the serialized state of [`BLAKE2b_512_MAC`]. +/// +/// Same layout as [`SUSPENDED_BLAKE2B_512_STATE_LEN`], except that `buf` is written as zeros while +/// it holds the padded key block (`t == 0`), so the key never appears in the state. The two lengths +/// are equal today; depend on the one for the type you are suspending. +pub const SUSPENDED_BLAKE2B_512_MAC_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// The BLAKE2b hash with a 64-byte digest, unkeyed (RFC 7693, kk = 0, nn = 64). +/// +/// See the [crate-level documentation](crate) for usage. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2b_512 { + core: Blake2bCore, +} + +impl BLAKE2b_512 { + /// Creates a new BLAKE2b-512 instance, ready for use. + pub fn new() -> Self { + Self { core: Blake2bCore::new_unkeyed() } + } +} + +impl Default for BLAKE2b_512 { + fn default() -> Self { + Self::new() + } +} + +impl Hash for BLAKE2b_512 { + /// RFC 7693 s. 2.1: "Block bytes | bb = 128", so 1024 bits. + fn block_bitlen(&self) -> usize { + BB * 8 + } + + fn output_len(&self) -> usize { + NN + } + + fn hash(self, data: &[u8]) -> Vec { + let mut output = vec![0u8; NN]; + self.hash_out(data, &mut output); + output + } + + fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut output = vec![0u8; NN]; + self.do_final_out(&mut output); + output + } + + fn do_final_out(self, output: &mut [u8]) -> usize { + self.core.finalize(output) + } + + fn do_final_partial_bits( + self, + partial_byte: u8, + num_bits: usize, + ) -> Result, HashError> { + let mut output = vec![0u8; NN]; + self.do_final_partial_bits_out(partial_byte, num_bits, &mut output)?; + Ok(output) + } + + /// BLAKE2 is byte-oriented (RFC 7693 s. 2.1 counts "Input bytes"), so it has no encoding for a + /// message that ends part-way through a byte. + /// + /// # Errors + /// * [`HashError::InvalidLength`] if `num_bits > 7`, as for every [`Hash`]. + /// * [`HashError::InvalidInput`] if `num_bits` is in `1..=7`. + /// + /// `num_bits == 0` means the message ended on a byte boundary and is exactly + /// [`Hash::do_final_out`]; `partial_byte` is ignored. + /// + /// This is the convention of Ascon-XOF128 / CXOF128 in this library. KMACXOF differs: it + /// returns [`HashError::InvalidLength`] for every non-zero `num_bits`. + fn do_final_partial_bits_out( + self, + _partial_byte: u8, + num_bits: usize, + output: &mut [u8], + ) -> Result { + output.fill(0); + if num_bits > 7 { + return Err(HashError::InvalidLength("num_partial_bits must be in the range [0,7]")); + } + if num_bits != 0 { + return Err(HashError::InvalidInput( + "BLAKE2 is byte-oriented (RFC 7693 s. 2.1) and cannot hash a partial final byte", + )); + } + Ok(self.do_final_out(output)) + } + + /// RFC 7693 s. 4: BLAKE2b-512 has "Collision Security 2**256". + fn max_security_strength(&self) -> SecurityStrength { + SecurityStrength::_256bit + } +} + +impl Suspendable for BLAKE2b_512 { + fn suspend(self) -> [u8; SUSPENDED_BLAKE2B_512_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE2B_512_STATE_LEN]; + let mut out = [0u8; CORE_STATE_LEN]; + self.core.write_state(&mut out, false); + // add_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + add_lib_ver(&mut out_to_return).copy_from_slice(&out); + out_to_return + } + + /// # Errors + /// * [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library + /// version. + /// * [`SuspendableError::InvalidData`] if the buffered length exceeds one block, if `t` is not + /// a whole number of blocks, or if a state taken before any compression (`t == 0`) does not + /// have the unkeyed initial `h` (for example a keyed state, or a tampered one). + fn from_suspended( + state: [u8; SUSPENDED_BLAKE2B_512_STATE_LEN], + ) -> Result { + // At the moment, we have no not_before version to specify. + // check_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + let mut input = [0u8; CORE_STATE_LEN]; + input.copy_from_slice(check_lib_ver(&state, None)?); + let core = Blake2bCore::read_state(&input)?; + // Before any compression h is exactly the unkeyed initial state (kk = 0), so anything else + // is a state of a different parameter block -- such as a keyed one -- or a corrupt one. + if core.t == 0 && *core.h != *initial_h(0) { + return Err(SuspendableError::InvalidData); + } + Ok(Self { core }) + } +} + +/// A BLAKE2b MAC key: RFC 7693 keyed BLAKE2b (kk = 1..=64, nn = 64), the key object for +/// [`BLAKE2b_512_MAC`]. +/// +/// This is the keyed BLAKE2b of the RFC ("BLAKE2BMAC" in OpenSSL), not HMAC-BLAKE2b. The key holds +/// the state after keyed initialisation (RFC 7693 s. 3.3), so every worker starts from it. +/// +/// # Keys +/// Material must be tagged [`KeyType::MACKey`] (or be all-zero, [`KeyType::Zeroized`]) and be 1 to +/// 64 bytes (RFC 7693 s. 2.1 "0 <= kk <= 64"). Unlike HMAC, a zero-length key is refused: +/// kk = 0 is the unkeyed hash, [`BLAKE2b_512`]. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2b_512_MACKey { + /// The engine after keyed initialisation: h with kk bound into p[0], t = 0, and the padded + /// key block `d[0]` in the buffer. + core: Blake2bCore, +} + +impl BLAKE2b_512_MACKey { + /// Validates material in the order: type, security strength (unless `allow_weak_key`), + /// length, and builds the key. + /// + /// The strength is checked before the length so that an over-long key with no security + /// strength gets the strength error the shared test framework expects of every MAC key. + fn from_checked(key: &dyn KeyMaterialTrait, allow_weak_key: bool) -> Result { + // Same stance as HMAC and KMAC: an all-zero key is tagged Zeroized rather than MACKey, + // and is let through so callers are not forced to re-tag it. + if !(key.key_type() == KeyType::MACKey || key.key_type() == KeyType::Zeroized) { + return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( + "Key type must be a MAC key.", + ))); + } + if !allow_weak_key && key.security_strength() < SecurityStrength::_256bit { + return Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength( + "BLAKE2b_512_MACKey: key has a lower security strength than BLAKE2b-512", + ))); + } + // RFC 7693 s. 2.1: "Key bytes | 0 <= kk <= 64". kk = 0 is the unkeyed hash, and there is + // no pre-hashing of long keys as in HMAC. + if key.key_len() == 0 || key.key_len() > MAX_KK { + return Err(MACError::InvalidLength("BLAKE2b key must be 1 to 64 bytes")); + } + Ok(Self { core: Blake2bCore::new_keyed(key.ref_to_bytes()) }) + } +} + +impl FromAnyKeyMaterial for BLAKE2b_512_MACKey { + /// # Errors + /// * [`MACError::KeyMaterialError`] with [`KeyMaterialError::InvalidKeyType`] unless the + /// material is [`KeyType::MACKey`] or [`KeyType::Zeroized`]. + /// * [`MACError::KeyMaterialError`] with [`KeyMaterialError::SecurityStrength`] if it is + /// tagged below 256 bits; [`Self::from_key_material_allow_weak`] skips this check. + /// * [`MACError::InvalidLength`] unless it is 1 to 64 bytes. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result { + Self::from_checked(key, false) + } + + /// As [`from_key_material`](Self::from_key_material) without the strength check. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result { + Self::from_checked(key, true) + } + + /// Draws a 64-byte key from `rng`, tagged [`KeyType::MACKey`] at the RNG's strength capped + /// at what the length carries. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails, or with + /// [`KeyMaterialError::SecurityStrength`] if its strength is below 256 bits. + fn generate(rng: &mut dyn RNG) -> Result { + let mut key = KeyMaterial::::new(); + do_hazardous_operations(&mut key, |key| { + rng.next_bytes_out(key.ref_to_bytes_mut()?) + .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + key.set_key_len(MAX_KK)?; + key.set_key_type(KeyType::MACKey)?; + // The RNG's strength, capped at what the length carries, as KMACKey::generate does. + // Mutants note: at by_rng == by_len both arms give the same strength, so `<` -> `<=` + // is an equivalent mutant. + let by_len = SecurityStrength::from_bits(MAX_KK * 8); + let by_rng = rng.security_strength(); + key.set_security_strength(if by_rng < by_len { by_rng } else { by_len }) + })?; + Self::from_key_material(&key) + } +} + +impl MacGenerationKey for BLAKE2b_512_MACKey { + type Generator<'k> + = BLAKE2b_512_MAC + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + BLAKE2b_512_MAC { core: self.core.clone() } + } +} + +impl MacVerificationKey for BLAKE2b_512_MACKey { + type Verifier<'k> + = BLAKE2b_512_MAC + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + self.create_generator() + } +} + +/// The keyed BLAKE2b worker handed out by a [`BLAKE2b_512_MACKey`]. +/// +/// See the [crate-level documentation](crate) for usage. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2b_512_MAC { + core: Blake2bCore, +} + +impl MAC for BLAKE2b_512_MAC { + fn output_len(&self) -> usize { + NN + } + + fn mac(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + fn mac_out(mut self, data: &[u8], out: &mut [u8]) -> Result { + out.fill(0); + self.do_update(data); + self.do_final_out(out) + } + + fn verify(mut self, data: &[u8], mac: &[u8]) -> bool { + self.do_update(data); + self.do_verify_final(mac) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut out = vec![0u8; NN]; + self.core.finalize(&mut out); + out + } + + /// Zeroizes all of `out`, then writes the first `min(out.len(), 64)` bytes of the tag and + /// returns that count. A truncated tag is the 64-byte tag cut short, not the RFC's BLAKE2b with + /// a smaller nn (which binds nn into the parameter block, s. 2.5, and is a different function). + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than [`MIN_MAC_OUTPUT_LEN`]. + fn do_final_out(self, out: &mut [u8]) -> Result { + out.fill(0); + if out.len() < MIN_MAC_OUTPUT_LEN { + return Err(MACError::InvalidLength( + "output buffer is shorter than MIN_MAC_OUTPUT_LEN (4 bytes)", + )); + } + Ok(self.core.finalize(out)) + } + + /// Compares in constant time against the full 64-byte tag: a truncated or extended `mac` is + /// not a match. + fn do_verify_final(self, mac: &[u8]) -> bool { + let computed = self.do_final(); + ct::ct_eq_bytes(&computed, mac) + } + + /// RFC 7693 s. 4: BLAKE2b-512 has "Collision Security 2**256". + fn max_security_strength(&self) -> SecurityStrength { + SecurityStrength::_256bit + } +} + +/// The key is never written into the serialized state; resume takes the [`BLAKE2b_512_MACKey`] +/// again. +/// +/// Before any message byte (`t == 0`) the buffer holds the padded key block, so `suspend` writes +/// zeros there and `from_suspended` rebuilds it from the key. Once a message byte arrives the key +/// is folded into `h`, and from then on the key passed to `from_suspended` is not used: resuming +/// with a different key gives the original MAC. +/// +/// # 🚨 Security Considerations 🚨 +/// Although it holds no key bytes, a suspended state with `t > 0` is a MAC oracle for every +/// extension of the absorbed prefix: its chaining value `h` alone finishes the MAC, so anyone +/// holding it can compute valid tags for any message that starts with that prefix. Protect it like +/// the key. +impl<'k> SuspendableKeyed<'k, SUSPENDED_BLAKE2B_512_MAC_STATE_LEN> for BLAKE2b_512_MAC { + type Key = BLAKE2b_512_MACKey; + + fn suspend(self) -> [u8; SUSPENDED_BLAKE2B_512_MAC_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE2B_512_MAC_STATE_LEN]; + let mut out = [0u8; CORE_STATE_LEN]; + // t == 0 exactly when buf still holds the key block d[0]. + self.core.write_state(&mut out, self.core.t == 0); + // add_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + add_lib_ver(&mut out_to_return).copy_from_slice(&out); + out_to_return + } + + /// # Errors + /// * [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library + /// version. + /// * [`SuspendableError::InvalidData`] if the buffered length exceeds one block or `t` is not + /// a whole number of blocks, or if a state taken before any input (`t == 0`) does not match + /// a fresh worker of `key` -- for example one from a key of a different length. + fn from_suspended( + state: [u8; SUSPENDED_BLAKE2B_512_MAC_STATE_LEN], + key: &'k Self::Key, + ) -> Result { + // At the moment, we have no not_before version to specify. + // check_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + let mut input = [0u8; CORE_STATE_LEN]; + input.copy_from_slice(check_lib_ver(&state, None)?); + let core = Blake2bCore::read_state(&input)?; + + if core.t == 0 { + // Before any input the state is exactly the key's: h is the initial state for this + // kk (bound into p[0], so a key of another length is caught) and buf is the full key + // block. The serialized buffer is zeros, so the key's own state is taken as a whole. + if core.buf_len != BB || *core.h != *key.core.h { + return Err(SuspendableError::InvalidData); + } + return Ok(Self { core: key.core.clone() }); + } + + Ok(Self { core }) + } +} diff --git a/crypto/blake2/src/blake2s.rs b/crypto/blake2/src/blake2s.rs new file mode 100644 index 000000000..4b3bee5d3 --- /dev/null +++ b/crypto/blake2/src/blake2s.rs @@ -0,0 +1,700 @@ +//! BLAKE2s (RFC 7693) with a 32-byte digest: the unkeyed hash [`BLAKE2s_256`] and the keyed MAC +//! key [`BLAKE2s_256_MACKey`] with its worker [`BLAKE2s_256_MAC`]. +//! +//! This file and `blake2b.rs` are deliberately structurally identical -- RFC 7693 s. 2.1 describes +//! one algorithm with two parameter columns -- so that a reviewer can diff them. Keep them in step. + +use crate::MIN_MAC_OUTPUT_LEN; +use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; +use bouncycastle_core::hazmat::do_hazardous_operations; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +use bouncycastle_core::traits::{Hash, MAC, RNG, Suspendable, SuspendableKeyed}; +use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_utils::{ct, min, secret::Secret}; + +/// RFC 7693 s. 2.1: "Block bytes | bb = 64". +const BB: usize = 64; + +/// RFC 7693 s. 2.1: "Hash bytes | 1 <= nn <= 32". This crate implements only nn = 32. +const NN: usize = 32; + +/// RFC 7693 s. 2.1: "Key bytes | 0 <= kk <= 32". +const MAX_KK: usize = 32; + +/// RFC 7693 s. 2.1: "Rounds in F | r = 10". +const R: usize = 10; + +/// RFC 7693 s. 2.1: "G Rotation constants = (16, 12, 8, 7)". +const R1: u32 = 16; +const R2: u32 = 12; +const R3: u32 = 8; +const R4: u32 = 7; + +/// RFC 7693 s. 2.6: IV[i] = floor(2**w * frac(sqrt(prime(i+1)))), w = 32; values as in Appendix D.2 +/// `blake2s_iv` ("BLAKE2s IV is the same as SHA-256 IV"). +const IV: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +/// RFC 7693 s. 2.7: message schedule SIGMA[0..9]. BLAKE2s has r = 10 rounds, so F's +/// `SIGMA[i mod 10]` is just `SIGMA[i]`; the `% 10` is kept to match the spec and blake2b.rs. +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// RFC 7693 s. 3.1: mixing function G. +#[inline] +fn g(v: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, x: u32, y: u32) { + // v[a] := (v[a] + v[b] + x) mod 2**w + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + // v[d] := (v[d] ^ v[a]) >>> R1 + v[d] = (v[d] ^ v[a]).rotate_right(R1); + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = v[c].wrapping_add(v[d]); + // v[b] := (v[b] ^ v[c]) >>> R2 + v[b] = (v[b] ^ v[c]).rotate_right(R2); + // v[a] := (v[a] + v[b] + y) mod 2**w + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + // v[d] := (v[d] ^ v[a]) >>> R3 + v[d] = (v[d] ^ v[a]).rotate_right(R3); + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = v[c].wrapping_add(v[d]); + // v[b] := (v[b] ^ v[c]) >>> R4 + v[b] = (v[b] ^ v[c]).rotate_right(R4); +} + +/// RFC 7693 s. 3.2: compression function F( h[0..7], m[0..15], t, f ), updating `h` in place. +/// +/// The message words `m` are read from `block` little-endian (s. 2.4). The transient work vector +/// `v` and words `m` are plain stack locals and are not zeroized; see the crate's Security +/// Considerations. +fn compress(h: &mut [u32; 8], block: &[u8; BB], t: u64, f: bool) { + // s. 2.4: "Byte (octet) streams are interpreted as words in little-endian order". + let mut m = [0u32; 16]; + let (words, _) = block.as_chunks::<4>(); + for (mi, bytes) in m.iter_mut().zip(words) { + *mi = u32::from_le_bytes(*bytes); + } + + // // Initialize local work vector v[0..15] + let mut v = [0u32; 16]; + // v[0..7] := h[0..7] // First half from state. + v[..8].copy_from_slice(h); + // v[8..15] := IV[0..7] // Second half from IV. + v[8..].copy_from_slice(&IV); + + // v[12] := v[12] ^ (t mod 2**w) // Low word of the offset. + v[12] ^= t as u32; + // v[13] := v[13] ^ (t >> w) // High word. + // Mutants note: the high word of t is zero unless 2**32 or more bytes have been processed + // (4 GiB), far beyond what a test can hash, so `^=` -> `|=` and `>>` -> `<<` here (both a + // no-op on a zero high word) survive as equivalent on every testable input. + v[13] ^= (t >> 32) as u32; + + // IF f = TRUE THEN // last block flag? + if f { + // v[14] := v[14] ^ 0xFF..FF // Invert all bits. + v[14] ^= u32::MAX; + } + + // // Cryptographic mixing + // FOR i = 0 TO r - 1 DO // Ten or twelve rounds. + for i in 0..R { + // s[0..15] := SIGMA[i mod 10][0..15] + let s = &SIGMA[i % 10]; + + // v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] ) + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + // v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] ) + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + // v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] ) + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + // v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] ) + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + // v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] ) + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + // v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] ) + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + // v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] ) + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + // v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] ) + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + // FOR i = 0 TO 7 DO // XOR the two halves. + for i in 0..8 { + // h[i] := h[i] ^ v[i] ^ v[i + 8] + h[i] ^= v[i] ^ v[i + 8]; + } +} + +/// RFC 7693 s. 3.3: the initial state for key length `kk`. +/// +/// h[0..7] := IV[0..7]; h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn (the parameter block p[0] of +/// s. 2.5, "p[0] = 0x0101kknn"). +fn initial_h(kk: usize) -> Secret<[u32; 8]> { + let mut h = Secret::<[u32; 8]>::new(); + // h[0..7] := IV[0..7] // Initialization Vector. + h.copy_from_slice(&IV); + // // Parameter block p[0] + // h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn + // Mutants note: 0x01010000, kk << 8 and nn occupy disjoint bytes of p[0] (s. 2.5 + // "0x0101kknn", with kk and nn below 256), so `^` -> `|` between them is an equivalent mutant. + h[0] ^= 0x01010000 ^ ((kk as u32) << 8) ^ (NN as u32); + h +} + +/// The BLAKE2s engine shared by the hash and the MAC: RFC 7693 s. 3.3 processed incrementally, in +/// the shape of the Appendix D reference code. +/// +/// Invariant: the buffer is compressed only when it is full *and* more input arrives, so the final +/// block d[dd-1] is always still in `buf` when [`Self::finalize`] runs and can be flagged +/// `f = TRUE`. +#[derive(Clone)] +struct Blake2sCore { + /// The chaining state h[0..7]. + h: Secret<[u32; 8]>, + /// s. 2.2: "Message byte offset at the end of the current block" -- the total of the bytes in + /// every block compressed so far (key block included). s. 3.2 calls it a "2w-bit offset + /// counter", which for w = 32 is a u64. + t: u64, + /// The current, not yet compressed, block. In keyed mode this is the padded key block `d[0]` + /// exactly when `t == 0`. + buf: Secret<[u8; BB]>, + /// Number of valid bytes in `buf`, in `0..=BB`. + buf_len: usize, +} + +impl Blake2sCore { + /// The unkeyed state (kk = 0). + fn new_unkeyed() -> Self { + Self { h: initial_h(0), t: 0, buf: Secret::new(), buf_len: 0 } + } + + /// The keyed state. `key` must be 1..=32 bytes; the callers validate it. + fn new_keyed(key: &[u8]) -> Self { + debug_assert!(!key.is_empty() && key.len() <= MAX_KK); + let mut core = Self { h: initial_h(key.len()), t: 0, buf: Secret::new(), buf_len: 0 }; + core.load_key_block(key); + core + } + + /// s. 3.3: "If a secret key is used (kk > 0), it is padded with zero bytes and set as `d[0]`." + /// `d[0]` is left in the buffer as a full block, which is compressed as soon as any message + /// byte arrives, or as the final block if none does + /// (Appendix D.2 `blake2s_init`: `ctx->c = 64`). + fn load_key_block(&mut self, key: &[u8]) { + self.buf.fill(0); + self.buf[..key.len()].copy_from_slice(key); + self.buf_len = BB; + } + + /// Absorbs `data`, compressing every block that is known not to be the last one. + fn update(&mut self, data: &[u8]) { + let mut data = data; + let fill = BB - self.buf_len; + + // Only if `data` overflows the buffer is the buffered block known not to be final. + if data.len() > fill { + self.buf[self.buf_len..].copy_from_slice(&data[..fill]); + data = &data[fill..]; + // s. 3.3: h := F( h, d[i], (i + 1) * bb, FALSE ) -- t is the byte offset at the end + // of this block. + self.add_to_t(BB); + compress(&mut self.h, &self.buf, self.t, false); + self.buf_len = 0; + + // Whole blocks straight from the input, keeping at least one byte back so that the + // final block always ends up in the buffer: (len - 1) / bb blocks are each followed by + // more input (e.g. 64 bytes -> 0 blocks, 65 -> 1, 128 -> 1). + let num_blocks = data.len().saturating_sub(1) / BB; + let (blocks, _) = data.as_chunks::(); + for block in &blocks[..num_blocks] { + self.add_to_t(BB); + compress(&mut self.h, block, self.t, false); + } + data = &data[num_blocks * BB..]; + } + + // What is left fits: data.len() <= BB - buf_len. + self.buf[self.buf_len..self.buf_len + data.len()].copy_from_slice(data); + self.buf_len += data.len(); + } + + /// `t += n`. s. 2.1 bounds the input at "0 <= ll < 2**64" bytes; `t` (ll, plus bb in keyed + /// mode) cannot reach 2**64 in practice, and overflow is asserted in debug builds rather than + /// silently wrapping into a different hash. + fn add_to_t(&mut self, n: usize) { + debug_assert!(self.t.checked_add(n as u64).is_some(), "BLAKE2s message too long"); + self.t = self.t.wrapping_add(n as u64); + } + + /// RFC 7693 s. 3.3 final block, then "RETURN first "nn" bytes from little-endian word array + /// h[]". Zeroizes all of `output`, writes `min(output.len(), nn)` bytes and returns that count. + fn finalize(mut self, output: &mut [u8]) -> usize { + // Final block: IF kk = 0 THEN h := F( h, d[dd - 1], ll, TRUE ) + // ELSE h := F( h, d[dd - 1], ll + bb, TRUE ) + // `t` already counts every compressed block (the key block included), so adding the bytes + // of the last block gives ll, or ll + bb when keyed. The unkeyed empty message is the + // s. 3.3 special case "d[0] consists of all zeros" with t = 0. + self.add_to_t(self.buf_len); + // "The final data block d[dd-1] is also padded with zero to "bb" bytes". + self.buf[self.buf_len..].fill(0); + compress(&mut self.h, &self.buf, self.t, true); + + output.fill(0); + let n = *min(&output.len(), &NN); + // Little-endian words, truncated to n bytes. + for (chunk, word) in output[..n].chunks_mut(4).zip(self.h.iter()) { + chunk.copy_from_slice(&word.to_le_bytes()[..chunk.len()]); + } + n + } + + /// Serializes `h || t || buf || buf_len` (all little-endian) into `out`, which must be + /// zeroed. `buf` is written as its first `buf_len` bytes followed by zeros, or as all zeros + /// when `zero_buf` is set. + fn write_state(&self, out: &mut [u8; CORE_STATE_LEN], zero_buf: bool) { + let (words, _) = out[..32].as_chunks_mut::<4>(); + for (bytes, word) in words.iter_mut().zip(self.h.iter()) { + *bytes = word.to_le_bytes(); + } + out[32..40].copy_from_slice(&self.t.to_le_bytes()); + // Only the valid bytes: past buf_len the buffer still holds stale bytes of earlier + // blocks -- in keyed mode, of the key block d[0] -- which must not leak into the state. + if !zero_buf { + out[40..40 + self.buf_len].copy_from_slice(&self.buf[..self.buf_len]); + } + debug_assert!(self.buf_len <= BB); + out[104] = self.buf_len as u8; + } + + /// Inverse of [`Self::write_state`]. Rejects a `buf_len` larger than a block (a full block, + /// `buf_len == bb`, is valid: it is compressed only when more input arrives) and a `t` that is + /// not a whole number of blocks. + fn read_state(input: &[u8; CORE_STATE_LEN]) -> Result { + let buf_len = input[104] as usize; + if buf_len > BB { + return Err(SuspendableError::InvalidData); + } + + let mut h = Secret::<[u32; 8]>::new(); + let (words, _) = input[..32].as_chunks::<4>(); + for (hi, bytes) in h.iter_mut().zip(words) { + *hi = u32::from_le_bytes(*bytes); + } + let mut t_bytes = [0u8; 8]; + t_bytes.copy_from_slice(&input[32..40]); + let t = u64::from_le_bytes(t_bytes); + // A compression only ever happens on a full block (s. 3.3 "(i + 1) * bb"), so at rest t is + // a multiple of bb. For the keyed type this also rules out 0 < t < bb: the key block is + // compressed first, with t = bb. + if t % (BB as u64) != 0 { + return Err(SuspendableError::InvalidData); + } + let mut buf = Secret::<[u8; BB]>::new(); + buf.copy_from_slice(&input[40..104]); + + Ok(Self { h, t, buf, buf_len }) + } +} + +/// Length of the serialized engine state, without the version header. +const CORE_STATE_LEN: usize = 32 + 8 + BB + 1; + +/// Length in bytes of the serialized state of [`BLAKE2s_256`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian): +/// [0 .. 32) h [u32; 8] +/// [32 .. 40) t u64 (RFC 7693 s. 2.2 byte offset) +/// [40 .. 104) buf [u8; 64] +/// [104 .. 105) buf_len u8 (always <= 64) +pub const SUSPENDED_BLAKE2S_256_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// Length in bytes of the serialized state of [`BLAKE2s_256_MAC`]. +/// +/// Same layout as [`SUSPENDED_BLAKE2S_256_STATE_LEN`], except that `buf` is written as zeros while +/// it holds the padded key block (`t == 0`), so the key never appears in the state. The two lengths +/// are equal today; depend on the one for the type you are suspending. +pub const SUSPENDED_BLAKE2S_256_MAC_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// The BLAKE2s hash with a 32-byte digest, unkeyed (RFC 7693, kk = 0, nn = 32). +/// +/// See the [crate-level documentation](crate) for usage. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2s_256 { + core: Blake2sCore, +} + +impl BLAKE2s_256 { + /// Creates a new BLAKE2s-256 instance, ready for use. + pub fn new() -> Self { + Self { core: Blake2sCore::new_unkeyed() } + } +} + +impl Default for BLAKE2s_256 { + fn default() -> Self { + Self::new() + } +} + +impl Hash for BLAKE2s_256 { + /// RFC 7693 s. 2.1: "Block bytes | bb = 64", so 512 bits. + fn block_bitlen(&self) -> usize { + BB * 8 + } + + fn output_len(&self) -> usize { + NN + } + + fn hash(self, data: &[u8]) -> Vec { + let mut output = vec![0u8; NN]; + self.hash_out(data, &mut output); + output + } + + fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut output = vec![0u8; NN]; + self.do_final_out(&mut output); + output + } + + fn do_final_out(self, output: &mut [u8]) -> usize { + self.core.finalize(output) + } + + fn do_final_partial_bits( + self, + partial_byte: u8, + num_bits: usize, + ) -> Result, HashError> { + let mut output = vec![0u8; NN]; + self.do_final_partial_bits_out(partial_byte, num_bits, &mut output)?; + Ok(output) + } + + /// BLAKE2 is byte-oriented (RFC 7693 s. 2.1 counts "Input bytes"), so it has no encoding for a + /// message that ends part-way through a byte. + /// + /// # Errors + /// * [`HashError::InvalidLength`] if `num_bits > 7`, as for every [`Hash`]. + /// * [`HashError::InvalidInput`] if `num_bits` is in `1..=7`. + /// + /// `num_bits == 0` means the message ended on a byte boundary and is exactly + /// [`Hash::do_final_out`]; `partial_byte` is ignored. + /// + /// This is the convention of Ascon-XOF128 / CXOF128 in this library. KMACXOF differs: it + /// returns [`HashError::InvalidLength`] for every non-zero `num_bits`. + fn do_final_partial_bits_out( + self, + _partial_byte: u8, + num_bits: usize, + output: &mut [u8], + ) -> Result { + output.fill(0); + if num_bits > 7 { + return Err(HashError::InvalidLength("num_partial_bits must be in the range [0,7]")); + } + if num_bits != 0 { + return Err(HashError::InvalidInput( + "BLAKE2 is byte-oriented (RFC 7693 s. 2.1) and cannot hash a partial final byte", + )); + } + Ok(self.do_final_out(output)) + } + + /// RFC 7693 s. 4: BLAKE2s-256 has "Collision Security 2**128". + fn max_security_strength(&self) -> SecurityStrength { + SecurityStrength::_128bit + } +} + +impl Suspendable for BLAKE2s_256 { + fn suspend(self) -> [u8; SUSPENDED_BLAKE2S_256_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE2S_256_STATE_LEN]; + let mut out = [0u8; CORE_STATE_LEN]; + self.core.write_state(&mut out, false); + // add_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + add_lib_ver(&mut out_to_return).copy_from_slice(&out); + out_to_return + } + + /// # Errors + /// * [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library + /// version. + /// * [`SuspendableError::InvalidData`] if the buffered length exceeds one block, if `t` is not + /// a whole number of blocks, or if a state taken before any compression (`t == 0`) does not + /// have the unkeyed initial `h` (for example a keyed state, or a tampered one). + fn from_suspended( + state: [u8; SUSPENDED_BLAKE2S_256_STATE_LEN], + ) -> Result { + // At the moment, we have no not_before version to specify. + // check_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + let mut input = [0u8; CORE_STATE_LEN]; + input.copy_from_slice(check_lib_ver(&state, None)?); + let core = Blake2sCore::read_state(&input)?; + // Before any compression h is exactly the unkeyed initial state (kk = 0), so anything else + // is a state of a different parameter block -- such as a keyed one -- or a corrupt one. + if core.t == 0 && *core.h != *initial_h(0) { + return Err(SuspendableError::InvalidData); + } + Ok(Self { core }) + } +} + +/// A BLAKE2s MAC key: RFC 7693 keyed BLAKE2s (kk = 1..=32, nn = 32), the key object for +/// [`BLAKE2s_256_MAC`]. +/// +/// This is the keyed BLAKE2s of the RFC ("BLAKE2SMAC" in OpenSSL), not HMAC-BLAKE2s. The key holds +/// the state after keyed initialisation (RFC 7693 s. 3.3), so every worker starts from it. +/// +/// # Keys +/// Material must be tagged [`KeyType::MACKey`] (or be all-zero, [`KeyType::Zeroized`]) and be 1 to +/// 32 bytes (RFC 7693 s. 2.1 "0 <= kk <= 32"). Unlike HMAC, a zero-length key is refused: +/// kk = 0 is the unkeyed hash, [`BLAKE2s_256`]. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2s_256_MACKey { + /// The engine after keyed initialisation: h with kk bound into p[0], t = 0, and the padded + /// key block `d[0]` in the buffer. + core: Blake2sCore, +} + +impl BLAKE2s_256_MACKey { + /// Validates material in the order: type, security strength (unless `allow_weak_key`), + /// length, and builds the key. + /// + /// The strength is checked before the length so that an over-long key with no security + /// strength gets the strength error the shared test framework expects of every MAC key. + fn from_checked(key: &dyn KeyMaterialTrait, allow_weak_key: bool) -> Result { + // Same stance as HMAC and KMAC: an all-zero key is tagged Zeroized rather than MACKey, + // and is let through so callers are not forced to re-tag it. + if !(key.key_type() == KeyType::MACKey || key.key_type() == KeyType::Zeroized) { + return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( + "Key type must be a MAC key.", + ))); + } + if !allow_weak_key && key.security_strength() < SecurityStrength::_128bit { + return Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength( + "BLAKE2s_256_MACKey: key has a lower security strength than BLAKE2s-256", + ))); + } + // RFC 7693 s. 2.1: "Key bytes | 0 <= kk <= 32". kk = 0 is the unkeyed hash, and there is + // no pre-hashing of long keys as in HMAC. + if key.key_len() == 0 || key.key_len() > MAX_KK { + return Err(MACError::InvalidLength("BLAKE2s key must be 1 to 32 bytes")); + } + Ok(Self { core: Blake2sCore::new_keyed(key.ref_to_bytes()) }) + } +} + +impl FromAnyKeyMaterial for BLAKE2s_256_MACKey { + /// # Errors + /// * [`MACError::KeyMaterialError`] with [`KeyMaterialError::InvalidKeyType`] unless the + /// material is [`KeyType::MACKey`] or [`KeyType::Zeroized`]. + /// * [`MACError::KeyMaterialError`] with [`KeyMaterialError::SecurityStrength`] if it is + /// tagged below 128 bits; [`Self::from_key_material_allow_weak`] skips this check. + /// * [`MACError::InvalidLength`] unless it is 1 to 32 bytes. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result { + Self::from_checked(key, false) + } + + /// As [`from_key_material`](Self::from_key_material) without the strength check. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result { + Self::from_checked(key, true) + } + + /// Draws a 32-byte key from `rng`, tagged [`KeyType::MACKey`] at the RNG's strength capped + /// at what the length carries. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails, or with + /// [`KeyMaterialError::SecurityStrength`] if its strength is below 128 bits. + fn generate(rng: &mut dyn RNG) -> Result { + let mut key = KeyMaterial::::new(); + do_hazardous_operations(&mut key, |key| { + rng.next_bytes_out(key.ref_to_bytes_mut()?) + .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + key.set_key_len(MAX_KK)?; + key.set_key_type(KeyType::MACKey)?; + // The RNG's strength, capped at what the length carries, as KMACKey::generate does. + // Mutants note: at by_rng == by_len both arms give the same strength, so `<` -> `<=` + // is an equivalent mutant. + let by_len = SecurityStrength::from_bits(MAX_KK * 8); + let by_rng = rng.security_strength(); + key.set_security_strength(if by_rng < by_len { by_rng } else { by_len }) + })?; + Self::from_key_material(&key) + } +} + +impl MacGenerationKey for BLAKE2s_256_MACKey { + type Generator<'k> + = BLAKE2s_256_MAC + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + BLAKE2s_256_MAC { core: self.core.clone() } + } +} + +impl MacVerificationKey for BLAKE2s_256_MACKey { + type Verifier<'k> + = BLAKE2s_256_MAC + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + self.create_generator() + } +} + +/// The keyed BLAKE2s worker handed out by a [`BLAKE2s_256_MACKey`]. +/// +/// See the [crate-level documentation](crate) for usage. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2s_256_MAC { + core: Blake2sCore, +} + +impl MAC for BLAKE2s_256_MAC { + fn output_len(&self) -> usize { + NN + } + + fn mac(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + fn mac_out(mut self, data: &[u8], out: &mut [u8]) -> Result { + out.fill(0); + self.do_update(data); + self.do_final_out(out) + } + + fn verify(mut self, data: &[u8], mac: &[u8]) -> bool { + self.do_update(data); + self.do_verify_final(mac) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut out = vec![0u8; NN]; + self.core.finalize(&mut out); + out + } + + /// Zeroizes all of `out`, then writes the first `min(out.len(), 32)` bytes of the tag and + /// returns that count. A truncated tag is the 32-byte tag cut short, not the RFC's BLAKE2s with + /// a smaller nn (which binds nn into the parameter block, s. 2.5, and is a different function). + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than [`MIN_MAC_OUTPUT_LEN`]. + fn do_final_out(self, out: &mut [u8]) -> Result { + out.fill(0); + if out.len() < MIN_MAC_OUTPUT_LEN { + return Err(MACError::InvalidLength( + "output buffer is shorter than MIN_MAC_OUTPUT_LEN (4 bytes)", + )); + } + Ok(self.core.finalize(out)) + } + + /// Compares in constant time against the full 32-byte tag: a truncated or extended `mac` is + /// not a match. + fn do_verify_final(self, mac: &[u8]) -> bool { + let computed = self.do_final(); + ct::ct_eq_bytes(&computed, mac) + } + + /// RFC 7693 s. 4: BLAKE2s-256 has "Collision Security 2**128". + fn max_security_strength(&self) -> SecurityStrength { + SecurityStrength::_128bit + } +} + +/// The key is never written into the serialized state; resume takes the [`BLAKE2s_256_MACKey`] +/// again. +/// +/// Before any message byte (`t == 0`) the buffer holds the padded key block, so `suspend` writes +/// zeros there and `from_suspended` rebuilds it from the key. Once a message byte arrives the key +/// is folded into `h`, and from then on the key passed to `from_suspended` is not used: resuming +/// with a different key gives the original MAC. +/// +/// # 🚨 Security Considerations 🚨 +/// Although it holds no key bytes, a suspended state with `t > 0` is a MAC oracle for every +/// extension of the absorbed prefix: its chaining value `h` alone finishes the MAC, so anyone +/// holding it can compute valid tags for any message that starts with that prefix. Protect it like +/// the key. +impl<'k> SuspendableKeyed<'k, SUSPENDED_BLAKE2S_256_MAC_STATE_LEN> for BLAKE2s_256_MAC { + type Key = BLAKE2s_256_MACKey; + + fn suspend(self) -> [u8; SUSPENDED_BLAKE2S_256_MAC_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE2S_256_MAC_STATE_LEN]; + let mut out = [0u8; CORE_STATE_LEN]; + // t == 0 exactly when buf still holds the key block d[0]. + self.core.write_state(&mut out, self.core.t == 0); + // add_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + add_lib_ver(&mut out_to_return).copy_from_slice(&out); + out_to_return + } + + /// # Errors + /// * [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library + /// version. + /// * [`SuspendableError::InvalidData`] if the buffered length exceeds one block or `t` is not + /// a whole number of blocks, or if a state taken before any input (`t == 0`) does not match + /// a fresh worker of `key` -- for example one from a key of a different length. + fn from_suspended( + state: [u8; SUSPENDED_BLAKE2S_256_MAC_STATE_LEN], + key: &'k Self::Key, + ) -> Result { + // At the moment, we have no not_before version to specify. + // check_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + let mut input = [0u8; CORE_STATE_LEN]; + input.copy_from_slice(check_lib_ver(&state, None)?); + let core = Blake2sCore::read_state(&input)?; + + if core.t == 0 { + // Before any input the state is exactly the key's: h is the initial state for this + // kk (bound into p[0], so a key of another length is caught) and buf is the full key + // block. The serialized buffer is zeros, so the key's own state is taken as a whole. + if core.buf_len != BB || *core.h != *key.core.h { + return Err(SuspendableError::InvalidData); + } + return Ok(Self { core: key.core.clone() }); + } + + Ok(Self { core }) + } +} diff --git a/crypto/blake2/src/lib.rs b/crypto/blake2/src/lib.rs new file mode 100644 index 000000000..1ded768d0 --- /dev/null +++ b/crypto/blake2/src/lib.rs @@ -0,0 +1,199 @@ +//! BLAKE2b-512 and BLAKE2s-256 (RFC 7693, "The BLAKE2 Cryptographic Hash and Message +//! Authentication Code (MAC)"), unkeyed and in BLAKE2's built-in keyed mode. +//! +//! | Type | RFC 7693 parameters | Use | +//! |---------------------|-------------------------------|--------------------------------------| +//! | [`BLAKE2b_512`] | BLAKE2b, nn = 64, kk = 0 | [`Hash`] | +//! | [`BLAKE2s_256`] | BLAKE2s, nn = 32, kk = 0 | [`Hash`] | +//! | [`BLAKE2b_512_MACKey`] | BLAKE2b, nn = 64, kk = 1..=64 | MAC key; worker [`BLAKE2b_512_MAC`] | +//! | [`BLAKE2s_256_MACKey`] | BLAKE2s, nn = 32, kk = 1..=32 | MAC key; worker [`BLAKE2s_256_MAC`] | +//! +//! Other digest lengths, salt and personalisation, and the tree / parallel / XOF variants are not +//! implemented. +//! +//! # Usage Examples +//! ``` +//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_blake2::{BLAKE2b_512, BLAKE2s_256}; +//! +//! // RFC 7693 Appendix A and B. +//! assert_eq!(BLAKE2b_512::new().hash(b"abc")[..4], [0xBA, 0x80, 0xA5, 0x3F]); +//! +//! let mut blake2s = BLAKE2s_256::new(); +//! blake2s.do_update(b"a"); +//! blake2s.do_update(b"bc"); +//! assert_eq!(blake2s.do_final()[..4], [0x50, 0x8C, 0x5E, 0x8C]); +//! ``` +//! +//! A MAC is reached through its key object, built from [`KeyMaterial`] tagged +//! [`KeyType::MACKey`]: +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::FromAnyKeyMaterial; +//! use bouncycastle_core::symmetric_key::{MacGenerationKey, MacVerificationKey}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_blake2::BLAKE2s_256_MACKey; +//! +//! let material = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey).unwrap(); +//! let key = BLAKE2s_256_MACKey::from_key_material(&material).unwrap(); +//! +//! let tag: Vec = key.mac(b"message"); +//! assert!(key.verify(b"message", &tag)); +//! +//! let mut mac = key.create_verifier(); +//! mac.do_update(b"mess"); +//! mac.do_update(b"age"); +//! assert!(mac.do_verify_final(&tag)); +//! ``` +//! +//! # Memory Usage +//! +//! No heap memory is used by the algorithm itself. Sizes from `core::mem::size_of` on x86_64: +//! +//! | Object | Size (bytes) | +//! |---------------------------------------------------------|--------------| +//! | [`BLAKE2b_512`], [`BLAKE2b_512_MAC`], [`BLAKE2b_512_MACKey`] | 224 | +//! | [`BLAKE2s_256`], [`BLAKE2s_256_MAC`], [`BLAKE2s_256_MACKey`] | 112 | +//! | BLAKE2b-512 suspended state (hash and MAC) | 212 | +//! | BLAKE2s-256 suspended state (hash and MAC) | 108 | +//! +//! The compression function also uses 256 bytes (BLAKE2b) or 128 bytes (BLAKE2s) of stack. +//! +//! # 🚨 Security Considerations 🚨 +//! +//! * BLAKE2b-512 offers 256 bits and BLAKE2s-256 128 bits of collision resistance (RFC 7693 s. 4). +//! * Use the MAC keys for keyed hashing: BLAKE2 "has a built-in keying mechanism" (RFC 7693 s. 1) +//! and its final-block flag (s. 3.2) rules out length extension. A MAC key should be uniformly +//! random. +//! * A truncated output is the full digest cut short, not BLAKE2 with a smaller `nn`, which is a +//! different function. MAC tags are not truncated below [`MIN_MAC_OUTPUT_LEN`] bytes. +//! * Verify tags with `verify` / [`MAC::do_verify_final`], which compare in constant time. +//! * BLAKE2 is byte-oriented: [`Hash::do_final_partial_bits`] refuses 1 to 7 partial bits. +//! * State and buffered input, including the key block, are zeroized on drop; the compression +//! function's stack locals are not. There are no data-dependent branches or table lookups. +//! * A suspended MAC state holds no key bytes, but once input has been absorbed it can finish the +//! MAC of any extension of that input without the key. Protect it like the key. +//! +//! # Suspending and resuming execution +//! +//! The hash types impl [`Suspendable`], and the MAC workers impl [`SuspendableKeyed`], which takes +//! the key object again to resume: +//! +//! ```rust +//! use bouncycastle_blake2::BLAKE2b_512; +//! use bouncycastle_core::traits::{Hash, Suspendable}; +//! +//! let mut blake2b = BLAKE2b_512::new(); +//! blake2b.do_update(b"The quick brown fox"); +//! let serialized_state = blake2b.suspend(); +//! +//! // ... later, possibly on another host. +//! let mut resumed = BLAKE2b_512::from_suspended(serialized_state).unwrap(); +//! resumed.do_update(b" jumps over the lazy dog"); +//! let h: Vec = resumed.do_final(); +//! ``` + +// todo #![no_std] +// waiting for the no_std refactor that removes the `-> Vec` from core::traits::Hash + +#![forbid(unsafe_code)] +#![forbid(missing_docs)] + +mod blake2b; +mod blake2s; + +pub use self::blake2b::{ + BLAKE2b_512, BLAKE2b_512_MAC, BLAKE2b_512_MACKey, SUSPENDED_BLAKE2B_512_MAC_STATE_LEN, + SUSPENDED_BLAKE2B_512_STATE_LEN, +}; +pub use self::blake2s::{ + BLAKE2s_256, BLAKE2s_256_MAC, BLAKE2s_256_MACKey, SUSPENDED_BLAKE2S_256_MAC_STATE_LEN, + SUSPENDED_BLAKE2S_256_STATE_LEN, +}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams}; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +#[allow(unused_imports)] +use bouncycastle_core::traits::{Hash, MAC, Suspendable, SuspendableKeyed}; + +/// Algorithm name string for BLAKE2b-512, as used by the factories and CLI. +pub const BLAKE2B_512_NAME: &str = "BLAKE2b-512"; +/// Algorithm name string for BLAKE2s-256, as used by the factories and CLI. +pub const BLAKE2S_256_NAME: &str = "BLAKE2s-256"; +/// Algorithm name string for keyed BLAKE2b-512, as used by the factories and CLI. +pub const BLAKE2B_512_MAC_NAME: &str = "BLAKE2b-512-MAC"; +/// Algorithm name string for keyed BLAKE2s-256, as used by the factories and CLI. +pub const BLAKE2S_256_MAC_NAME: &str = "BLAKE2s-256-MAC"; + +/// Smallest output buffer the MAC workers will truncate a tag into: 4 bytes. +/// +/// RFC 7693 sets no floor; this follows HMAC's `MIN_FIPS_DIGEST_LEN` (32 bits). +pub const MIN_MAC_OUTPUT_LEN: usize = 4; + +/// RFC 7693 s. 4: id-blake2b512 has "Collision Security 2**256". +impl Algorithm for BLAKE2b_512 { + const ALG_NAME: &'static str = BLAKE2B_512_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +} + +/// RFC 7693 s. 4: id-blake2s256 has "Collision Security 2**128". +impl Algorithm for BLAKE2s_256 { + const ALG_NAME: &'static str = BLAKE2S_256_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Keyed BLAKE2b-512: the same strength as the hash. +impl Algorithm for BLAKE2b_512_MAC { + const ALG_NAME: &'static str = BLAKE2B_512_MAC_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +} + +/// Keyed BLAKE2s-256: the same strength as the hash. +impl Algorithm for BLAKE2s_256_MAC { + const ALG_NAME: &'static str = BLAKE2S_256_MAC_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// RFC 7693 s. 2.1: nn = 64 output bytes, bb = 128 block bytes. +impl HashAlgParams for BLAKE2b_512 { + const OUTPUT_LEN: usize = 64; + const BLOCK_LEN: usize = 128; +} + +/// RFC 7693 s. 2.1: nn = 32 output bytes, bb = 64 block bytes. +impl HashAlgParams for BLAKE2s_256 { + const OUTPUT_LEN: usize = 32; + const BLOCK_LEN: usize = 64; +} + +/// RFC 7693 s. 4: id-blake2b512 OBJECT IDENTIFIER ::= { blake2b 16 }, under +/// hashAlgs = 1.3.6.1.4.1.1722.12.2 and blake2b = { hashAlgs 1 }. +impl AlgorithmOID for BLAKE2b_512 { + const OID: &'static [u32] = &[1, 3, 6, 1, 4, 1, 1722, 12, 2, 1, 16]; + const OID_DER: &'static [u8] = + &[0x06, 0x0B, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x8D, 0x3A, 0x0C, 0x02, 0x01, 0x10]; +} + +/// RFC 7693 s. 4: id-blake2s256 OBJECT IDENTIFIER ::= { blake2s 8 }, under +/// hashAlgs = 1.3.6.1.4.1.1722.12.2 and blake2s = { hashAlgs 2 }. +impl AlgorithmOID for BLAKE2s_256 { + const OID: &'static [u32] = &[1, 3, 6, 1, 4, 1, 1722, 12, 2, 2, 8]; + const OID_DER: &'static [u8] = + &[0x06, 0x0B, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x8D, 0x3A, 0x0C, 0x02, 0x02, 0x08]; +} + +/// RFC 7693 s. 4: "The same OID can be used for both keyed and unkeyed hashing since in the latter +/// case the key simply has zero length." So keyed BLAKE2b-512 shares id-blake2b512. +impl AlgorithmOID for BLAKE2b_512_MAC { + const OID: &'static [u32] = BLAKE2b_512::OID; + const OID_DER: &'static [u8] = BLAKE2b_512::OID_DER; +} + +/// RFC 7693 s. 4, as for [`BLAKE2b_512_MAC`]: keyed BLAKE2s-256 shares id-blake2s256. +impl AlgorithmOID for BLAKE2s_256_MAC { + const OID: &'static [u32] = BLAKE2s_256::OID; + const OID_DER: &'static [u8] = BLAKE2s_256::OID_DER; +} diff --git a/crypto/blake2/tests/bc_test_data.rs b/crypto/blake2/tests/bc_test_data.rs new file mode 100644 index 000000000..cc361a2b1 --- /dev/null +++ b/crypto/blake2/tests/bc_test_data.rs @@ -0,0 +1,152 @@ +//! Test against the bc-test-data repo. +//! Requires that the bc-test-data repository is cloned and available for testing at +//! "../bc-test-data" relative to the root of this git project (or "../../../bc-test-data" relative +//! to this crate). When the repo is absent these tests print a warning and are skipped. +//! +//! `bc-test-data/crypto/Blake2KAT.txt` holds records of five lines: ``, ``, +//! ``, ``, `#`. The blake2b and blake2s records (512 of each: +//! input lengths 0..=255, unkeyed and keyed) are checked here; the blake2bp, blake2sp, blake2xb and +//! blake2xs records are variants this crate does not implement and are skipped. + +#[cfg(test)] +mod bc_test_data { + use bouncycastle_blake2::{BLAKE2b_512, BLAKE2b_512_MACKey, BLAKE2s_256, BLAKE2s_256_MACKey}; + use bouncycastle_core::key_material::{KeyMaterial, KeyType}; + use bouncycastle_core::symmetric_key::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; + use bouncycastle_core::traits::{Hash, MAC}; + use bouncycastle_hex as hex; + use std::fs; + use std::path::Path; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/crypto"; + const TEST_DATA_PATH: &str = "../bc-test-data/crypto"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn get_test_data(filename: &str) -> Result { + let found: u8 = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + 1 + } else if Path::new(TEST_DATA_PATH).exists() { + 2 + } else { + 3 + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("bc-test-data found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("bc-test-data found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: bc-test-data directory not found; tests will be skipped"), + }); + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() + } else { + return Err(()); + }; + + Ok(contents) + } + + /// One Blake2KAT.txt record. + struct Record { + alg: String, + input: Vec, + key: Vec, + digest: Vec, + } + + fn decode_hex(value: &str) -> Vec { + let clean = value.trim(); + if clean.is_empty() { Vec::new() } else { hex::decode(clean).expect("valid hex") } + } + + /// Parses the five-line records; panics on a malformed file rather than skipping records. + fn parse(contents: &str) -> Vec { + let lines: Vec<&str> = contents.lines().collect(); + assert_eq!(lines.len() % 5, 0, "Blake2KAT.txt is not a whole number of 5-line records"); + lines + .chunks(5) + .map(|r| { + assert_eq!(r[4].trim(), "#", "record for {} does not end in #", r[0]); + Record { + alg: r[0].trim().to_string(), + input: decode_hex(r[1]), + key: decode_hex(r[2]), + digest: decode_hex(r[3]), + } + }) + .collect() + } + + /// Runs one record through the hash (empty key) or the MAC (non-empty key), one-shot and + /// streamed byte-at-a-time. + fn check< + HASH: Hash + Default, + KEY: FromAnyKeyMaterial + MacGenerationKey + MacVerificationKey, + >( + r: &Record, + ) { + if r.key.is_empty() { + assert_eq!(HASH::default().hash(&r.input), r.digest, "{} len {}", r.alg, r.input.len()); + let mut hash = HASH::default(); + for byte in &r.input { + hash.do_update(core::slice::from_ref(byte)); + } + assert_eq!(hash.do_final(), r.digest, "{} streamed len {}", r.alg, r.input.len()); + } else { + let key = KeyMaterial::<64>::from_bytes_as_type(&r.key, KeyType::MACKey).unwrap(); + assert_eq!( + KEY::from_key_material(&key).unwrap().mac(&r.input), + r.digest, + "keyed {} len {}", + r.alg, + r.input.len() + ); + let key = KEY::from_key_material(&key).unwrap(); + let mut mac = key.create_verifier(); + for byte in &r.input { + mac.do_update(core::slice::from_ref(byte)); + } + assert!( + mac.do_verify_final(&r.digest), + "keyed {} streamed len {}", + r.alg, + r.input.len() + ); + } + } + + #[test] + fn blake2_kat() { + let contents = match get_test_data("Blake2KAT.txt") { + Ok(c) => c, + Err(()) => return, + }; + let records = parse(&contents); + + let (mut b, mut s) = (0, 0); + for r in &records { + match r.alg.as_str() { + "blake2b" => { + check::(r); + b += 1; + } + "blake2s" => { + check::(r); + s += 1; + } + _ => {} + } + } + + assert_eq!(b, 512, "blake2b records"); + assert_eq!(s, 512, "blake2s records"); + println!("BLAKE2: {b} blake2b and {s} blake2s KAT records passed"); + } +} diff --git a/crypto/blake2/tests/blake2b_tests.rs b/crypto/blake2/tests/blake2b_tests.rs new file mode 100644 index 000000000..bc72bfb93 --- /dev/null +++ b/crypto/blake2/tests/blake2b_tests.rs @@ -0,0 +1,711 @@ +//! Tests for BLAKE2b-512 and keyed BLAKE2b-512 (RFC 7693). +//! +//! Vector sources: RFC 7693 Appendix A; the official BLAKE2 KAT (blake2-kat.json, embedded below); +//! and the bc-test-data sweep in `bc_test_data.rs`. Wycheproof has no BLAKE2 vectors, so the +//! QUALITY_AND_STYLE.md "against wycheproof" requirement is satisfied vacuously. + +#[cfg(test)] +mod blake2b_tests { + use bouncycastle_blake2::*; + use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; + use bouncycastle_core::hazmat::do_hazardous_operations; + use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::symmetric_key::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; + use bouncycastle_core::traits::{ + Algorithm, AlgorithmOID, Hash, HashAlgParams, MAC, Suspendable, SuspendableKeyed, + }; + use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; + use bouncycastle_core_test_framework::hash::TestFrameworkHash; + use bouncycastle_core_test_framework::mac::TestFrameworkMAC; + use bouncycastle_core_test_framework::suspendable_state::{ + TestFrameworkSuspendableKeyedState, TestFrameworkSuspendableState, + }; + use bouncycastle_hex as hex; + + type H = BLAKE2b_512; + type K = BLAKE2b_512_MACKey; + type M = BLAKE2b_512_MAC; + /// RFC 7693 s. 2.1 block bytes bb. + const BB: usize = 128; + /// Digest bytes nn. + const NN: usize = 64; + /// Largest key, kk. + const KK_MAX: usize = 64; + const STATE_LEN: usize = SUSPENDED_BLAKE2B_512_STATE_LEN; + const MAC_STATE_LEN: usize = SUSPENDED_BLAKE2B_512_MAC_STATE_LEN; + /// Offsets in the suspended state (after the 3-byte version header) of t, buf and buf_len. + const T_OFF: usize = 3 + 64; + const BUF_OFF: usize = 3 + 80; + const BUF_LEN_OFF: usize = 3 + 208; + + /// RFC 7693 Appendix A: BLAKE2b-512("abc"), copied by script from the RFC text. + const RFC_ABC: &str = "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"; + + /// (input length, unkeyed digest, keyed digest) for blake2b: generated by script from the + /// official BLAKE2 KAT, github.com/BLAKE2/BLAKE2 testvectors/blake2-kat.json. The input is + /// the bytes 0, 1, 2, ..., len - 1 and the key is the 64 bytes 0, 1, ..., 63. + const KAT: [(usize, &str, &str); 19] = [ + ( + 0, + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", + "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568", + ), + ( + 1, + "2fa3f686df876995167e7c2e5d74c4c7b6e48f8068fe0e44208344d480f7904c36963e44115fe3eb2a3ac8694c28bcb4f5a0f3276f2e79487d8219057a506e4b", + "961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd", + ), + ( + 2, + "1c08798dc641aba9dee435e22519a4729a09b2bfe0ff00ef2dcd8ed6f8a07d15eaf4aee52bbf18ab5608a6190f70b90486c8a7d4873710b1115d3debbb4327b5", + "da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965", + ), + ( + 3, + "40a374727302d9a4769c17b5f409ff32f58aa24ff122d7603e4fda1509e919d4107a52c57570a6d94e50967aea573b11f86f473f537565c66f7039830a85d186", + "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1", + ), + ( + 7, + "8f945ba700f2530e5c2a7df7d5dce0f83f9efc78c073fe71ae1f88204a4fd1cf70a073f5d1f942ed623aa16e90a871246c90c45b621b3401a5ddbd9df6264165", + "7a8cfe9b90f75f7ecb3acc053aaed6193112b6f6a4aeeb3f65d3de541942deb9e2228152a3c4bbbe72fc3b12629528cfbb09fe630f0474339f54abf453e2ed52", + ), + ( + 8, + "e998e0dc03ec30eb99bb6bfaaf6618acc620320d7220b3af2b23d112d8e9cb1262f3c0d60d183b1ee7f096d12dae42c958418600214d04f5ed6f5e718be35566", + "380beaf6ea7cc9365e270ef0e6f3a64fb902acae51dd5512f84259ad2c91f4bc4108db73192a5bbfb0cbcf71e46c3e21aee1c5e860dc96e8eb0b7b8426e6abe9", + ), + ( + 55, + "659858f0b5c9edab5b94fd732f6e6b17c51cc096104f09beb3afc3aa467c2ecf885c4c6541effa9023d3b5738ae5a14d867e15db06fe1f9d1127b77e1aabb516", + "e1590d585a3d39f7cb599abd479070966409a6846d4377acf4471d065d5db94129cc9be92573b05ed226be1e9b7cb0cabe87918589f80dadd4ef5ef25a93d28e", + ), + ( + 56, + "26cca0126f5d1a813c62e5c71001c046f9c92095704550be5873a495a999ad010a4f79491f24f286500adce1a137bc2084e4949f5b7294cefe51ecaff8e95cba", + "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e76842d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2", + ), + ( + 57, + "4147c1f55172788c5567c561feef876f621fff1ce87786b8467637e70dfbcd0dbdb6415cb600954ab9c04c0e457e625b407222c0fe1ae21b2143688ada94dc58", + "30186055c07949948183c850e9a756cc09937e247d9d928e869e20bafc3cd9721719d34e04a0899b92c736084550186886efba2e790d8be6ebf040b209c439a4", + ), + ( + 63, + "d10bf9a15b1c9fc8d41f89bb140bf0be08d2f3666176d13baac4d381358ad074c9d4748c300520eb026daeaea7c5b158892fde4e8ec17dc998dcd507df26eb63", + "bd965bf31e87d70327536f2a341cebc4768eca275fa05ef98f7f1b71a0351298de006fba73fe6733ed01d75801b4a928e54231b38e38c562b2e33ea1284992fa", + ), + ( + 64, + "2fc6e69fa26a89a5ed269092cb9b2a449a4409a7a44011eecad13d7c4b0456602d402fa5844f1a7a758136ce3d5d8d0e8b86921ffff4f692dd95bdc8e5ff0052", + "65676d800617972fbd87e4b9514e1c67402b7a331096d3bfac22f1abb95374abc942f16e9ab0ead33b87c91968a6e509e119ff07787b3ef483e1dcdccf6e3022", + ), + ( + 65, + "fcbe8be7dcb49a32dbdf239459e26308b84dff1ea480df8d104eeff34b46fae98627b450c2267d48c0946a697c5b59531452ac0484f1c84e3a33d0c339bb2e28", + "939fa189699c5d2c81ddd1ffc1fa207c970b6a3685bb29ce1d3e99d42f2f7442da53e95a72907314f4588399a3ff5b0a92beb3f6be2694f9f86ecf2952d5b41c", + ), + ( + 127, + "b6292669ccd38d5f01caae96ba272c76a879a45743afa0725d83b9ebb26665b731f1848c52f11972b6644f554c064fa90780dbbbf3a89d4fc31f67df3e5857ef", + "76d2d819c92bce55fa8e092ab1bf9b9eab237a25267986cacf2b8ee14d214d730dc9a5aa2d7b596e86a1fd8fa0804c77402d2fcd45083688b218b1cdfa0dcbcb", + ), + ( + 128, + "2319e3789c47e2daa5fe807f61bec2a1a6537fa03f19ff32e87eecbfd64b7e0e8ccff439ac333b040f19b0c4ddd11a61e24ac1fe0f10a039806c5dcc0da3d115", + "72065ee4dd91c2d8509fa1fc28a37c7fc9fa7d5b3f8ad3d0d7a25626b57b1b44788d4caf806290425f9890a3a2a35a905ab4b37acfd0da6e4517b2525c9651e4", + ), + ( + 129, + "f59711d44a031d5f97a9413c065d1e614c417ede998590325f49bad2fd444d3e4418be19aec4e11449ac1a57207898bc57d76a1bcf3566292c20c683a5c4648f", + "64475dfe7600d7171bea0b394e27c9b00d8e74dd1e416a79473682ad3dfdbb706631558055cfc8a40e07bd015a4540dcdea15883cbbf31412df1de1cd4152b91", + ), + ( + 191, + "b9d7ca81cc60bb9578e44024e5a0a0be80f27336a6a9f4e53df3999cb191280b090e2ac2d29c5baad9d71415bdc129e69aa2667af6a7fd5e189fccdcee817340", + "e3c0739190ed849c9c962fd9dbb55e207e624fcac1eb417691515499eea8d8267b7e8f1287a63633af5011fde8c4ddf55bfdf722edf88831414f2cfaed59cb9a", + ), + ( + 192, + "a755e113386572c75ced61d719706070b9146048e42a9f8cd35667a088b42f08808abdf77e618abd959afc757379ca2c00bcc1a48390fa2bff618b1e0078a613", + "8d6cf87c08380d2d1506eee46fd4222d21d8c04e585fbfd08269c98f702833a156326a0724656400ee09351d57b440175e2a5de93cc5f80db6daf83576cf75fa", + ), + ( + 193, + "a73c7debed326f1c0db0795ee7d6e3946894b826b1f8101c56c823ba17168312e7f53fc7dbe52c3e11e69852c40485e2ef182477862ea6a34ec136e2dfeea6f4", + "da24bede383666d563eeed37f6319baf20d5c75d1635a6ba5ef4cfa1ac95487e96f8c08af600aab87c986ebad49fc70a58b4890b9c876e091016daf49e1d322e", + ), + ( + 255, + "5b21c5fd8868367612474fa2e70e9cfa2201ffeee8fafab5797ad58fefa17c9b5b107da4a3db6320baaf2c8617d5a51df914ae88da3867c2d41f0cc14fa67928", + "142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461", + ), + ]; + + fn h(s: &str) -> Vec { + hex::decode(s).unwrap() + } + + /// The KAT message of length `len`: bytes 0, 1, ..., len - 1. + fn msg(len: usize) -> &'static [u8] { + &DUMMY_SEED[..len] + } + + /// The KAT key material, bytes 0..KK_MAX, as a MAC key. + fn kat_material() -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KK_MAX], KeyType::MACKey).unwrap() + } + + /// The KAT key object. + fn kat_key() -> K { + K::from_key_material(&kat_material()).unwrap() + } + + /// A MAC key of `len` bytes 0xA5.., tagged with `strength`. + fn key_with(len: usize, strength: SecurityStrength) -> KeyMaterial<256> { + let mut key = + KeyMaterial::<256>::from_bytes_as_type(&[0xA5u8; 256][..len], KeyType::MACKey).unwrap(); + do_hazardous_operations(&mut key, |k| k.set_security_strength(strength)).unwrap(); + key + } + + fn kat(len: usize) -> (Vec, Vec) { + let (_, unkeyed, keyed) = KAT.iter().find(|(n, _, _)| *n == len).unwrap(); + (h(unkeyed), h(keyed)) + } + + #[test] + fn rfc7693_appendix_a() { + assert_eq!(H::new().hash(b"abc"), h(RFC_ABC)); + } + + /// Every embedded KAT entry, one-shot and streamed byte-at-a-time, hash and MAC. + #[test] + fn official_kat() { + for (len, unkeyed, keyed) in KAT { + let (unkeyed, keyed) = (h(unkeyed), h(keyed)); + let m = msg(len); + + assert_eq!(H::new().hash(m), unkeyed, "hash, len {len}"); + let mut hash = H::new(); + for byte in m { + hash.do_update(core::slice::from_ref(byte)); + } + assert_eq!(hash.do_final(), unkeyed, "byte-at-a-time hash, len {len}"); + + assert_eq!(kat_key().create_generator().mac(m), keyed, "mac, len {len}"); + let mut mac = kat_key().create_generator(); + for byte in m { + mac.do_update(core::slice::from_ref(byte)); + } + assert_eq!(mac.do_final(), keyed, "byte-at-a-time mac, len {len}"); + } + } + + /// Splitting the input at every point, and feeding it in every chunk size up to two blocks, + /// gives the one-shot answer. This walks every branch of the buffering in do_update. + #[test] + fn streaming_splits() { + let (unkeyed, keyed) = kat(255); + let m = msg(255); + for split in 0..=m.len() { + let mut hash = H::new(); + hash.do_update(&m[..split]); + hash.do_update(&[]); + hash.do_update(&m[split..]); + assert_eq!(hash.do_final(), unkeyed, "split at {split}"); + + let mut mac = kat_key().create_generator(); + mac.do_update(&m[..split]); + mac.do_update(&m[split..]); + assert_eq!(mac.do_final(), keyed, "keyed split at {split}"); + } + for chunk in 1..=(2 * BB + 1) { + let mut hash = H::new(); + let mut mac = kat_key().create_generator(); + for part in m.chunks(chunk) { + hash.do_update(part); + mac.do_update(part); + } + assert_eq!(hash.do_final(), unkeyed, "chunk size {chunk}"); + assert_eq!(mac.do_final(), keyed, "keyed chunk size {chunk}"); + } + } + + #[test] + fn core_test_framework_hash() { + let tf = TestFrameworkHash { enable_partial_byte_tests: false }; + tf.test_hash::(b"abc", &h(RFC_ABC)); + for len in [0, 1, 64, BB - 1, BB, BB + 1, 255] { + tf.test_hash::(msg(len), &kat(len).0); + tf.test_hash_output_buffers(H::new, msg(len)); + } + } + + #[test] + fn core_test_framework_mac() { + let tf = TestFrameworkMAC::new(); + for len in [0, 3, BB, BB + 1, 255] { + tf.test_mac_key(&kat_key(), msg(len), &kat(len).1); + } + TestFrameworkFromAnyKeyMaterial::new().test::(&kat_material()); + } + + #[test] + fn constants() { + assert_eq!(BLAKE2B_512_NAME, "BLAKE2b-512"); + assert_eq!(BLAKE2B_512_MAC_NAME, "BLAKE2b-512-MAC"); + assert_eq!(H::ALG_NAME, BLAKE2B_512_NAME); + assert_eq!(M::ALG_NAME, BLAKE2B_512_MAC_NAME); + assert_eq!(H::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + assert_eq!(M::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + assert_eq!(H::new().max_security_strength(), SecurityStrength::_256bit); + assert_eq!(kat_key().create_generator().max_security_strength(), SecurityStrength::_256bit); + assert_eq!(H::OUTPUT_LEN, NN); + assert_eq!(H::BLOCK_LEN, BB); + assert_eq!(H::new().output_len(), NN); + assert_eq!(H::default().block_bitlen(), BB * 8); + assert_eq!(kat_key().create_generator().output_len(), NN); + // RFC 7693 s. 4: id-blake2b512 = 1.3.6.1.4.1.1722.12.2.1.16. DER checked with + // `openssl asn1parse -genstr OID:1.3.6.1.4.1.1722.12.2.1.16`. + assert_eq!(H::OID, &[1, 3, 6, 1, 4, 1, 1722, 12, 2, 1, 16]); + assert_eq!(H::OID_DER, h("060b2b060104018d3a0c020110").as_slice()); + assert_eq!(STATE_LEN, 3 + 64 + 16 + BB + 1); + assert_eq!(MAC_STATE_LEN, STATE_LEN); + assert_eq!(MIN_MAC_OUTPUT_LEN, 4); + // RFC 7693 s. 4: the same OID covers keyed hashing. + assert_eq!(M::OID, H::OID); + assert_eq!(M::OID_DER, H::OID_DER); + } + + /// BLAKE2 is byte-oriented: 0 partial bits is do_final, 1..=7 is InvalidInput, more is + /// InvalidLength. + #[test] + fn partial_bits() { + let (expected, _) = kat(3); + let started = || { + let mut hash = H::new(); + hash.do_update(msg(3)); + hash + }; + + assert_eq!(started().do_final_partial_bits(0xFF, 0).unwrap(), expected); + let mut out = [0xAAu8; NN + 1]; + assert_eq!(started().do_final_partial_bits_out(0xFF, 0, &mut out).unwrap(), NN); + assert_eq!(&out[..NN], expected.as_slice()); + assert_eq!(out[NN], 0); + + for num_bits in 1..=7 { + assert!(matches!( + started().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidInput(_)) + )); + out.fill(0xAA); + assert!(matches!( + started().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidInput(_)) + )); + assert!(out.iter().all(|&b| b == 0), "the buffer is zeroed before the error"); + } + for num_bits in [8, 9, 15, 16, 64, usize::MAX] { + assert!(matches!( + started().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidLength(_)) + )); + out.fill(0xAA); + assert!(matches!( + started().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0), "the buffer is zeroed before the error"); + } + } + + /// The BLAKE2-specific construction rules; type and strength policy is the framework's. + #[test] + fn key_validation() { + // Empty key: kk = 0 is the unkeyed hash, not a MAC. + let empty = KeyMaterial::::new(); + assert!(matches!(K::from_key_material_allow_weak(&empty), Err(MACError::InvalidLength(_)))); + + // Over-long key. + let long = key_with(KK_MAX + 1, SecurityStrength::_256bit); + assert!(matches!(K::from_key_material(&long), Err(MACError::InvalidLength(_)))); + assert!(matches!(K::from_key_material_allow_weak(&long), Err(MACError::InvalidLength(_)))); + + // Boundary lengths are accepted. + assert!(K::from_key_material_allow_weak(&key_with(1, SecurityStrength::None)).is_ok()); + assert!(K::from_key_material(&key_with(KK_MAX, SecurityStrength::_256bit)).is_ok()); + + // Order: type before strength before length. + let mut wrong = key_with(KK_MAX + 1, SecurityStrength::None); + do_hazardous_operations(&mut wrong, |k| k.set_key_type(KeyType::SymmetricCipherKey)) + .unwrap(); + assert!(matches!( + K::from_key_material(&wrong), + Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) + )); + let none64 = key_with(64, SecurityStrength::None); + assert!(matches!( + K::from_key_material(&none64), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + let weak = key_with(KK_MAX, SecurityStrength::_192bit); + assert!(matches!( + K::from_key_material(&weak), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + + // An all-zero key is tagged Zeroized (no strength): allowed only as a weak key, and it is + // a real key, distinct from the unkeyed hash. + let zero = + KeyMaterial::::from_bytes_as_type(&[0u8; KK_MAX], KeyType::MACKey).unwrap(); + assert_eq!(zero.key_type(), KeyType::Zeroized); + assert!(matches!( + K::from_key_material(&zero), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + let tag = K::from_key_material_allow_weak(&zero).unwrap().mac(b"abc"); + assert_ne!(tag, H::new().hash(b"abc")); + } + + /// generate draws KK_MAX bytes from the RNG and refuses an RNG below the MAC's strength. + #[test] + fn key_generate() { + let seed: [u8; 64] = core::array::from_fn(|i| DUMMY_SEED[300 + i]); + let generated = K::generate(&mut FixedSeedRNG::<64>::new(seed)).unwrap(); + let material = + KeyMaterial::::from_bytes_as_type(&seed[..KK_MAX], KeyType::MACKey).unwrap(); + assert_eq!(generated.mac(b"abc"), K::from_key_material(&material).unwrap().mac(b"abc")); + + let mut weak_rng = FixedSeedRNG::<64>::new(seed); + weak_rng.set_security_strength(SecurityStrength::_192bit); + assert!(matches!( + K::generate(&mut weak_rng), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + } + + /// The key length kk is bound into the parameter block: a key and the same key with a zero + /// byte appended give different tags, even though they pad to the same block d[0]. + #[test] + fn key_length_bound() { + let short = key_with(KK_MAX - 1, SecurityStrength::_256bit); + let mut padded_bytes = [0xA5u8; KK_MAX]; + padded_bytes[KK_MAX - 1] = 0; + let padded = + KeyMaterial::::from_bytes_as_type(&padded_bytes, KeyType::MACKey).unwrap(); + assert_ne!( + K::from_key_material_allow_weak(&short).unwrap().create_generator().mac(b"abc"), + K::from_key_material_allow_weak(&padded).unwrap().create_generator().mac(b"abc") + ); + } + + #[test] + fn mac_output_buffers() { + let (_, tag) = kat(65); + let started = || { + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + mac + }; + + // Below the floor: rejected, through every entry point, with the buffer zeroed first. + for len in 0..MIN_MAC_OUTPUT_LEN { + let mut out = vec![0xAAu8; len]; + assert!(matches!(started().do_final_out(&mut out), Err(MACError::InvalidLength(_)))); + assert!(out.iter().all(|&b| b == 0)); + out.fill(0xAA); + assert!(matches!( + kat_key().create_generator().mac_out(msg(65), &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0)); + out.fill(0xAA); + assert!(matches!( + kat_key().mac_out(msg(65), &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0)); + } + // At the floor and above: truncated. + for len in MIN_MAC_OUTPUT_LEN..=NN { + let mut out = vec![0xAAu8; len]; + assert_eq!(started().do_final_out(&mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + let mut out = vec![0xAAu8; len]; + assert_eq!(kat_key().create_generator().mac_out(msg(65), &mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + } + // Oversized: the tag, then zeros. + let mut out = vec![0xAAu8; NN + 7]; + assert_eq!(started().do_final_out(&mut out).unwrap(), NN); + assert_eq!(&out[..NN], tag.as_slice()); + assert!(out[NN..].iter().all(|&b| b == 0)); + } + + #[test] + fn mac_verify() { + let (_, tag) = kat(65); + assert!(kat_key().verify(msg(65), &tag)); + assert!(kat_key().create_verifier().verify(msg(65), &tag)); + // Exact length only. + assert!(!kat_key().verify(msg(65), &tag[..NN - 1])); + let mut longer = tag.clone(); + longer.push(0); + assert!(!kat_key().verify(msg(65), &longer)); + // Any flipped bit fails. + for i in [0, NN / 2, NN - 1] { + let mut bad = tag.clone(); + bad[i] ^= 0x01; + assert!(!kat_key().verify(msg(65), &bad)); + assert!(!kat_key().create_verifier().verify(msg(65), &bad)); + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + assert!(!mac.do_verify_final(&bad)); + } + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + assert!(mac.do_verify_final(&tag)); + } + + #[test] + fn mac_clone() { + let (_, tag) = kat(255); + let mut mac = kat_key().create_generator(); + mac.do_update(&msg(255)[..100]); + let mut fork = mac.clone(); + mac.do_update(&msg(255)[100..]); + fork.do_update(&msg(255)[100..]); + assert_eq!(mac.do_final(), tag); + assert_eq!(fork.clone().do_final(), tag); + fork.do_update(b"x"); + assert_ne!(fork.do_final(), tag); + } + + /// Suspending at every interesting position and resuming finishes with the same digest. + #[test] + fn suspend_resume_hash() { + let (expected, _) = kat(255); + for at in [0, 1, BB - 1, BB, BB + 1, 2 * BB - 1, 255] { + let mut hash = H::new(); + hash.do_update(&msg(255)[..at]); + let state = hash.suspend(); + let mut resumed = H::from_suspended(state).unwrap(); + resumed.do_update(&msg(255)[at..]); + assert_eq!(resumed.do_final(), expected, "suspended at {at}"); + } + + let mut hash = H::new(); + hash.do_update(&msg(255)[..BB + 3]); + TestFrameworkSuspendableState::new().test(&hash); + TestFrameworkSuspendableState::new().test(&H::new()); + } + + /// Pins the documented state layout. + #[test] + fn suspend_layout() { + let mut hash = H::new(); + hash.do_update(b"abc"); + let state = hash.suspend(); + // h[0] = IV[0] ^ 0x01010000 ^ (kk << 8) ^ nn, little-endian: RFC 7693 Appendix A shows + // v[0] = 6A09E667F2BDC948 at i = 0. + assert_eq!(state[3..11], 0x6A09E667F2BDC948u64.to_le_bytes()); + assert!(state[T_OFF..BUF_OFF].iter().all(|&b| b == 0), "t = 0"); + assert_eq!(&state[BUF_OFF..BUF_OFF + 3], b"abc"); + assert_eq!(state[BUF_LEN_OFF], 3); + + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let state = hash.suspend(); + assert_eq!(state[T_OFF], BB as u8, "t = bb after one block"); + assert_eq!(state[BUF_OFF], BB as u8, "buf holds message byte bb"); + assert_eq!(state[BUF_LEN_OFF], 1); + } + + #[test] + fn suspend_rejects_bad_buf_len() { + let mut hash = H::new(); + hash.do_update(msg(BB)); + let mut state = hash.suspend(); + // A full buffer is valid ... + assert_eq!(state[BUF_LEN_OFF], BB as u8); + let (expected, _) = kat(BB); + assert_eq!(H::from_suspended(state).unwrap().do_final(), expected); + // ... one more byte is not. + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + state[BUF_LEN_OFF] = 0xFF; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + } + + /// Before any compression the hash's h must be the unkeyed initial state: a keyed state + /// (h carries kk in p[0]) or a tampered h is rejected. + #[test] + fn suspend_hash_rejects_foreign_h() { + // A fresh MAC state has the same shape as a hash state with a full buffer. + let mac_state: [u8; STATE_LEN] = kat_key().create_generator().suspend(); + assert!(matches!(H::from_suspended(mac_state), Err(SuspendableError::InvalidData))); + + let mut hash = H::new(); + hash.do_update(b"abc"); + let state = hash.suspend(); + assert!(H::from_suspended(state).is_ok()); + for i in [3, 4, (3 + T_OFF) / 2, T_OFF - 1] { + let mut st = state; + st[i] ^= 0x01; + assert!( + matches!(H::from_suspended(st), Err(SuspendableError::InvalidData)), + "h byte {i}" + ); + } + + // After a compression h is no longer the initial state, and that is fine. + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + assert!(H::from_suspended(hash.suspend()).is_ok()); + } + + /// t must be a whole number of blocks, in both the hash and the MAC state. + #[test] + fn suspend_rejects_bad_t() { + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let state = hash.suspend(); + assert_eq!(state[T_OFF], BB as u8); + for bad in [1u8, BB as u8 - 1, BB as u8 + 1] { + let mut st = state; + st[T_OFF] = bad; + assert!(matches!(H::from_suspended(st), Err(SuspendableError::InvalidData)), "t {bad}"); + } + // A larger multiple of bb is fine. + let mut st = state; + st[T_OFF + 1] = 1; + assert!(H::from_suspended(st).is_ok()); + + let key = kat_key(); + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let state = mac.suspend(); + assert_eq!(state[T_OFF], BB as u8); + for bad in [1u8, BB as u8 - 1, BB as u8 + 1] { + let mut st = state; + st[T_OFF] = bad; + assert!(matches!(M::from_suspended(st, &key), Err(SuspendableError::InvalidData))); + } + } + + /// Before any input the serialized buffer is ignored and rebuilt from the supplied key. + #[test] + fn suspend_mac_ignores_serialized_key_block() { + let key = kat_key(); + let (_, tag) = kat(3); + let mut state = key.create_generator().suspend(); + state[BUF_OFF..BUF_OFF + BB].fill(0x5A); + assert_eq!(M::from_suspended(state, &key).unwrap().mac(msg(3)), tag); + } + + /// The key never appears in a keyed state, and resuming with the key gives the KAT tag. + #[test] + fn suspend_resume_mac() { + let key = kat_key(); + let (_, tag) = kat(255); + for at in [0, 1, BB - 1, BB, BB + 1, 255] { + let mut mac = key.create_generator(); + mac.do_update(&msg(255)[..at]); + let state = mac.suspend(); + if at == 0 { + // Before any input the buffer is the key block, which must be written as zeros. + assert!(state[BUF_OFF..BUF_OFF + BB].iter().all(|&b| b == 0)); + assert_eq!(state[BUF_LEN_OFF], BB as u8); + } + let mut resumed = M::from_suspended(state, &key).unwrap(); + resumed.do_update(&msg(255)[at..]); + assert_eq!(resumed.do_final(), tag, "suspended at {at}"); + + // The KAT key 0, 1, 2, ... is also a run of the KAT message, so look for a key that + // cannot occur in the message: 0xA5 repeated. + let distinct = key_with(KK_MAX, SecurityStrength::_256bit); + let mut mac = K::from_key_material(&distinct).unwrap().create_generator(); + mac.do_update(&msg(255)[..at]); + let state = mac.suspend(); + assert!( + !state.windows(8).any(|w| w == &distinct.ref_to_bytes()[..8]), + "key bytes found in the state suspended at {at}" + ); + } + + TestFrameworkSuspendableKeyedState::new().test(&key.create_generator(), &key); + let mut mac = key.create_generator(); + mac.do_update(msg(BB + 3)); + TestFrameworkSuspendableKeyedState::new().test(&mac, &key); + } + + /// Before any input, the resumed MAC depends on the supplied key; after it, the key has been + /// folded into h and the supplied key is validated but not used. + #[test] + fn suspend_mac_wrong_key() { + let key = kat_key(); + let other = K::from_key_material(&key_with(KK_MAX, SecurityStrength::_256bit)).unwrap(); + let (_, tag) = kat(3); + + let state = key.create_generator().suspend(); + assert_ne!(M::from_suspended(state, &other).unwrap().mac(msg(3)), tag); + assert_eq!(M::from_suspended(state, &key).unwrap().mac(msg(3)), tag); + + let mut mac = key.create_generator(); + mac.do_update(msg(1)); + let state = mac.suspend(); + let mut resumed = M::from_suspended(state, &other).unwrap(); + resumed.do_update(&msg(3)[1..]); + assert_eq!(resumed.do_final(), tag); + } + + /// A wrong key type or length cannot reach resume (the key object enforces them), but a valid + /// key of a different length can: kk is bound into h, so a state taken before any input is + /// rejected. The strength is not re-checked, so a MAC from a weak key resumes. + #[test] + fn suspend_mac_key_length_and_strength() { + let state = kat_key().create_generator().suspend(); + let short = + K::from_key_material_allow_weak(&key_with(KK_MAX - 1, SecurityStrength::_256bit)) + .unwrap(); + assert!(matches!(M::from_suspended(state, &short), Err(SuspendableError::InvalidData))); + + let weak = + K::from_key_material_allow_weak(&key_with(KK_MAX, SecurityStrength::None)).unwrap(); + let weak_state = weak.create_generator().suspend(); + assert_eq!(M::from_suspended(weak_state, &weak).unwrap().mac(b"abc"), weak.mac(b"abc")); + } + + #[test] + fn suspend_mac_rejects_bad_state() { + let key = kat_key(); + + // Before any input the buffer must be the full key block. + let mut state = key.create_generator().suspend(); + state[BUF_LEN_OFF] = BB as u8 - 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + + // An unkeyed hash state of the same shape (t == 0, full buffer) is not a keyed state. + let mut hash = H::new(); + hash.do_update(&[0u8; BB]); + let hash_state = hash.suspend(); + let as_mac: [u8; MAC_STATE_LEN] = hash_state; + assert!(matches!(M::from_suspended(as_mac, &key), Err(SuspendableError::InvalidData))); + + // After input, buf_len is still bounded. + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let mut state = mac.suspend(); + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + } +} diff --git a/crypto/blake2/tests/blake2s_tests.rs b/crypto/blake2/tests/blake2s_tests.rs new file mode 100644 index 000000000..5264a986c --- /dev/null +++ b/crypto/blake2/tests/blake2s_tests.rs @@ -0,0 +1,711 @@ +//! Tests for BLAKE2s-256 and keyed BLAKE2s-256 (RFC 7693). +//! +//! Vector sources: RFC 7693 Appendix B; the official BLAKE2 KAT (blake2-kat.json, embedded below); +//! and the bc-test-data sweep in `bc_test_data.rs`. Wycheproof has no BLAKE2 vectors, so the +//! QUALITY_AND_STYLE.md "against wycheproof" requirement is satisfied vacuously. + +#[cfg(test)] +mod blake2s_tests { + use bouncycastle_blake2::*; + use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; + use bouncycastle_core::hazmat::do_hazardous_operations; + use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::symmetric_key::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; + use bouncycastle_core::traits::{ + Algorithm, AlgorithmOID, Hash, HashAlgParams, MAC, Suspendable, SuspendableKeyed, + }; + use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; + use bouncycastle_core_test_framework::hash::TestFrameworkHash; + use bouncycastle_core_test_framework::mac::TestFrameworkMAC; + use bouncycastle_core_test_framework::suspendable_state::{ + TestFrameworkSuspendableKeyedState, TestFrameworkSuspendableState, + }; + use bouncycastle_hex as hex; + + type H = BLAKE2s_256; + type K = BLAKE2s_256_MACKey; + type M = BLAKE2s_256_MAC; + /// RFC 7693 s. 2.1 block bytes bb. + const BB: usize = 64; + /// Digest bytes nn. + const NN: usize = 32; + /// Largest key, kk. + const KK_MAX: usize = 32; + const STATE_LEN: usize = SUSPENDED_BLAKE2S_256_STATE_LEN; + const MAC_STATE_LEN: usize = SUSPENDED_BLAKE2S_256_MAC_STATE_LEN; + /// Offsets in the suspended state (after the 3-byte version header) of t, buf and buf_len. + const T_OFF: usize = 3 + 32; + const BUF_OFF: usize = 3 + 40; + const BUF_LEN_OFF: usize = 3 + 104; + + /// RFC 7693 Appendix B: BLAKE2s-256("abc"), copied by script from the RFC text. + const RFC_ABC: &str = "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982"; + + /// (input length, unkeyed digest, keyed digest) for blake2s: generated by script from the + /// official BLAKE2 KAT, github.com/BLAKE2/BLAKE2 testvectors/blake2-kat.json. The input is + /// the bytes 0, 1, 2, ..., len - 1 and the key is the 32 bytes 0, 1, ..., 31. + const KAT: [(usize, &str, &str); 19] = [ + ( + 0, + "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", + "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49", + ), + ( + 1, + "e34d74dbaf4ff4c6abd871cc220451d2ea2648846c7757fbaac82fe51ad64bea", + "40d15fee7c328830166ac3f918650f807e7e01e177258cdc0a39b11f598066f1", + ), + ( + 2, + "ddad9ab15dac4549ba42f49d262496bef6c0bae1dd342a8808f8ea267c6e210c", + "6bb71300644cd3991b26ccd4d274acd1adeab8b1d7914546c1198bbe9fc9d803", + ), + ( + 3, + "e8f91c6ef232a041452ab0e149070cdd7dd1769e75b3a5921be37876c45c9900", + "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b", + ), + ( + 7, + "598001fafbe8f94ec66dc827d012cfcbba2228569f448e89ea2208c8bf769293", + "e6c8125637438d0905b749f46560ac89fd471cf8692e28fab982f73f019b83a9", + ), + ( + 8, + "c7e887b546623635e93e0495598f1726821996c2377705b93a1f636f872bfa2d", + "19fc8ca6979d60e6edd3b4541e2f967ced740df6ec1eaebbfe813832e96b2974", + ), + ( + 55, + "f4495470f226c8c214be08fdfad4bc4a2a9dbea9136a210df0d4b64929e6fc14", + "f16012d93f28851a1eb989f5d0b43f3f39ca73c9a62d5181bff237536bd348c3", + ), + ( + 56, + "e290dd270b467f34ab1c002d340fa016257ff19e5833fdbbf2cb401c3b2817de", + "2966b3cfae1e44ea996dc5d686cf25fa053fb6f67201b9e46eade85d0ad6b806", + ), + ( + 57, + "9fc7b5ded3c15042b2a6582dc39be016d24a682d5e61ad1eff9c63309848f706", + "ddb8782485e900bc60bcf4c33a6fd585680cc683d516efa03eb9985fad8715fb", + ), + ( + 63, + "e57cb79487dd57902432b250733813bd96a84efce59f650fac26e6696aefafc3", + "c65382513f07460da39833cb666c5ed82e61b9e998f4b0c4287cee56c3cc9bcd", + ), + ( + 64, + "56f34e8b96557e90c1f24b52d0c89d51086acf1b00f634cf1dde9233b8eaaa3e", + "8975b0577fd35566d750b362b0897a26c399136df07bababbde6203ff2954ed4", + ), + ( + 65, + "1b53ee94aaf34e4b159d48de352c7f0661d0a40edff95a0b1639b4090e974472", + "21fe0ceb0052be7fb0f004187cacd7de67fa6eb0938d927677f2398c132317a8", + ), + ( + 127, + "f18417b39d617ab1c18fdf91ebd0fc6d5516bb34cf39364037bce81fa04cecb1", + "ddbfea75cc467882eb3483ce5e2e756a4f4701b76b445519e89f22d60fa86e06", + ), + ( + 128, + "1fa877de67259d19863a2a34bcc6962a2b25fcbf5cbecd7ede8f1fa36688a796", + "0c311f38c35a4fb90d651c289d486856cd1413df9b0677f53ece2cd9e477c60a", + ), + ( + 129, + "5bd169e67c82c2c2e98ef7008bdf261f2ddf30b1c00f9e7f275bb3e8a28dc9a2", + "46a73a8dd3e70f59d3942c01df599def783c9da82fd83222cd662b53dce7dbdf", + ), + ( + 191, + "d120b5ef6d57ebf06eaf96bc933c967b16cbe6e2bf00741c30aa1c54ba64801f", + "91a25ec0ec0d9a567f89c4bfe1a65a0e432d07064b4190e27dfb81901fd3139b", + ), + ( + 192, + "58d212ad6f58aef0f80116b441e57f6195bfef26b61463edec1183cdb04fe76d", + "5950d39a23e1545f301270aa1a12f2e6c453776e4d6355de425cc153f9818867", + ), + ( + 193, + "b8836f51d1e29bdfdba325565360268b8fad627473edecef7eaefee837c74003", + "d79f14720c610af179a3765d4b7c0968f977962dbf655b521272b6f1e194488e", + ), + ( + 255, + "f03f5789d3336b80d002d59fdf918bdb775b00956ed5528e86aa994acb38fe2d", + "3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd", + ), + ]; + + fn h(s: &str) -> Vec { + hex::decode(s).unwrap() + } + + /// The KAT message of length `len`: bytes 0, 1, ..., len - 1. + fn msg(len: usize) -> &'static [u8] { + &DUMMY_SEED[..len] + } + + /// The KAT key material, bytes 0..KK_MAX, as a MAC key. + fn kat_material() -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KK_MAX], KeyType::MACKey).unwrap() + } + + /// The KAT key object. + fn kat_key() -> K { + K::from_key_material(&kat_material()).unwrap() + } + + /// A MAC key of `len` bytes 0xA5.., tagged with `strength`. + fn key_with(len: usize, strength: SecurityStrength) -> KeyMaterial<256> { + let mut key = + KeyMaterial::<256>::from_bytes_as_type(&[0xA5u8; 256][..len], KeyType::MACKey).unwrap(); + do_hazardous_operations(&mut key, |k| k.set_security_strength(strength)).unwrap(); + key + } + + fn kat(len: usize) -> (Vec, Vec) { + let (_, unkeyed, keyed) = KAT.iter().find(|(n, _, _)| *n == len).unwrap(); + (h(unkeyed), h(keyed)) + } + + #[test] + fn rfc7693_appendix_b() { + assert_eq!(H::new().hash(b"abc"), h(RFC_ABC)); + } + + /// Every embedded KAT entry, one-shot and streamed byte-at-a-time, hash and MAC. + #[test] + fn official_kat() { + for (len, unkeyed, keyed) in KAT { + let (unkeyed, keyed) = (h(unkeyed), h(keyed)); + let m = msg(len); + + assert_eq!(H::new().hash(m), unkeyed, "hash, len {len}"); + let mut hash = H::new(); + for byte in m { + hash.do_update(core::slice::from_ref(byte)); + } + assert_eq!(hash.do_final(), unkeyed, "byte-at-a-time hash, len {len}"); + + assert_eq!(kat_key().create_generator().mac(m), keyed, "mac, len {len}"); + let mut mac = kat_key().create_generator(); + for byte in m { + mac.do_update(core::slice::from_ref(byte)); + } + assert_eq!(mac.do_final(), keyed, "byte-at-a-time mac, len {len}"); + } + } + + /// Splitting the input at every point, and feeding it in every chunk size up to two blocks, + /// gives the one-shot answer. This walks every branch of the buffering in do_update. + #[test] + fn streaming_splits() { + let (unkeyed, keyed) = kat(255); + let m = msg(255); + for split in 0..=m.len() { + let mut hash = H::new(); + hash.do_update(&m[..split]); + hash.do_update(&[]); + hash.do_update(&m[split..]); + assert_eq!(hash.do_final(), unkeyed, "split at {split}"); + + let mut mac = kat_key().create_generator(); + mac.do_update(&m[..split]); + mac.do_update(&m[split..]); + assert_eq!(mac.do_final(), keyed, "keyed split at {split}"); + } + for chunk in 1..=(2 * BB + 1) { + let mut hash = H::new(); + let mut mac = kat_key().create_generator(); + for part in m.chunks(chunk) { + hash.do_update(part); + mac.do_update(part); + } + assert_eq!(hash.do_final(), unkeyed, "chunk size {chunk}"); + assert_eq!(mac.do_final(), keyed, "keyed chunk size {chunk}"); + } + } + + #[test] + fn core_test_framework_hash() { + let tf = TestFrameworkHash { enable_partial_byte_tests: false }; + tf.test_hash::(b"abc", &h(RFC_ABC)); + for len in [0, 1, 64, BB - 1, BB, BB + 1, 255] { + tf.test_hash::(msg(len), &kat(len).0); + tf.test_hash_output_buffers(H::new, msg(len)); + } + } + + #[test] + fn core_test_framework_mac() { + let tf = TestFrameworkMAC::new(); + for len in [0, 3, BB, BB + 1, 255] { + tf.test_mac_key(&kat_key(), msg(len), &kat(len).1); + } + TestFrameworkFromAnyKeyMaterial::new().test::(&kat_material()); + } + + #[test] + fn constants() { + assert_eq!(BLAKE2S_256_NAME, "BLAKE2s-256"); + assert_eq!(BLAKE2S_256_MAC_NAME, "BLAKE2s-256-MAC"); + assert_eq!(H::ALG_NAME, BLAKE2S_256_NAME); + assert_eq!(M::ALG_NAME, BLAKE2S_256_MAC_NAME); + assert_eq!(H::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(M::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(H::new().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(kat_key().create_generator().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(H::OUTPUT_LEN, NN); + assert_eq!(H::BLOCK_LEN, BB); + assert_eq!(H::new().output_len(), NN); + assert_eq!(H::default().block_bitlen(), BB * 8); + assert_eq!(kat_key().create_generator().output_len(), NN); + // RFC 7693 s. 4: id-blake2s256 = 1.3.6.1.4.1.1722.12.2.2.8. DER checked with + // `openssl asn1parse -genstr OID:1.3.6.1.4.1.1722.12.2.2.8`. + assert_eq!(H::OID, &[1, 3, 6, 1, 4, 1, 1722, 12, 2, 2, 8]); + assert_eq!(H::OID_DER, h("060b2b060104018d3a0c020208").as_slice()); + assert_eq!(STATE_LEN, 3 + 32 + 8 + BB + 1); + assert_eq!(MAC_STATE_LEN, STATE_LEN); + assert_eq!(MIN_MAC_OUTPUT_LEN, 4); + // RFC 7693 s. 4: the same OID covers keyed hashing. + assert_eq!(M::OID, H::OID); + assert_eq!(M::OID_DER, H::OID_DER); + } + + /// BLAKE2 is byte-oriented: 0 partial bits is do_final, 1..=7 is InvalidInput, more is + /// InvalidLength. + #[test] + fn partial_bits() { + let (expected, _) = kat(3); + let started = || { + let mut hash = H::new(); + hash.do_update(msg(3)); + hash + }; + + assert_eq!(started().do_final_partial_bits(0xFF, 0).unwrap(), expected); + let mut out = [0xAAu8; NN + 1]; + assert_eq!(started().do_final_partial_bits_out(0xFF, 0, &mut out).unwrap(), NN); + assert_eq!(&out[..NN], expected.as_slice()); + assert_eq!(out[NN], 0); + + for num_bits in 1..=7 { + assert!(matches!( + started().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidInput(_)) + )); + out.fill(0xAA); + assert!(matches!( + started().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidInput(_)) + )); + assert!(out.iter().all(|&b| b == 0), "the buffer is zeroed before the error"); + } + for num_bits in [8, 9, 15, 16, 64, usize::MAX] { + assert!(matches!( + started().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidLength(_)) + )); + out.fill(0xAA); + assert!(matches!( + started().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0), "the buffer is zeroed before the error"); + } + } + + /// The BLAKE2-specific construction rules; type and strength policy is the framework's. + #[test] + fn key_validation() { + // Empty key: kk = 0 is the unkeyed hash, not a MAC. + let empty = KeyMaterial::::new(); + assert!(matches!(K::from_key_material_allow_weak(&empty), Err(MACError::InvalidLength(_)))); + + // Over-long key. + let long = key_with(KK_MAX + 1, SecurityStrength::_128bit); + assert!(matches!(K::from_key_material(&long), Err(MACError::InvalidLength(_)))); + assert!(matches!(K::from_key_material_allow_weak(&long), Err(MACError::InvalidLength(_)))); + + // Boundary lengths are accepted. + assert!(K::from_key_material_allow_weak(&key_with(1, SecurityStrength::None)).is_ok()); + assert!(K::from_key_material(&key_with(KK_MAX, SecurityStrength::_128bit)).is_ok()); + + // Order: type before strength before length. + let mut wrong = key_with(KK_MAX + 1, SecurityStrength::None); + do_hazardous_operations(&mut wrong, |k| k.set_key_type(KeyType::SymmetricCipherKey)) + .unwrap(); + assert!(matches!( + K::from_key_material(&wrong), + Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) + )); + let none64 = key_with(64, SecurityStrength::None); + assert!(matches!( + K::from_key_material(&none64), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + let weak = key_with(KK_MAX, SecurityStrength::_112bit); + assert!(matches!( + K::from_key_material(&weak), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + + // An all-zero key is tagged Zeroized (no strength): allowed only as a weak key, and it is + // a real key, distinct from the unkeyed hash. + let zero = + KeyMaterial::::from_bytes_as_type(&[0u8; KK_MAX], KeyType::MACKey).unwrap(); + assert_eq!(zero.key_type(), KeyType::Zeroized); + assert!(matches!( + K::from_key_material(&zero), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + let tag = K::from_key_material_allow_weak(&zero).unwrap().mac(b"abc"); + assert_ne!(tag, H::new().hash(b"abc")); + } + + /// generate draws KK_MAX bytes from the RNG and refuses an RNG below the MAC's strength. + #[test] + fn key_generate() { + let seed: [u8; 64] = core::array::from_fn(|i| DUMMY_SEED[300 + i]); + let generated = K::generate(&mut FixedSeedRNG::<64>::new(seed)).unwrap(); + let material = + KeyMaterial::::from_bytes_as_type(&seed[..KK_MAX], KeyType::MACKey).unwrap(); + assert_eq!(generated.mac(b"abc"), K::from_key_material(&material).unwrap().mac(b"abc")); + + let mut weak_rng = FixedSeedRNG::<64>::new(seed); + weak_rng.set_security_strength(SecurityStrength::_112bit); + assert!(matches!( + K::generate(&mut weak_rng), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + } + + /// The key length kk is bound into the parameter block: a key and the same key with a zero + /// byte appended give different tags, even though they pad to the same block d[0]. + #[test] + fn key_length_bound() { + let short = key_with(KK_MAX - 1, SecurityStrength::_128bit); + let mut padded_bytes = [0xA5u8; KK_MAX]; + padded_bytes[KK_MAX - 1] = 0; + let padded = + KeyMaterial::::from_bytes_as_type(&padded_bytes, KeyType::MACKey).unwrap(); + assert_ne!( + K::from_key_material_allow_weak(&short).unwrap().create_generator().mac(b"abc"), + K::from_key_material_allow_weak(&padded).unwrap().create_generator().mac(b"abc") + ); + } + + #[test] + fn mac_output_buffers() { + let (_, tag) = kat(65); + let started = || { + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + mac + }; + + // Below the floor: rejected, through every entry point, with the buffer zeroed first. + for len in 0..MIN_MAC_OUTPUT_LEN { + let mut out = vec![0xAAu8; len]; + assert!(matches!(started().do_final_out(&mut out), Err(MACError::InvalidLength(_)))); + assert!(out.iter().all(|&b| b == 0)); + out.fill(0xAA); + assert!(matches!( + kat_key().create_generator().mac_out(msg(65), &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0)); + out.fill(0xAA); + assert!(matches!( + kat_key().mac_out(msg(65), &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0)); + } + // At the floor and above: truncated. + for len in MIN_MAC_OUTPUT_LEN..=NN { + let mut out = vec![0xAAu8; len]; + assert_eq!(started().do_final_out(&mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + let mut out = vec![0xAAu8; len]; + assert_eq!(kat_key().create_generator().mac_out(msg(65), &mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + } + // Oversized: the tag, then zeros. + let mut out = vec![0xAAu8; NN + 7]; + assert_eq!(started().do_final_out(&mut out).unwrap(), NN); + assert_eq!(&out[..NN], tag.as_slice()); + assert!(out[NN..].iter().all(|&b| b == 0)); + } + + #[test] + fn mac_verify() { + let (_, tag) = kat(65); + assert!(kat_key().verify(msg(65), &tag)); + assert!(kat_key().create_verifier().verify(msg(65), &tag)); + // Exact length only. + assert!(!kat_key().verify(msg(65), &tag[..NN - 1])); + let mut longer = tag.clone(); + longer.push(0); + assert!(!kat_key().verify(msg(65), &longer)); + // Any flipped bit fails. + for i in [0, NN / 2, NN - 1] { + let mut bad = tag.clone(); + bad[i] ^= 0x01; + assert!(!kat_key().verify(msg(65), &bad)); + assert!(!kat_key().create_verifier().verify(msg(65), &bad)); + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + assert!(!mac.do_verify_final(&bad)); + } + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + assert!(mac.do_verify_final(&tag)); + } + + #[test] + fn mac_clone() { + let (_, tag) = kat(255); + let mut mac = kat_key().create_generator(); + mac.do_update(&msg(255)[..100]); + let mut fork = mac.clone(); + mac.do_update(&msg(255)[100..]); + fork.do_update(&msg(255)[100..]); + assert_eq!(mac.do_final(), tag); + assert_eq!(fork.clone().do_final(), tag); + fork.do_update(b"x"); + assert_ne!(fork.do_final(), tag); + } + + /// Suspending at every interesting position and resuming finishes with the same digest. + #[test] + fn suspend_resume_hash() { + let (expected, _) = kat(255); + for at in [0, 1, BB - 1, BB, BB + 1, 2 * BB - 1, 255] { + let mut hash = H::new(); + hash.do_update(&msg(255)[..at]); + let state = hash.suspend(); + let mut resumed = H::from_suspended(state).unwrap(); + resumed.do_update(&msg(255)[at..]); + assert_eq!(resumed.do_final(), expected, "suspended at {at}"); + } + + let mut hash = H::new(); + hash.do_update(&msg(255)[..BB + 3]); + TestFrameworkSuspendableState::new().test(&hash); + TestFrameworkSuspendableState::new().test(&H::new()); + } + + /// Pins the documented state layout. + #[test] + fn suspend_layout() { + let mut hash = H::new(); + hash.do_update(b"abc"); + let state = hash.suspend(); + // h[0] = IV[0] ^ 0x01010000 ^ (kk << 8) ^ nn, little-endian: RFC 7693 Appendix B shows + // v[0] = 6B08E647 at i = 0. + assert_eq!(state[3..7], 0x6B08E647u32.to_le_bytes()); + assert!(state[T_OFF..BUF_OFF].iter().all(|&b| b == 0), "t = 0"); + assert_eq!(&state[BUF_OFF..BUF_OFF + 3], b"abc"); + assert_eq!(state[BUF_LEN_OFF], 3); + + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let state = hash.suspend(); + assert_eq!(state[T_OFF], BB as u8, "t = bb after one block"); + assert_eq!(state[BUF_OFF], BB as u8, "buf holds message byte bb"); + assert_eq!(state[BUF_LEN_OFF], 1); + } + + #[test] + fn suspend_rejects_bad_buf_len() { + let mut hash = H::new(); + hash.do_update(msg(BB)); + let mut state = hash.suspend(); + // A full buffer is valid ... + assert_eq!(state[BUF_LEN_OFF], BB as u8); + let (expected, _) = kat(BB); + assert_eq!(H::from_suspended(state).unwrap().do_final(), expected); + // ... one more byte is not. + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + state[BUF_LEN_OFF] = 0xFF; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + } + + /// Before any compression the hash's h must be the unkeyed initial state: a keyed state + /// (h carries kk in p[0]) or a tampered h is rejected. + #[test] + fn suspend_hash_rejects_foreign_h() { + // A fresh MAC state has the same shape as a hash state with a full buffer. + let mac_state: [u8; STATE_LEN] = kat_key().create_generator().suspend(); + assert!(matches!(H::from_suspended(mac_state), Err(SuspendableError::InvalidData))); + + let mut hash = H::new(); + hash.do_update(b"abc"); + let state = hash.suspend(); + assert!(H::from_suspended(state).is_ok()); + for i in [3, 4, (3 + T_OFF) / 2, T_OFF - 1] { + let mut st = state; + st[i] ^= 0x01; + assert!( + matches!(H::from_suspended(st), Err(SuspendableError::InvalidData)), + "h byte {i}" + ); + } + + // After a compression h is no longer the initial state, and that is fine. + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + assert!(H::from_suspended(hash.suspend()).is_ok()); + } + + /// t must be a whole number of blocks, in both the hash and the MAC state. + #[test] + fn suspend_rejects_bad_t() { + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let state = hash.suspend(); + assert_eq!(state[T_OFF], BB as u8); + for bad in [1u8, BB as u8 - 1, BB as u8 + 1] { + let mut st = state; + st[T_OFF] = bad; + assert!(matches!(H::from_suspended(st), Err(SuspendableError::InvalidData)), "t {bad}"); + } + // A larger multiple of bb is fine. + let mut st = state; + st[T_OFF + 1] = 1; + assert!(H::from_suspended(st).is_ok()); + + let key = kat_key(); + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let state = mac.suspend(); + assert_eq!(state[T_OFF], BB as u8); + for bad in [1u8, BB as u8 - 1, BB as u8 + 1] { + let mut st = state; + st[T_OFF] = bad; + assert!(matches!(M::from_suspended(st, &key), Err(SuspendableError::InvalidData))); + } + } + + /// Before any input the serialized buffer is ignored and rebuilt from the supplied key. + #[test] + fn suspend_mac_ignores_serialized_key_block() { + let key = kat_key(); + let (_, tag) = kat(3); + let mut state = key.create_generator().suspend(); + state[BUF_OFF..BUF_OFF + BB].fill(0x5A); + assert_eq!(M::from_suspended(state, &key).unwrap().mac(msg(3)), tag); + } + + /// The key never appears in a keyed state, and resuming with the key gives the KAT tag. + #[test] + fn suspend_resume_mac() { + let key = kat_key(); + let (_, tag) = kat(255); + for at in [0, 1, BB - 1, BB, BB + 1, 255] { + let mut mac = key.create_generator(); + mac.do_update(&msg(255)[..at]); + let state = mac.suspend(); + if at == 0 { + // Before any input the buffer is the key block, which must be written as zeros. + assert!(state[BUF_OFF..BUF_OFF + BB].iter().all(|&b| b == 0)); + assert_eq!(state[BUF_LEN_OFF], BB as u8); + } + let mut resumed = M::from_suspended(state, &key).unwrap(); + resumed.do_update(&msg(255)[at..]); + assert_eq!(resumed.do_final(), tag, "suspended at {at}"); + + // The KAT key 0, 1, 2, ... is also a run of the KAT message, so look for a key that + // cannot occur in the message: 0xA5 repeated. + let distinct = key_with(KK_MAX, SecurityStrength::_128bit); + let mut mac = K::from_key_material(&distinct).unwrap().create_generator(); + mac.do_update(&msg(255)[..at]); + let state = mac.suspend(); + assert!( + !state.windows(8).any(|w| w == &distinct.ref_to_bytes()[..8]), + "key bytes found in the state suspended at {at}" + ); + } + + TestFrameworkSuspendableKeyedState::new().test(&key.create_generator(), &key); + let mut mac = key.create_generator(); + mac.do_update(msg(BB + 3)); + TestFrameworkSuspendableKeyedState::new().test(&mac, &key); + } + + /// Before any input, the resumed MAC depends on the supplied key; after it, the key has been + /// folded into h and the supplied key is validated but not used. + #[test] + fn suspend_mac_wrong_key() { + let key = kat_key(); + let other = K::from_key_material(&key_with(KK_MAX, SecurityStrength::_128bit)).unwrap(); + let (_, tag) = kat(3); + + let state = key.create_generator().suspend(); + assert_ne!(M::from_suspended(state, &other).unwrap().mac(msg(3)), tag); + assert_eq!(M::from_suspended(state, &key).unwrap().mac(msg(3)), tag); + + let mut mac = key.create_generator(); + mac.do_update(msg(1)); + let state = mac.suspend(); + let mut resumed = M::from_suspended(state, &other).unwrap(); + resumed.do_update(&msg(3)[1..]); + assert_eq!(resumed.do_final(), tag); + } + + /// A wrong key type or length cannot reach resume (the key object enforces them), but a valid + /// key of a different length can: kk is bound into h, so a state taken before any input is + /// rejected. The strength is not re-checked, so a MAC from a weak key resumes. + #[test] + fn suspend_mac_key_length_and_strength() { + let state = kat_key().create_generator().suspend(); + let short = + K::from_key_material_allow_weak(&key_with(KK_MAX - 1, SecurityStrength::_128bit)) + .unwrap(); + assert!(matches!(M::from_suspended(state, &short), Err(SuspendableError::InvalidData))); + + let weak = + K::from_key_material_allow_weak(&key_with(KK_MAX, SecurityStrength::None)).unwrap(); + let weak_state = weak.create_generator().suspend(); + assert_eq!(M::from_suspended(weak_state, &weak).unwrap().mac(b"abc"), weak.mac(b"abc")); + } + + #[test] + fn suspend_mac_rejects_bad_state() { + let key = kat_key(); + + // Before any input the buffer must be the full key block. + let mut state = key.create_generator().suspend(); + state[BUF_LEN_OFF] = BB as u8 - 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + + // An unkeyed hash state of the same shape (t == 0, full buffer) is not a keyed state. + let mut hash = H::new(); + hash.do_update(&[0u8; BB]); + let hash_state = hash.suspend(); + let as_mac: [u8; MAC_STATE_LEN] = hash_state; + assert!(matches!(M::from_suspended(as_mac, &key), Err(SuspendableError::InvalidData))); + + // After input, buf_len is still bounded. + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let mut state = mac.suspend(); + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + } +} diff --git a/crypto/blake3/Cargo.toml b/crypto/blake3/Cargo.toml new file mode 100644 index 000000000..406fab1a4 --- /dev/null +++ b/crypto/blake3/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bouncycastle-blake3" +version.workspace = true +edition.workspace = true + +[dependencies] +bouncycastle-core.workspace = true +bouncycastle-utils.workspace = true + +[dev-dependencies] +criterion.workspace = true +bouncycastle-core-test-framework.workspace = true +bouncycastle-hex.workspace = true +bouncycastle-rng.workspace = true + +[[bench]] +name = "blake3_benches" +harness = false diff --git a/crypto/blake3/benches/blake3_benches.rs b/crypto/blake3/benches/blake3_benches.rs new file mode 100644 index 000000000..c40321e7d --- /dev/null +++ b/crypto/blake3/benches/blake3_benches.rs @@ -0,0 +1,56 @@ +//! Criterion benches for BLAKE3. +//! +//! Two cost profiles are measured: absorbing input (the chunk/tree hash) and squeezing extended +//! output (repeated root compressions, s. 2.6). The keyed_hash and derive_key modes are not benched +//! separately: they run the same compression function over the same tree, differing only in the +//! key words and a flag (s. 2.3). derive_key adds one hash of the context string at construction. + +use criterion::{Criterion, Throughput, criterion_group, criterion_main}; +use std::hint::black_box; + +use bouncycastle_blake3::BLAKE3; +use bouncycastle_core::traits::{Hash, RNG, XOF, XOFSqueezer}; +use bouncycastle_rng as rng; + +fn bench_blake3(c: &mut Criterion) { + let mut data = [0_u8; 1024]; + rng::DefaultRNG::default().next_bytes_out(&mut data).unwrap(); + + let mut digest = vec![0; BLAKE3::new().output_len()]; + + let mut group = c.benchmark_group("blake3"); + group.throughput(Throughput::Bytes(16 * 1024)); + group.bench_function("16KiB", |b| { + b.iter(|| { + let mut md = BLAKE3::new(); + for _ in 0..16 { + md.do_update(black_box(&data)); + } + _ = md.do_final_out(&mut digest); + black_box(&digest); + }) + }); + group.finish(); +} + +fn bench_blake3_squeeze(c: &mut Criterion) { + let mut output = vec![0_u8; 1024]; + + let mut group = c.benchmark_group("blake3-squeeze"); + group.throughput(Throughput::Bytes(16 * 1024)); + group.bench_function("16KiB", |b| { + b.iter(|| { + let mut md = BLAKE3::new(); + md.do_update(black_box(b"abc")); + let mut squeezer = md.into_squeezer(); + for _ in 0..16 { + squeezer.do_output_out(&mut output); + black_box(&output); + } + }) + }); + group.finish(); +} + +criterion_group!(benches, bench_blake3, bench_blake3_squeeze); +criterion_main!(benches); diff --git a/crypto/blake3/src/blake3.rs b/crypto/blake3/src/blake3.rs new file mode 100644 index 000000000..1343083ee --- /dev/null +++ b/crypto/blake3/src/blake3.rs @@ -0,0 +1,1173 @@ +//! The BLAKE3 tree hash (spec s. 2.1, 2.3-2.6) as an incremental hasher (spec s. 5.1). +//! +//! The structure -- `Output`, `ChunkState`, the CV stack, `parent_output` -- follows the reference +//! implementation that BLAKE3 spec s. 5.1 describes "as an aid to implementers", adapted to this +//! library's types: chaining values, buffered input and key words live in [`Secret`]s, and the +//! public types implement the `core` traits rather than the reference's `Hasher` API. + +use crate::compress::{ + BLOCK_LEN, CHUNK_END, CHUNK_LEN, CHUNK_START, DERIVE_KEY_CONTEXT, DERIVE_KEY_MATERIAL, IV, + KEYED_HASH, PARENT, ROOT, compress, words_from_le_bytes, +}; +use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; +use bouncycastle_core::hazmat::do_hazardous_operations; +use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +use bouncycastle_core::traits::{Hash, MAC, RNG, Suspendable, SuspendableKeyed, XOF, XOFSqueezer}; +use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_utils::{ct, min, secret::Secret}; + +/// BLAKE3 spec s. 2.1: "The chaining value of the root node, encoded as 32 bytes in little-endian +/// order, is the default-length BLAKE3 hash of the input." +const OUT_LEN: usize = 32; + +/// BLAKE3 spec s. 2.3: "The keyed_hash mode takes an input of any length together with a 256-bit +/// key." +const KEY_LEN: usize = 32; + +/// BLAKE3 spec s. 5.4: "The maximum input size is 2^64 - 1 bytes, and the chunk size is 2^10 +/// bytes, giving a maximum tree depth of 64 - 10 = 54. The CV stack thus requires 54 * 32 = 1728 +/// bytes." +const MAX_DEPTH: usize = 54; + +/// The shortest output [`MAC::do_final_out`] and [`MAC::mac_out`] will truncate a +/// [`BLAKE3KeyedHash`] tag to; a shorter buffer is rejected with [`MACError::InvalidLength`]. +/// +/// BLAKE3 itself sets no minimum (s. 2.6: outputs of "any byte length"), and BLAKE3 is not a NIST +/// algorithm, so no FIPS rule applies. The floor of 4 bytes follows this library's HMAC +/// (`bouncycastle_hmac::MIN_FIPS_DIGEST_LEN`), so that a tag through the [`MAC`] trait cannot be +/// truncated to a trivially forgeable length by accident. Extended keyed output through +/// [`BLAKE3KeyedHash::into_squeezer`] is not subject to this floor. +pub const MIN_MAC_OUTPUT_LEN: usize = 4; + +/// The first 8 words of a compression output: "The output of the compression function is most +/// often truncated to produce 256-bit chaining values" (s. 2.2). +#[inline] +fn first_8_words(words: &[u32; 16]) -> [u32; 8] { + let mut cv = [0u32; 8]; + cv.copy_from_slice(&words[..8]); + cv +} + +/// Reads a little-endian u32 from exactly 4 bytes (serialized-state decoding). +#[inline] +fn u32_from_le(bytes: &[u8]) -> u32 { + let mut b = [0u8; 4]; + b.copy_from_slice(bytes); + u32::from_le_bytes(b) +} + +/// Reads a little-endian u64 from exactly 8 bytes (serialized-state decoding). +#[inline] +fn u64_from_le(bytes: &[u8]) -> u64 { + let mut b = [0u8; 8]; + b.copy_from_slice(bytes); + u64::from_le_bytes(b) +} + +/* --------------------------------------------------------------------------------------------- + * Output: one compression held back until it is known whether it is the root (reference impl). + * ------------------------------------------------------------------------------------------- */ + +/// The inputs of a single compression, captured "just prior to choosing between" producing an +/// 8-word chaining value and, with the ROOT flag, any number of output bytes (reference impl). +/// +/// The incremental hasher cannot know whether a chunk or parent node is the root until the input +/// ends (s. 5.1.1, 5.1.2), so the last compression of each candidate is kept in this form. +#[derive(Clone)] +struct Output { + /// h0 ... h7 for this compression. + input_chaining_value: Secret<[u32; 8]>, + /// m0 ... m15 for this compression. + block_words: Secret<[u32; 16]>, + /// t: the chunk index for a chunk's last block, 0 for a parent (s. 2.4, 2.5). + counter: u64, + /// b: the number of input bytes in the block. + block_len: u32, + /// d: every flag except ROOT, which is added only when the root is finalized. + flags: u32, +} + +impl Output { + /// This node's chaining value: it is not the root, so ROOT is not set. + fn chaining_value(&self) -> [u32; 8] { + first_8_words(&compress( + &self.input_chaining_value, + &self.block_words, + self.counter, + self.block_len, + self.flags, + )) + } + + /// One 64-byte block of root output, as 16 words. + /// + /// BLAKE3 spec s. 2.6: "This is done by repeating the root compression -- that is, the very + /// last call to the compression function, which sets the ROOT flag -- with incrementing + /// values of the counter t." and "the first root compression always uses the counter value + /// t = 0 ... After the first root compression, as long as more output bytes are needed, t is + /// incremented by 1, and the root compression is repeated on otherwise the same inputs." + /// + /// Mutants note: `self.flags` never contains ROOT (it is added only here), so `| ROOT` and + /// `^ ROOT` are equivalent; a surviving `|`/`^` swap is an equivalent mutant. + /// + /// So `self.counter` is not used here: it is always 0 for a root (the root is either the only + /// chunk, index 0, or a parent node, counter 0), and the output block counter replaces it. + fn root_output_block(&self, output_block_counter: u64) -> [u32; 16] { + compress( + &self.input_chaining_value, + &self.block_words, + output_block_counter, + self.block_len, + self.flags | ROOT, + ) + } +} + +/// BLAKE3 spec s. 2.5: a parent node's single compression. +fn parent_output( + left_child_cv: &[u32; 8], + right_child_cv: &[u32; 8], + key_words: &[u32; 8], + flags: u32, +) -> Output { + let mut input_chaining_value = Secret::<[u32; 8]>::new(); + // "The input chaining value h0 ... h7 is the key words k0 ... k7." + input_chaining_value.copy_from_slice(key_words); + + let mut block_words = Secret::<[u32; 16]>::new(); + // "The message words m0 ... m7 are the chaining value of the left child, and the message + // words m8 ... m15 are the chaining value of the right child." + block_words[..8].copy_from_slice(left_child_cv); + block_words[8..].copy_from_slice(right_child_cv); + + Output { + input_chaining_value, + block_words, + // "The counter t for parent nodes is always 0." + counter: 0, + // "The number of bytes b for parent nodes is always 64." + block_len: BLOCK_LEN as u32, + // "Parent nodes set the PARENT flag." (ROOT is added by root_output_block if this + // parent turns out to be the root.) `flags` carries the mode flag of s. 2.3. + // Mutants note: `flags` is only ever a mode flag, never PARENT, so `|` and `^` are + // equivalent here; a surviving swap is an equivalent mutant. + flags: PARENT | flags, + } +} + +/* --------------------------------------------------------------------------------------------- + * Chunk state (s. 2.4, 5.1.1) + * ------------------------------------------------------------------------------------------- */ + +/// BLAKE3 spec s. 5.1.1: "The chunk state contains the 32-byte CV of the previous block and a +/// 64-byte input buffer for the next block, and typically also the compression function +/// parameters t and d." +#[derive(Clone)] +struct ChunkState { + /// The CV of the previous block; the key words before the first block is compressed. + chaining_value: Secret<[u32; 8]>, + /// t: "the chunk index, i.e., 0 for all blocks in the first chunk, 1 for all blocks in the + /// second chunk, and so on" (s. 2.4). + chunk_counter: u64, + /// The block not yet compressed. Only the first `block_len` bytes are meaningful. + block: Secret<[u8; BLOCK_LEN]>, + /// Number of meaningful bytes in `block`, 0..=64. + block_len: u8, + /// Number of blocks of this chunk already compressed, 0..=15 (the 16th block of a full chunk + /// is never compressed early, see `update`). + blocks_compressed: u8, + /// The mode flag of s. 2.3 (0, KEYED_HASH or a DERIVE_KEY_* flag). + flags: u32, +} + +impl ChunkState { + fn new(key_words: &[u32; 8], chunk_counter: u64, flags: u32) -> Self { + let mut chaining_value = Secret::<[u32; 8]>::new(); + // s. 2.4: "The input chaining value h0 ... h7 for the first block of each chunk is + // composed of the key words k0 ... k7." + chaining_value.copy_from_slice(key_words); + Self { + chaining_value, + chunk_counter, + block: Secret::new(), + block_len: 0, + blocks_compressed: 0, + flags, + } + } + + /// Number of input bytes absorbed into this chunk so far. + fn len(&self) -> usize { + BLOCK_LEN * self.blocks_compressed as usize + self.block_len as usize + } + + /// s. 2.4: "The first block of each chunk sets the CHUNK_START flag". + fn start_flag(&self) -> u32 { + if self.blocks_compressed == 0 { CHUNK_START } else { 0 } + } + + /// Absorbs `input`, which the caller guarantees fits in this chunk (`len() + input.len() <= + /// CHUNK_LEN`). + /// + /// s. 5.1.1: "Input bytes from the caller are copied into the buffer until it is full. Then the + /// buffer is compressed together with the previous CV, using the truncated compression + /// function. The output CV overwrites the previous CV, and the buffer is cleared. ... the + /// chunk state waits to compress the next block until both the buffer is full and the caller + /// has supplied more input." + fn update(&mut self, mut input: &[u8]) { + while !input.is_empty() { + // The buffer is full and more input is here, so this block is not the chunk's last: + // no CHUNK_END, no ROOT. + if self.block_len as usize == BLOCK_LEN { + let mut block_words = Secret::<[u32; 16]>::new(); + // s. 2.4: "Each block is parsed in little-endian order into message words m0 ... + // m15 and compressed." + words_from_le_bytes(&*self.block, &mut block_words); + let cv = first_8_words(&compress( + &self.chaining_value, + &block_words, + // s. 2.4: "The counter t for each block is the chunk index" + self.chunk_counter, + // s. 2.4: "The block length b is the number of input bytes in each block, + // i.e., 64 for all blocks except the last block of the last chunk" + BLOCK_LEN as u32, + // Mutants note: the mode flag and CHUNK_START are distinct bits, so `|` and + // `^` are equivalent here; a surviving swap is an equivalent mutant. + self.flags | self.start_flag(), + )); + // s. 2.4: "The input chaining value for subsequent blocks in each chunk is the + // output of the truncated compression function for the previous block." + self.chaining_value.copy_from_slice(&cv); + self.blocks_compressed += 1; + // "the buffer is cleared": only the length is reset. The stale bytes are never + // read, because `output` zero-pads everything past `block_len` itself. + self.block_len = 0; + } + + // Copy input bytes into the block buffer. + let block_len = self.block_len as usize; + let take = *min(&(BLOCK_LEN - block_len), &input.len()); + self.block[block_len..block_len + take].copy_from_slice(&input[..take]); + self.block_len += take as u8; + input = &input[take..]; + } + } + + /// The chunk's last compression, held back as an [`Output`]. + fn output(&self) -> Output { + // s. 2.4: "The last block of the last chunk may be shorter, but not empty, unless the + // entire input is empty. If necessary, the last block is padded with zeros to be 64 + // bytes." + let block_len = self.block_len as usize; + let mut padded = Secret::<[u8; BLOCK_LEN]>::new(); + padded[..block_len].copy_from_slice(&self.block[..block_len]); + let mut block_words = Secret::<[u32; 16]>::new(); + words_from_le_bytes(&*padded, &mut block_words); + + let mut input_chaining_value = Secret::<[u32; 8]>::new(); + input_chaining_value.copy_from_slice(&*self.chaining_value); + + Output { + input_chaining_value, + block_words, + counter: self.chunk_counter, + block_len: self.block_len as u32, + // s. 2.4: "the last block of each chunk sets the CHUNK_END flag. If a chunk contains + // only one block, that block sets both CHUNK_START and CHUNK_END." ROOT is added by + // root_output_block if this chunk turns out to be the root. + // Mutants note: the mode flag, CHUNK_START and CHUNK_END are distinct bits, so `|` and + // `^` are equivalent here; surviving swaps are equivalent mutants. + flags: self.flags | self.start_flag() | CHUNK_END, + } + } +} + +/* --------------------------------------------------------------------------------------------- + * The incremental hasher: chunk state + CV stack (s. 5.1) + * ------------------------------------------------------------------------------------------- */ + +/// Length of the chunk-state-and-CV-stack part of a serialized state; see +/// [`Blake3Core::write_state`]. +const CORE_STATE_LEN: usize = 32 + 8 + BLOCK_LEN + 1 + 1 + 1 + MAX_DEPTH * 32; + +/// BLAKE3 spec s. 5.1: "An incremental implementation of BLAKE3 has two major components: the +/// state of the current chunk and a stack of subtree chaining values (the "CV stack")." +/// +/// Shared by all three modes, which differ only in `key_words` and `flags` (s. 2.3). +#[derive(Clone)] +struct Blake3Core { + chunk_state: ChunkState, + /// k0 ... k7 (s. 2.3). + key_words: Secret<[u32; 8]>, + /// "Space for 54 subtree chaining values: 2^54 * CHUNK_LEN = 2^64" (reference impl). + cv_stack: Secret<[[u32; 8]; MAX_DEPTH]>, + cv_stack_len: u8, + /// The mode flag of s. 2.3, set "for every compression". + flags: u32, +} + +impl Blake3Core { + fn new(key_words: &[u32; 8], flags: u32) -> Self { + let mut kw = Secret::<[u32; 8]>::new(); + kw.copy_from_slice(key_words); + Self { + chunk_state: ChunkState::new(key_words, 0, flags), + key_words: kw, + cv_stack: Secret::new(), + cv_stack_len: 0, + flags, + } + } + + fn push_stack(&mut self, cv: &[u32; 8]) { + // The stack cannot overflow for any input BLAKE3 accepts: s. 2.1 caps the input at + // ell < 2^64 bytes, i.e. fewer than 2^54 chunks, and s. 5.4 derives the depth bound of 54 + // from exactly that. Reaching this would need 2^64 bytes of input, so a debug assertion + // is enough; in release the index below would panic rather than corrupt memory. + debug_assert!((self.cv_stack_len as usize) < MAX_DEPTH, "BLAKE3 input exceeds 2^64 bytes"); + self.cv_stack[self.cv_stack_len as usize] = *cv; + self.cv_stack_len += 1; + } + + fn pop_stack(&mut self) -> [u32; 8] { + self.cv_stack_len -= 1; + self.cv_stack[self.cv_stack_len as usize] + } + + /// BLAKE3 spec s. 5.1.2, Listing 1: "For each trailing 0-bit in the new total number of chunks, + /// pop a CV off the stack, and merge it with the new CV we were about to add. Finally, push the + /// fully merged result onto the stack." + fn add_chunk_chaining_value(&mut self, mut new_cv: [u32; 8], mut total_chunks: u64) { + // "The entries in the CV stack behave like the 1-bits in the binary representation of the + // total number of chunks so far." Each trailing 0-bit of the new total is a completed + // subtree whose left child is on top of the stack and whose right child is `new_cv`. + while total_chunks & 1 == 0 { + let left_child_cv = self.pop_stack(); + new_cv = parent_output(&left_child_cv, &new_cv, &self.key_words, self.flags) + .chaining_value(); + total_chunks >>= 1; + } + self.push_stack(&new_cv); + } + + fn update(&mut self, mut input: &[u8]) { + while !input.is_empty() { + // s. 5.1.1: "chunk CVs are added only after the caller supplies at least 1 byte for the + // following chunk" -- so a full chunk is finalized here, where more input is known to + // follow, and it is therefore not the root (s. 5.1.2: "we know that none of these + // chunks or parent nodes is the root of the tree"). + if self.chunk_state.len() == CHUNK_LEN { + let chunk_cv = self.chunk_state.output().chaining_value(); + let total_chunks = self.chunk_state.chunk_counter + 1; + self.add_chunk_chaining_value(chunk_cv, total_chunks); + self.chunk_state = ChunkState::new(&self.key_words, total_chunks, self.flags); + } + + // Absorb as much as fits in the current chunk. + let take = *min(&(CHUNK_LEN - self.chunk_state.len()), &input.len()); + self.chunk_state.update(&input[..take]); + input = &input[take..]; + } + } + + /// The root node's held-back compression. + /// + /// BLAKE3 spec s. 5.1.2: "Once we reach the end of the input, we can use a simpler algorithm + /// to compute the root hash. Finalize the CV of the current chunk, which may be incomplete or + /// empty. Then, merge that CV with every CV currently in the stack, regardless of the number + /// of chunks so far. Set the ROOT flag for the last parent node to be merged. Or if there are + /// no CVs in the stack, and thus no parents to merge, set the ROOT flag for chunk + /// finalization instead." (ROOT itself is applied by [`Output::root_output_block`].) + fn root_output(&self) -> Output { + let mut output = self.chunk_state.output(); + let mut parent_nodes_remaining = self.cv_stack_len as usize; + while parent_nodes_remaining > 0 { + parent_nodes_remaining -= 1; + output = parent_output( + &self.cv_stack[parent_nodes_remaining], + &output.chaining_value(), + &self.key_words, + self.flags, + ); + } + output + } + + /// Serializes the chunk state and CV stack (not `key_words` or `flags`, which each public type + /// handles itself) into `CORE_STATE_LEN` bytes. All integers little-endian. + /// + /// ```text + /// [0 .. 32) chunk chaining_value [u32; 8], all zero when blocks_compressed == 0 + /// [32 .. 40) chunk_counter u64 + /// [40 .. 104) block [u8; 64], zero past block_len + /// [104 .. 105) block_len u8 (0..=64) + /// [105 .. 106) blocks_compressed u8 (0..=15) + /// [106 .. 107) cv_stack_len u8 (0..=54, == chunk_counter.count_ones()) + /// [107 .. 1835) cv_stack 54 x [u32; 8], zero past cv_stack_len + /// ``` + /// + /// The chunk chaining value is omitted while no block of the chunk has been compressed, + /// because it then *is* the key words (s. 2.4: "The input chaining value h0 ... h7 for the + /// first block of each chunk is composed of the key words k0 ... k7"), and in keyed_hash mode + /// those are the key. `read_state` restores it from the key words. Stale bytes past + /// `block_len` and stale CVs past `cv_stack_len` are not written either. + fn write_state(&self, out: &mut [u8]) { + debug_assert_eq!(out.len(), CORE_STATE_LEN); + let cs = &self.chunk_state; + if cs.blocks_compressed != 0 { + for (i, word) in cs.chaining_value.iter().enumerate() { + out[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + } + out[32..40].copy_from_slice(&cs.chunk_counter.to_le_bytes()); + let block_len = cs.block_len as usize; + out[40..40 + block_len].copy_from_slice(&cs.block[..block_len]); + out[104] = cs.block_len; + out[105] = cs.blocks_compressed; + out[106] = self.cv_stack_len; + for (j, cv) in self.cv_stack[..self.cv_stack_len as usize].iter().enumerate() { + for (i, word) in cv.iter().enumerate() { + let at = 107 + j * 32 + i * 4; + out[at..at + 4].copy_from_slice(&word.to_le_bytes()); + } + } + } + + /// The inverse of [`Self::write_state`], given the key words and mode flag. + /// + /// Rejects with [`SuspendableError::InvalidData`] any state the hasher could not have reached, + /// where resuming it would index out of bounds or underflow the stack: + /// * `block_len > 64`: the buffer is one block; + /// * `blocks_compressed > 15`: a chunk is at most 16 blocks and its last block is never + /// compressed before the chunk is finalized (s. 5.1.1); + /// * `cv_stack_len > 54`: s. 5.4's maximum tree depth; + /// * `chunk_counter >= 2^54`: s. 2.1 caps the input below 2^64 bytes, i.e. 2^54 chunks. Without + /// this, a counter with 54 one-bits whose next chunk adds a 55th would overflow the stack; + /// * `cv_stack_len != chunk_counter.count_ones()`: s. 5.1.2, "The entries in the CV stack + /// behave like the 1-bits in the binary representation of the total number of chunks so + /// far". This is what guarantees `add_chunk_chaining_value` never pops an empty stack; + /// * `blocks_compressed > 0 && block_len == 0`: s. 5.1.1, a block is compressed only once "the + /// caller has supplied more input", so a compression is always followed by at least one + /// buffered byte. (Resuming such a state would finish the chunk on an empty block without + /// CHUNK_START, which no input produces.) + /// + /// While `blocks_compressed == 0` the chunk chaining value is taken from `key_words` + /// unconditionally; the serialized bytes for it are never read. + fn read_state( + input: &[u8], + key_words: &[u32; 8], + flags: u32, + ) -> Result { + debug_assert_eq!(input.len(), CORE_STATE_LEN); + let chunk_counter = u64_from_le(&input[32..40]); + let block_len = input[104]; + let blocks_compressed = input[105]; + let cv_stack_len = input[106]; + + if block_len as usize > BLOCK_LEN + || blocks_compressed > 15 + || cv_stack_len as usize > MAX_DEPTH + || chunk_counter >= (1u64 << MAX_DEPTH) + || chunk_counter.count_ones() != cv_stack_len as u32 + || (blocks_compressed > 0 && block_len == 0) + { + return Err(SuspendableError::InvalidData); + } + + let mut core = Self::new(key_words, flags); + let cs = &mut core.chunk_state; + if blocks_compressed != 0 { + words_from_le_bytes(&input[0..32], &mut cs.chaining_value); + } + // else: ChunkState::new already set the chaining value to the key words. + cs.chunk_counter = chunk_counter; + cs.block[..block_len as usize].copy_from_slice(&input[40..40 + block_len as usize]); + cs.block_len = block_len; + cs.blocks_compressed = blocks_compressed; + + for j in 0..cv_stack_len as usize { + words_from_le_bytes(&input[107 + j * 32..107 + j * 32 + 32], &mut core.cv_stack[j]); + } + core.cv_stack_len = cv_stack_len; + Ok(core) + } +} + +/* --------------------------------------------------------------------------------------------- + * Partial-bit policy + * ------------------------------------------------------------------------------------------- */ + +/// BLAKE3 is byte-oriented: s. 2.1 "BLAKE3 supports input of any byte length 0 <= ell < 2^64", and +/// the block length parameter b of s. 2.2 counts bytes. There is no defined encoding of a partial +/// final byte, so the partial-bit entry points accept only `num_bits == 0`. +/// +/// This is the convention of this library's other byte-oriented XOFs, Ascon-XOF128/CXOF128: +/// `InvalidLength` for `num_bits > 7` (the trait's rule), `InvalidInput` for `1..=7`, and 0 is the +/// whole-byte call. KMACXOF differs: it returns `InvalidLength` for every non-zero `num_bits`. +fn check_partial_bits(num_bits: usize) -> Result<(), HashError> { + if num_bits > 7 { + return Err(HashError::InvalidLength("num_bits must be in the range [0,7]")); + } + if num_bits != 0 { + return Err(HashError::InvalidInput( + "BLAKE3 is byte-oriented: a partial final byte (num_bits in 1..=7) cannot be hashed", + )); + } + Ok(()) +} + +/* --------------------------------------------------------------------------------------------- + * BLAKE3: hash and derive_key modes + * ------------------------------------------------------------------------------------------- */ + +/// BLAKE3 in its `hash` and `derive_key` modes (spec s. 2.3), usable as a [`Hash`] and as a +/// [`XOF`]. +/// +/// * [`BLAKE3::new`] (and [`Default`]) is the `hash` mode: "k0 ... k7 are the constants +/// IV0 ... IV7, and no additional flags are set." +/// * [`BLAKE3::new_derive_key`] is the `derive_key` mode: the context string is hashed into a +/// context key, and the key material is then fed through [`Hash::do_update`]. +/// +/// As a [`Hash`] it produces the default 32-byte output; as a [`XOF`] any length, and "Shorter +/// outputs are prefixes of longer ones" (s. 2.6). +/// +/// Neither mode holds a secret key -- the `derive_key` context key is derived from a public, +/// hardcoded string -- so this type implements [`Suspendable`]. For the keyed mode see +/// [`BLAKE3KeyedHash`]. +/// +/// See the [crate-level documentation](crate) for usage. +#[derive(Clone)] +pub struct BLAKE3 { + core: Blake3Core, +} + +impl BLAKE3 { + /// A new BLAKE3 in `hash` mode, ready for use. + pub fn new() -> Self { + // s. 2.3: "In the hash mode, k0 ... k7 are the constants IV0 ... IV7, and no additional + // flags are set." + Self { core: Blake3Core::new(&IV, 0) } + } + + /// A new BLAKE3 in `derive_key` mode for the given context string. The key material is then + /// supplied through [`Hash::do_update`] (or the one-shot [`Hash::hash`] / [`XOF::xof`]), and + /// the output is the derived key. + /// + /// The context string "should be hardcoded, globally unique, and application-specific" and + /// "should not contain variable data, like salts, IDs, or the current time" (s. 6.2); see the + /// crate's Security Considerations. + pub fn new_derive_key(context: &[u8]) -> Self { + // s. 2.3: "First the context string is hashed, with k0 ... k7 set to the constants + // IV0 ... IV7, and the DERIVE_KEY_CONTEXT flag set for every compression." + let mut context_hasher = Blake3Core::new(&IV, DERIVE_KEY_CONTEXT); + context_hasher.update(context); + + // "Then the key material is hashed, with k0 ... k7 set to the first 8 output words of the + // first stage, and the DERIVE_KEY_MATERIAL flag set for every compression." The first + // 8 output words are the first 8 words of root output block 0 (s. 2.6). + let first_stage_output = context_hasher.root_output().root_output_block(0); + Self { core: Blake3Core::new(&first_8_words(&first_stage_output), DERIVE_KEY_MATERIAL) } + } +} + +impl Default for BLAKE3 { + fn default() -> Self { + Self::new() + } +} + +impl Hash for BLAKE3 { + /// BLAKE3 spec s. 2.2: the message block is 512 bits. + fn block_bitlen(&self) -> usize { + BLOCK_LEN * 8 + } + + /// The default output length, 32 bytes (s. 2.1). This is nominal: BLAKE3 does not bind the + /// output length into the computation (s. 2.6), so use the [`XOF`] methods for other lengths. + fn output_len(&self) -> usize { + OUT_LEN + } + + fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut output = vec![0u8; OUT_LEN]; + self.do_final_out(&mut output); + output + } + + /// Writes `min(output.len(), 32)` bytes of the default-length hash and zeroizes the rest of + /// `output`. To fill a longer buffer with extended output, use [`XOF::xof_out`] or + /// [`XOFSqueezer::do_output_out`]. + fn do_final_out(self, output: &mut [u8]) -> usize { + output.fill(0); + let n = *min(&output.len(), &OUT_LEN); + self.into_squeezer().do_output_out(&mut output[..n]) + } + + /// Only `num_bits == 0` is accepted (BLAKE3 is byte-oriented; s. 2.1): it is identical to + /// [`Hash::do_final`] and `partial_byte` is ignored. `num_bits` in `1..=7` returns + /// [`HashError::InvalidInput`], and larger values [`HashError::InvalidLength`]. + fn do_final_partial_bits( + self, + _partial_byte: u8, + num_bits: usize, + ) -> Result, HashError> { + check_partial_bits(num_bits)?; + Ok(self.do_final()) + } + + /// As [`Hash::do_final_partial_bits`]; `output` is zeroized first, so a rejected call leaves + /// it all zeros. + fn do_final_partial_bits_out( + self, + _partial_byte: u8, + num_bits: usize, + output: &mut [u8], + ) -> Result { + output.fill(0); + check_partial_bits(num_bits)?; + Ok(self.do_final_out(output)) + } + + /// BLAKE3 spec s. 4.1: "BLAKE3 targets 128-bit security for all of its security goals." + fn max_security_strength(&self) -> SecurityStrength { + SecurityStrength::_128bit + } +} + +/// The absorb-then-squeeze rule is a compile error: +/// +/// ```compile_fail +/// use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; +/// use bouncycastle_blake3::BLAKE3; +/// +/// let mut blake3 = BLAKE3::new(); +/// blake3.do_update(b"abc"); +/// let mut out = blake3.into_squeezer(); +/// let _ = out.do_output(32); +/// blake3.do_update(b"more"); // `blake3` was moved by into_squeezer() +/// ``` +impl XOF for BLAKE3 { + type Squeezer = BLAKE3Squeezer; + + fn into_squeezer(self) -> Self::Squeezer { + BLAKE3Squeezer::new(self.core.root_output()) + } + + /// Only `num_bits == 0` is accepted; see [`Hash::do_final_partial_bits`] on [`BLAKE3`]. + fn into_squeezer_partial_bits( + self, + _partial_byte: u8, + num_bits: usize, + ) -> Result { + check_partial_bits(num_bits)?; + Ok(self.into_squeezer()) + } +} + +/// Length in bytes of the serialized state of [`BLAKE3`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian): +/// ```text +/// [0 .. 4) flags u32: 0 (hash mode) or DERIVE_KEY_MATERIAL (derive_key mode) +/// [4 .. 36) key_words [u32; 8]: IV in hash mode, the context key in derive_key mode +/// [36 .. 1871) the chunk state and CV stack: +/// [36 .. 68) chunk chaining_value [u32; 8], all zero when blocks_compressed == 0 +/// [68 .. 76) chunk_counter u64 (< 2^54) +/// [76 .. 140) block [u8; 64], zero past block_len +/// [140 .. 141) block_len u8 (0..=64) +/// [141 .. 142) blocks_compressed u8 (0..=15) +/// [142 .. 143) cv_stack_len u8 (0..=54, == chunk_counter.count_ones()) +/// [143 .. 1871) cv_stack 54 x [u32; 8], zero past cv_stack_len +/// ``` +pub const SUSPENDED_BLAKE3_STATE_LEN: usize = 3 + 4 + 32 + CORE_STATE_LEN; + +impl Suspendable for BLAKE3 { + fn suspend(self) -> [u8; SUSPENDED_BLAKE3_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE3_STATE_LEN]; + // infallible: add_lib_ver returns a slice of exactly SUSPENDED_BLAKE3_STATE_LEN - 3 bytes. + let out: &mut [u8; SUSPENDED_BLAKE3_STATE_LEN - 3] = + add_lib_ver(&mut out_to_return).try_into().unwrap(); + + out[0..4].copy_from_slice(&self.core.flags.to_le_bytes()); + for (i, word) in self.core.key_words.iter().enumerate() { + out[4 + i * 4..4 + i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + self.core.write_state(&mut out[36..]); + + out_to_return + } + + /// Rejects with [`SuspendableError::InvalidData`]: a flags word other than 0 or + /// DERIVE_KEY_MATERIAL (a `keyed_hash` state is never in this format, see + /// [`BLAKE3KeyedHash`]); hash-mode key words other than IV0 ... IV7 (s. 2.3); and the + /// chunk-state and CV-stack inconsistencies listed in the layout above. + fn from_suspended( + serialized_state: [u8; SUSPENDED_BLAKE3_STATE_LEN], + ) -> Result { + // infallible: check_lib_ver returns a slice of exactly SUSPENDED_BLAKE3_STATE_LEN - 3 + // bytes. + let input: &[u8; SUSPENDED_BLAKE3_STATE_LEN - 3] = + check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + let flags = u32_from_le(&input[0..4]); + let mut key_words = Secret::<[u32; 8]>::new(); + words_from_le_bytes(&input[4..36], &mut key_words); + + // s. 2.3: hash mode has "no additional flags" and k = IV; derive_key's second stage sets + // DERIVE_KEY_MATERIAL. Nothing else can be a BLAKE3 (absorbing) state. + let hash_mode = flags == 0 && *key_words == IV; + if !(hash_mode || flags == DERIVE_KEY_MATERIAL) { + return Err(SuspendableError::InvalidData); + } + + let core = Blake3Core::read_state(&input[36..], &key_words, flags)?; + Ok(Self { core }) + } +} + +/* --------------------------------------------------------------------------------------------- + * The output phase + * ------------------------------------------------------------------------------------------- */ + +/// The squeezing half of [`BLAKE3`]: what [`XOF::into_squeezer`] hands back. +/// +/// It holds the root node's compression inputs and produces "outputs of any byte length +/// 0 <= ell < 2^64 ... by repeating the root compression ... with incrementing values of the +/// counter t" (s. 2.6). Successive reads continue one stream. +#[derive(Clone)] +pub struct BLAKE3Squeezer { + /// The root compression (without ROOT, which `root_output_block` adds). + root: Output, + /// t for the next root compression. + output_block_counter: u64, + /// The current 64-byte output block. Derive_key output is a key, hence the `Secret`. + buf: Secret<[u8; BLOCK_LEN]>, + /// Bytes of `buf` already handed out, 0..=64; 64 means a new block is needed. + buf_pos: u8, +} + +impl BLAKE3Squeezer { + fn new(root: Output) -> Self { + Self { root, output_block_counter: 0, buf: Secret::new(), buf_pos: BLOCK_LEN as u8 } + } +} + +impl XOFSqueezer for BLAKE3Squeezer { + fn do_output(&mut self, num_bytes: usize) -> Vec { + let mut out = vec![0u8; num_bytes]; + self.do_output_out(&mut out); + out + } + + fn do_output_out(&mut self, output: &mut [u8]) -> usize { + // Zeroized first per the house rule, although every byte is then overwritten. + output.fill(0); + let mut written = 0; + while written < output.len() { + if self.buf_pos as usize == BLOCK_LEN { + // s. 2.6: "When building the output, BLAKE3 uses the full output of the + // compression function (cf. s. 2.2). Each 16-word output is encoded as 64 bytes in + // little-endian order." + let words = self.root.root_output_block(self.output_block_counter); + for (i, word) in words.iter().enumerate() { + self.buf[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + // "as long as more output bytes are needed, t is incremented by 1". This cannot + // overflow: 2^64 output blocks would be 2^70 bytes, past s. 2.6's ell < 2^64. + self.output_block_counter += 1; + self.buf_pos = 0; + } + // "If the target output length is not a multiple of 64, the final compression output + // is truncated" -- the rest of the block stays buffered for the next read. + let pos = self.buf_pos as usize; + let take = *min(&(BLOCK_LEN - pos), &(output.len() - written)); + output[written..written + take].copy_from_slice(&self.buf[pos..pos + take]); + self.buf_pos += take as u8; + written += take; + } + written + } +} + +/// Length in bytes of the serialized state of [`BLAKE3Squeezer`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian): +/// ```text +/// [0 .. 32) root input_chaining_value [u32; 8] +/// [32 .. 96) root block_words [u32; 16] +/// [96 .. 100) root block_len u32 +/// [100 .. 104) root flags u32 (without ROOT; never KEYED_HASH) +/// [104 .. 112) output_block_counter u64 +/// [112 .. 176) output buffer [u8; 64] +/// [176 .. 177) buffer position u8 (0..=64) +/// ``` +/// The root's own counter is not stored: s. 2.6, "the first root compression always uses the +/// counter value t = 0", and the output block counter takes its place. The stream position is +/// `64 * (output_block_counter - 1) + buffer position` once a block has been produced. +pub const SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN: usize = 3 + 177; + +/// A long output stream can be paused and resumed. Only the squeezer of [`BLAKE3`] implements +/// this; the keyed squeezer does not, see [`BLAKE3KeyedSqueezer`]. +impl Suspendable for BLAKE3Squeezer { + fn suspend(self) -> [u8; SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN]; + // infallible: add_lib_ver returns a slice of exactly 177 bytes. + let out: &mut [u8; 177] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + + for (i, word) in self.root.input_chaining_value.iter().enumerate() { + out[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + for (i, word) in self.root.block_words.iter().enumerate() { + out[32 + i * 4..32 + i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + out[96..100].copy_from_slice(&self.root.block_len.to_le_bytes()); + out[100..104].copy_from_slice(&self.root.flags.to_le_bytes()); + out[104..112].copy_from_slice(&self.output_block_counter.to_le_bytes()); + out[112..176].copy_from_slice(&*self.buf); + out[176] = self.buf_pos; + + out_to_return + } + + /// Rejects with [`SuspendableError::InvalidData`] a buffer position past 64, and root flags + /// that no [`BLAKE3`] root can have: the stored flags are a node-type part (CHUNK_START, + /// CHUNK_END, PARENT) plus a mode part that must be 0 or DERIVE_KEY_MATERIAL. In particular + /// KEYED_HASH is rejected (a keyed stream is never suspendable, see [`BLAKE3KeyedSqueezer`]), + /// as is ROOT (it is added per output block, never stored). Also rejected, as states no + /// squeezer reaches: + /// * a root `block_len` above 64: a root is a chunk's last block (b <= 64, s. 2.4) or a + /// parent node (b = 64, s. 2.5); + /// * `output_block_counter == 0` with a position below 64: a fresh squeezer starts at position + /// 64 (no block computed), and computing a block makes the counter at least 1, so such a + /// state would hand out buffer bytes that were never computed. + fn from_suspended( + serialized_state: [u8; SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN], + ) -> Result { + // infallible: check_lib_ver returns a slice of exactly 177 bytes. + let input: &[u8; 177] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + let buf_pos = input[176]; + let flags = u32_from_le(&input[100..104]); + // Mutants note: the three node-type flags are distinct bits, so `|` and `^` build the same + // mask; surviving swaps here are equivalent mutants. + let mode_flags = flags & !(CHUNK_START | CHUNK_END | PARENT); + let block_len = u32_from_le(&input[96..100]); + let output_block_counter = u64_from_le(&input[104..112]); + if buf_pos as usize > BLOCK_LEN + || !(mode_flags == 0 || mode_flags == DERIVE_KEY_MATERIAL) + || block_len as usize > BLOCK_LEN + || (output_block_counter == 0 && (buf_pos as usize) < BLOCK_LEN) + { + return Err(SuspendableError::InvalidData); + } + + let mut input_chaining_value = Secret::<[u32; 8]>::new(); + words_from_le_bytes(&input[0..32], &mut input_chaining_value); + let mut block_words = Secret::<[u32; 16]>::new(); + words_from_le_bytes(&input[32..96], &mut block_words); + let root = Output { input_chaining_value, block_words, counter: 0, block_len, flags }; + let mut buf = Secret::<[u8; BLOCK_LEN]>::new(); + buf.copy_from_slice(&input[112..176]); + + Ok(Self { root, output_block_counter, buf, buf_pos }) + } +} + +/* --------------------------------------------------------------------------------------------- + * BLAKE3KeyedHash: keyed_hash mode + * ------------------------------------------------------------------------------------------- */ + +/// A BLAKE3 `keyed_hash` key (spec s. 2.3): the key words k0 ... k7, "parsed in little-endian +/// order from the 256-bit key given by the caller". +/// +/// Construction is [`FromAnyKeyMaterial`]: the material must be exactly 32 bytes, tagged +/// [`KeyType::MACKey`] (or [`KeyType::Zeroized`]). An empty key is refused like any other length; +/// unkeyed BLAKE3 is [`BLAKE3`]. The workers come from [`MacGenerationKey`] and +/// [`MacVerificationKey`], each holding its own copy of the key words. +#[derive(Clone)] +pub struct BLAKE3KeyedHashKey { + key_words: Secret<[u32; 8]>, +} + +impl BLAKE3KeyedHashKey { + fn from_key_material_checked( + key: &dyn KeyMaterialTrait, + allow_weak_key: bool, + ) -> Result { + // Same stance as HMAC and KMAC: an all-zero key is `Zeroized` rather than `MACKey`, and is + // let through so callers are not forced to re-tag it. + if !(key.key_type() == KeyType::MACKey || key.key_type() == KeyType::Zeroized) { + return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( + "Key type must be a MAC key.", + ))); + } + // Strength before length, so that an under-strength key is reported as such whatever its + // length (the shared key-construction suite relies on this). + if !allow_weak_key && key.security_strength() < SecurityStrength::_128bit { + Err(KeyMaterialError::SecurityStrength( + "BLAKE3KeyedHashKey: the key's security strength is below 128 bits", + ))? + } + // s. 2.3: "a 256-bit key". BLAKE3 defines no way to use a key of any other length. + if key.key_len() != KEY_LEN { + return Err(MACError::InvalidLength("BLAKE3 keyed_hash requires a 32-byte key")); + } + let mut key_words = Secret::<[u32; 8]>::new(); + words_from_le_bytes(key.ref_to_bytes(), &mut key_words); + Ok(Self { key_words }) + } +} + +impl FromAnyKeyMaterial for BLAKE3KeyedHashKey { + /// # Errors + /// [`MACError::KeyMaterialError`] if the material is not tagged [`KeyType::MACKey`] or + /// [`KeyType::Zeroized`], or is tagged below 128-bit security; [`MACError::InvalidLength`] if + /// it is not exactly 32 bytes. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result { + Self::from_key_material_checked(key, false) + } + + /// As [`from_key_material`](Self::from_key_material) without the strength check. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result { + Self::from_key_material_checked(key, true) + } + + /// Draws a 32-byte key from `rng`, tagged [`KeyType::MACKey`] at the RNG's strength. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails, or with + /// [`KeyMaterialError::SecurityStrength`] if its strength is below 128 bits. + fn generate(rng: &mut dyn RNG) -> Result { + let mut key = KeyMaterial256::new(); + do_hazardous_operations(&mut key, |key| { + rng.next_bytes_out(&mut key.ref_to_bytes_mut()?[..KEY_LEN]) + .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + key.set_key_len(KEY_LEN)?; + key.set_key_type(KeyType::MACKey)?; + // The RNG's strength, capped at what the length carries, as KMACKey::generate does. + // Mutants note: `<` and `<=` pick the same value when the two are equal, so a + // surviving `<=` is an equivalent mutant. + let by_len = SecurityStrength::from_bits(KEY_LEN * 8); + let by_rng = rng.security_strength(); + key.set_security_strength(if by_rng < by_len { by_rng } else { by_len }) + })?; + Self::from_key_material(&key) + } +} + +impl MacGenerationKey for BLAKE3KeyedHashKey { + type Generator<'k> + = BLAKE3KeyedHash + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + // s. 2.3: "In the keyed_hash mode, k0 ... k7 are parsed ... from the 256-bit key ..., and + // the KEYED_HASH flag is set for every compression." + BLAKE3KeyedHash { core: Blake3Core::new(&self.key_words, KEYED_HASH) } + } +} + +impl MacVerificationKey for BLAKE3KeyedHashKey { + type Verifier<'k> + = BLAKE3KeyedHash + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + self.create_generator() + } +} + +/// BLAKE3 in its `keyed_hash` mode (spec s. 2.3), as a [`MAC`]: the worker a +/// [`BLAKE3KeyedHashKey`] hands out. +/// +/// The [`MAC`] methods produce the default 32-byte tag; [`BLAKE3KeyedHash::into_squeezer`] gives +/// keyed output of any length (BLAKE3 as a PRF or XOF). +#[derive(Clone)] +pub struct BLAKE3KeyedHash { + core: Blake3Core, +} + +impl BLAKE3KeyedHash { + /// Ends the input and returns a squeezer for keyed output of any length (s. 2.6). Its first + /// 32 bytes are the [`MAC`] tag. No minimum length applies: choosing a safe output length is + /// the caller's responsibility. + pub fn into_squeezer(self) -> BLAKE3KeyedSqueezer { + BLAKE3KeyedSqueezer(BLAKE3Squeezer::new(self.core.root_output())) + } +} + +impl MAC for BLAKE3KeyedHash { + fn output_len(&self) -> usize { + OUT_LEN + } + + fn mac(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + fn mac_out(mut self, data: &[u8], out: &mut [u8]) -> Result { + out.fill(0); + self.do_update(data); + self.do_final_out(out) + } + + fn verify(mut self, data: &[u8], mac: &[u8]) -> bool { + self.do_update(data); + self.do_verify_final(mac) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut out = vec![0u8; OUT_LEN]; + self.into_squeezer().do_output_out(&mut out); + out + } + + /// Zeroizes `out` (also on error), then writes `min(out.len(), 32)` bytes of the tag (a + /// shorter buffer truncates it) and returns that count. + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than [`MIN_MAC_OUTPUT_LEN`]. + fn do_final_out(self, out: &mut [u8]) -> Result { + out.fill(0); + if out.len() < MIN_MAC_OUTPUT_LEN { + return Err(MACError::InvalidLength( + "BLAKE3KeyedHash: output buffer is shorter than MIN_MAC_OUTPUT_LEN", + )); + } + let n = *min(&out.len(), &OUT_LEN); + Ok(self.into_squeezer().do_output_out(&mut out[..n])) + } + + /// Compares in constant time against the full 32-byte tag; a truncated or extended `mac` does + /// not verify. + fn do_verify_final(self, mac: &[u8]) -> bool { + if mac.len() != OUT_LEN { + return false; + } + let computed = self.do_final(); + ct::ct_eq_bytes(&computed, mac) + } + + /// BLAKE3 spec s. 4.1: "BLAKE3 targets 128-bit security for all of its security goals." (The + /// 256-bit key is "an extra defense layer against weakly chosen keys", not a higher target.) + fn max_security_strength(&self) -> SecurityStrength { + SecurityStrength::_128bit + } +} + +/// Length in bytes of the serialized state of [`BLAKE3KeyedHash`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian). The key words and +/// the KEYED_HASH flag are not stored: the key is re-supplied to +/// [`SuspendableKeyed::from_suspended`] and the flag is implied by the type. +/// ```text +/// [0 .. 32) chunk chaining_value [u32; 8], all zero when blocks_compressed == 0 +/// (it is then the key itself) +/// [32 .. 40) chunk_counter u64 (< 2^54) +/// [40 .. 104) block [u8; 64], zero past block_len +/// [104 .. 105) block_len u8 (0..=64) +/// [105 .. 106) blocks_compressed u8 (0..=15) +/// [106 .. 107) cv_stack_len u8 (0..=54, == chunk_counter.count_ones()) +/// [107 .. 1835) cv_stack 54 x [u32; 8], zero past cv_stack_len +/// ``` +pub const SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// The key is never written into the serialized state; resume takes the key object. +/// +/// A wrong key cannot be detected: the resumed MAC silently produces a different tag, which will +/// then fail verification -- *if* the key is still needed. It is not always: the key words enter +/// only as the first chaining value of each chunk and of each parent node (s. 2.4, 2.5). While +/// the absorbed prefix P is 65..=1024 bytes (first chunk, at least one block compressed, CV stack +/// empty), finishing a message that stays a single chunk needs no key. **Treat a suspended keyed +/// state as secret**: whoever holds such a state can compute the tag of P || X for every X that +/// keeps the total at or under 1024 bytes. Crossing the chunk boundary starts a new chunk from the +/// key words, and once the CV stack is non-empty the root is a parent node keyed by the key words, +/// so those extensions still need the key. +// The only place the key words would otherwise appear is the current chunk's chaining value +// before its first block is compressed (s. 2.4), so `write_state` zeroes that field while +// `blocks_compressed == 0` and `read_state` rebuilds it from the key; every other stored value is +// a compression output. The worker owns its key words, so `'k` is unused. +impl<'k> SuspendableKeyed<'k, SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN> for BLAKE3KeyedHash { + type Key = BLAKE3KeyedHashKey; + + fn suspend(self) -> [u8; SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN]; + // infallible: add_lib_ver returns a slice of exactly CORE_STATE_LEN bytes. + let out: &mut [u8; CORE_STATE_LEN] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + self.core.write_state(out); + out_to_return + } + + fn from_suspended( + state: [u8; SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN], + key: &'k Self::Key, + ) -> Result { + // infallible: check_lib_ver returns a slice of exactly CORE_STATE_LEN bytes. + let input: &[u8; CORE_STATE_LEN] = check_lib_ver(&state, None)?.try_into().unwrap(); + let core = Blake3Core::read_state(input, &key.key_words, KEYED_HASH)?; + Ok(Self { core }) + } +} + +/// The squeezing half of [`BLAKE3KeyedHash`]: what [`BLAKE3KeyedHash::into_squeezer`] hands back. +/// +/// It deliberately does **not** implement [`Suspendable`]. A squeezer holds the root compression's +/// inputs, and in keyed_hash mode the root's input chaining value is the key words themselves for +/// almost every message: +/// * every multi-chunk message, whose root is a parent node (s. 2.5: "The input chaining value +/// h0 ... h7 is the key words k0 ... k7"); +/// * every single-chunk message of at most 64 bytes, whose root is the chunk's first block (s. 2.4: +/// "The input chaining value h0 ... h7 for the first block of each chunk is composed of the key +/// words k0 ... k7"). +/// +/// Only a single-chunk message of more than 64 bytes has a root CV that is not the key. Serializing +/// the squeezer would therefore write the key out, which [`Suspendable`] forbids ("it MUST NOT +/// include a serialized private key"). +/// +/// There is no route to suspending it: +/// +/// ```compile_fail +/// use bouncycastle_blake3::BLAKE3KeyedHashKey; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +/// use bouncycastle_core::traits::Suspendable; +/// +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); +/// let key = BLAKE3KeyedHashKey::from_key_material(&material).unwrap(); +/// let _ = key.create_generator().into_squeezer().suspend(); // not Suspendable +/// ``` +#[derive(Clone)] +pub struct BLAKE3KeyedSqueezer(BLAKE3Squeezer); + +impl XOFSqueezer for BLAKE3KeyedSqueezer { + fn do_output(&mut self, num_bytes: usize) -> Vec { + self.0.do_output(num_bytes) + } + + fn do_output_out(&mut self, output: &mut [u8]) -> usize { + self.0.do_output_out(output) + } +} diff --git a/crypto/blake3/src/compress.rs b/crypto/blake3/src/compress.rs new file mode 100644 index 000000000..81d264b8b --- /dev/null +++ b/crypto/blake3/src/compress.rs @@ -0,0 +1,159 @@ +//! The BLAKE3 compression function and its constants (BLAKE3 spec s. 2.2). +//! +//! Everything in this module is a direct transcription of s. 2.2 and Tables 1-3. The structure +//! (`g`, `round`, `permute`, `compress`) follows the reference implementation that the spec's +//! s. 5.1 describes "as an aid to implementers". +//! +//! There are no data-dependent branches or table lookups here: the only indexing is by the +//! fixed `MSG_PERMUTATION` schedule and fixed state positions. + +/// BLAKE3 spec s. 2.4: "Each block is parsed in little-endian order into message words m0 ... m15", +/// so a block is 16 x 4 = 64 bytes. +pub(crate) const BLOCK_LEN: usize = 64; + +/// BLAKE3 spec s. 2.1: "The input of BLAKE3 is split into contiguous chunks of 1024 bytes". +pub(crate) const CHUNK_LEN: usize = 1024; + +/// BLAKE3 spec s. 2.2, Table 1: "Constants IV0 ... IV7 used in the initialization of BLAKE3." +/// ("The IV0 ... IV7 constants are the same as in BLAKE2s".) +pub(crate) const IV: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +]; + +/// BLAKE3 spec s. 2.2, Table 2: "Permutational key schedule for BLAKE3's keyed permutation." +/// Entry `i` names the message word that moves to position `i`. +const MSG_PERMUTATION: [usize; 16] = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8]; + +// BLAKE3 spec s. 2.2, Table 3: "Admissible values for input d in the BLAKE3 compression function." +// "The value of d is the sum of all the flags defined for a given compression." + +/// Table 3: CHUNK_START = 2^0. +/// Mutants note: `1 << 0` and `1 >> 0` are both 1, so a surviving `<<`/`>>` swap here is an +/// equivalent mutant. +pub(crate) const CHUNK_START: u32 = 1 << 0; +/// Table 3: CHUNK_END = 2^1. +pub(crate) const CHUNK_END: u32 = 1 << 1; +/// Table 3: PARENT = 2^2. +pub(crate) const PARENT: u32 = 1 << 2; +/// Table 3: ROOT = 2^3. +pub(crate) const ROOT: u32 = 1 << 3; +/// Table 3: KEYED_HASH = 2^4. +pub(crate) const KEYED_HASH: u32 = 1 << 4; +/// Table 3: DERIVE_KEY_CONTEXT = 2^5. +pub(crate) const DERIVE_KEY_CONTEXT: u32 = 1 << 5; +/// Table 3: DERIVE_KEY_MATERIAL = 2^6. +pub(crate) const DERIVE_KEY_MATERIAL: u32 = 1 << 6; + +/// BLAKE3 spec s. 2.2: the "quarter-round" G_i(a, b, c, d), where `mx` = m_{2i+0} and +/// `my` = m_{2i+1}. "+ denote addition modulo 2^32, >>> denote bitwise right rotation". +#[inline] +fn g(v: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, mx: u32, my: u32) { + // a <- a + b + m_{2i+0} + v[a] = v[a].wrapping_add(v[b]).wrapping_add(mx); + // d <- (d ^ a) >>> 16 + v[d] = (v[d] ^ v[a]).rotate_right(16); + // c <- c + d + v[c] = v[c].wrapping_add(v[d]); + // b <- (b ^ c) >>> 12 + v[b] = (v[b] ^ v[c]).rotate_right(12); + // a <- a + b + m_{2i+1} + v[a] = v[a].wrapping_add(v[b]).wrapping_add(my); + // d <- (d ^ a) >>> 8 + v[d] = (v[d] ^ v[a]).rotate_right(8); + // c <- c + d + v[c] = v[c].wrapping_add(v[d]); + // b <- (b ^ c) >>> 7 + v[b] = (v[b] ^ v[c]).rotate_right(7); +} + +/// BLAKE3 spec s. 2.2: "A round consists of the following 8 operations ... That is, a round +/// applies a G function to each column of the 4 x 4 state in parallel, and then to each of the +/// diagonals in parallel." G_i takes message words m_{2i} and m_{2i+1}. +#[inline] +fn round(v: &mut [u32; 16], m: &[u32; 16]) { + // G0(v0, v4, v8, v12) G1(v1, v5, v9, v13) G2(v2, v6, v10, v14) G3(v3, v7, v11, v15) + g(v, 0, 4, 8, 12, m[0], m[1]); + g(v, 1, 5, 9, 13, m[2], m[3]); + g(v, 2, 6, 10, 14, m[4], m[5]); + g(v, 3, 7, 11, 15, m[6], m[7]); + // G4(v0, v5, v10, v15) G5(v1, v6, v11, v12) G6(v2, v7, v8, v13) G7(v3, v4, v9, v14) + g(v, 0, 5, 10, 15, m[8], m[9]); + g(v, 1, 6, 11, 12, m[10], m[11]); + g(v, 2, 7, 8, 13, m[12], m[13]); + g(v, 3, 4, 9, 14, m[14], m[15]); +} + +/// BLAKE3 spec s. 2.2: "After each round (except the last one where it would be useless), the +/// message words are permuted according to Table 2." +#[inline] +fn permute(m: &mut [u32; 16]) { + let mut permuted = [0u32; 16]; + for i in 0..16 { + permuted[i] = m[MSG_PERMUTATION[i]]; + } + *m = permuted; +} + +/// The BLAKE3 compression function of spec s. 2.2. +/// +/// Inputs, in the spec's words: "The input chaining value, h0 ... h7 (256 bits)", "The message +/// block, m0 ... m15 (512 bits)", "A 64-bit counter, t = t0, t1, with t0 the lower order word and +/// t1 the higher order word", "The number of input bytes in the block, b (32 bits)", and "A set +/// of domain separation bit flags, d (32 bits)". +/// +/// Returns the full 16-word output h'0 ... h'15. "The output of the compression function is most +/// often truncated to produce 256-bit chaining values" -- callers that want a chaining value take +/// the first 8 words; the extendable output of s. 2.6 uses all 16. +/// +/// The working state and the permuted message copy are ordinary stack locals and are not +/// zeroized; the persistent chaining values and buffers live in `Secret`s in the callers. +pub(crate) fn compress(h: &[u32; 8], m: &[u32; 16], t: u64, b: u32, d: u32) -> [u32; 16] { + // t = t0, t1: "t0 the lower order word and t1 the higher order word". + let t0 = t as u32; + let t1 = (t >> 32) as u32; + + // "The compression function initializes its 16-word internal state v0 ... v15 as follows:" + // v0 v1 v2 v3 h0 h1 h2 h3 + // v4 v5 v6 v7 <- h4 h5 h6 h7 + // v8 v9 v10 v11 IV0 IV1 IV2 IV3 + // v12 v13 v14 v15 t0 t1 b d + #[rustfmt::skip] + let mut v: [u32; 16] = [ + h[0], h[1], h[2], h[3], + h[4], h[5], h[6], h[7], + IV[0], IV[1], IV[2], IV[3], + t0, t1, b, d, + ]; + + // "The compression function applies a 7-round keyed permutation v' = E(m, v) to the state + // v0 ... v15, keyed by the message m0 ... m15." The message is permuted between rounds but + // not after the seventh ("except the last one where it would be useless"). + let mut m = *m; + for _ in 0..6 { + round(&mut v, &m); + permute(&mut m); + } + round(&mut v, &m); + + // "The output of the compression function h'0 ... h'15 is defined as:" + // h'i <- v'i ^ v'(i+8) for i = 0..7 + // h'(i+8) <- v'(i+8) ^ hi for i = 0..7 + // The left column must read v'(i+8) before the right column overwrites it. + for i in 0..8 { + v[i] ^= v[i + 8]; + v[i + 8] ^= h[i]; + } + v +} + +/// BLAKE3 spec s. 2.4: "Each block is parsed in little-endian order into message words +/// m0 ... m15". Also used for the 32-byte key of s. 2.3 ("k0 ... k7 are parsed in little-endian +/// order from the 256-bit key"). +#[inline] +pub(crate) fn words_from_le_bytes(bytes: &[u8], words: &mut [u32; N]) { + debug_assert_eq!(bytes.len(), 4 * N); + let (chunks, _remainder) = bytes.as_chunks::<4>(); + for (word, four_bytes) in words.iter_mut().zip(chunks) { + *word = u32::from_le_bytes(*four_bytes); + } +} diff --git a/crypto/blake3/src/lib.rs b/crypto/blake3/src/lib.rs new file mode 100644 index 000000000..e7c417e9d --- /dev/null +++ b/crypto/blake3/src/lib.rs @@ -0,0 +1,166 @@ +//! Implements the BLAKE3 cryptographic hash function -- the `hash`, `keyed_hash` and `derive_key` +//! modes, with extendable output -- as per the BLAKE3 specification (J. O'Connor, J.-P. Aumasson, +//! S. Neves, Z. Wilcox-O'Hearn, "BLAKE3: one function, fast everywhere", version 20211102173700). +//! +//! * [`BLAKE3`]: the `hash` mode ([`BLAKE3::new`]) and the `derive_key` mode +//! ([`BLAKE3::new_derive_key`]), as a [`Hash`] and a [`XOF`]. +//! * [`BLAKE3KeyedHashKey`]: the `keyed_hash` key, which hands out [`BLAKE3KeyedHash`] workers, a +//! [`MAC`] with extended keyed output via [`BLAKE3KeyedHash::into_squeezer`]. +//! +//! # Usage Examples +//! ``` +//! use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; +//! use bouncycastle_blake3::BLAKE3; +//! +//! // One-shot: the official test vector for the empty input begins af1349b9. +//! assert_eq!(BLAKE3::new().hash(b"")[..4], [0xaf, 0x13, 0x49, 0xb9]); +//! +//! // Streaming, then reading any amount of output; the first 32 bytes are the hash. +//! let mut blake3 = BLAKE3::new(); +//! blake3.do_update(b"ab"); +//! blake3.do_update(b"c"); +//! let mut squeezer = blake3.into_squeezer(); +//! let long = [squeezer.do_output(40), squeezer.do_output(60)].concat(); +//! assert_eq!(long, BLAKE3::new().xof(b"abc", 100)); +//! assert_eq!(long[..32], BLAKE3::new().hash(b"abc")); +//! +//! // derive_key: a hardcoded context string, then the key material as input. +//! const CONTEXT: &[u8] = b"example.com 2019-12-25 16:18:03 session tokens v1"; +//! let derived: Vec = BLAKE3::new_derive_key(CONTEXT).xof(&[0x0bu8; 32], 64); +//! ``` +//! +//! The `keyed_hash` mode takes a 32-byte key tagged [`KeyType::MACKey`]: +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{ +//! FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, +//! }; +//! use bouncycastle_core::traits::{MAC, XOFSqueezer}; +//! use bouncycastle_blake3::BLAKE3KeyedHashKey; +//! +//! let material = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); +//! let key = BLAKE3KeyedHashKey::from_key_material(&material).unwrap(); +//! +//! let tag: Vec = key.mac(b"message"); +//! assert!(key.verify(b"message", &tag)); // constant-time comparison +//! +//! // Streaming, then extended keyed output: its first 32 bytes are the tag. +//! let mut mac = key.create_generator(); +//! mac.do_update(b"mess"); +//! mac.do_update(b"age"); +//! assert_eq!(mac.into_squeezer().do_output(64)[..32], tag); +//! ``` +//! +//! # Memory Usage +//! +//! No heap memory is used except by the `Vec`-returning convenience methods. +//! +//! | Object | Size (bytes) | +//! |-------------------------------------------|--------------| +//! | [`BLAKE3`] | 1880 | +//! | [`BLAKE3KeyedHashKey`] | 32 | +//! | [`BLAKE3KeyedHash`] | 1880 | +//! | [`BLAKE3Squeezer`] | 192 | +//! | [`BLAKE3KeyedSqueezer`] | 192 | +//! | [`SUSPENDED_BLAKE3_STATE_LEN`] | 1874 | +//! | [`SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN`] | 1838 | +//! | [`SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN`] | 180 | +//! +//! 1728 bytes of a hasher are the chaining-value stack, sized for the full 2^64-byte input range +//! (spec s. 5.4). +//! +//! # 🚨 Security Considerations 🚨 +//! +//! * BLAKE3 targets 128-bit security for all its goals (s. 4.1), so every type here reports +//! [`SecurityStrength::_128bit`]; the 256-bit key and longer outputs do not raise it. +//! * Shorter outputs are prefixes of longer ones (s. 2.6): the output length is not bound into the +//! result. +//! * `keyed_hash` is a MAC and PRF by design (s. 6.1); its key must be 32 uniformly random bytes. +//! The [`MAC`] trait will not truncate a tag below [`MIN_MAC_OUTPUT_LEN`] bytes; +//! [`BLAKE3KeyedHash::into_squeezer`] has no floor. +//! * The `derive_key` context string must be hardcoded, globally unique and application-specific, +//! never variable data (s. 6.2); see [`BLAKE3::new_derive_key`]. +//! * BLAKE3 is byte-oriented: a partial final byte is refused. +//! * State and buffers are zeroized on drop; there are no data-dependent branches or table +//! lookups. +//! * Suspended states are sensitive: a `derive_key` state holds up to 64 bytes of key material +//! verbatim, and a `keyed_hash` state can finish some messages without the key (see +//! [`BLAKE3KeyedHash`]'s [`SuspendableKeyed`] impl). Protect either like the key. +//! +//! # Suspending and resuming execution +//! +//! [`BLAKE3`] and [`BLAKE3Squeezer`] implement [`Suspendable`]; [`BLAKE3KeyedHash`] implements +//! [`SuspendableKeyed`], which never writes the key into the state and takes the key object on +//! resume. [`BLAKE3KeyedSqueezer`] cannot be suspended, because its state can contain the key +//! words. +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +//! use bouncycastle_core::traits::{Hash, MAC, Suspendable, SuspendableKeyed}; +//! use bouncycastle_blake3::{BLAKE3, BLAKE3KeyedHash, BLAKE3KeyedHashKey}; +//! +//! let mut blake3 = BLAKE3::new(); +//! blake3.do_update(b"part one, "); +//! let mut resumed = BLAKE3::from_suspended(blake3.suspend()).unwrap(); +//! resumed.do_update(b"part two"); +//! assert_eq!(resumed.do_final(), BLAKE3::new().hash(b"part one, part two")); +//! +//! let material = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); +//! let key = BLAKE3KeyedHashKey::from_key_material(&material).unwrap(); +//! let mut mac = key.create_generator(); +//! mac.do_update(b"part one, "); +//! let mut resumed = BLAKE3KeyedHash::from_suspended(mac.suspend(), &key).unwrap(); +//! resumed.do_update(b"part two"); +//! assert_eq!(resumed.do_final(), key.mac(b"part one, part two")); +//! ``` + +// todo #![no_std] +// waiting for the no_std refactor that removes the `-> Vec` from core::traits::Hash + +#![forbid(unsafe_code)] +#![forbid(missing_docs)] + +mod blake3; +mod compress; + +pub use self::blake3::{ + BLAKE3, BLAKE3KeyedHash, BLAKE3KeyedHashKey, BLAKE3KeyedSqueezer, BLAKE3Squeezer, + MIN_MAC_OUTPUT_LEN, SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN, SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN, + SUSPENDED_BLAKE3_STATE_LEN, +}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, HashAlgParams}; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::key_material::KeyType; +#[allow(unused_imports)] +use bouncycastle_core::traits::{Hash, MAC, Suspendable, SuspendableKeyed, XOF}; + +/// Algorithm name string for BLAKE3 (`hash` and `derive_key` modes), as used by the factories and +/// CLI. +pub const BLAKE3_NAME: &str = "BLAKE3"; + +/// Algorithm name string for BLAKE3 in `keyed_hash` mode, as used by the factories and CLI. +pub const BLAKE3_KEYED_HASH_NAME: &str = "BLAKE3-KeyedHash"; + +/// BLAKE3 spec s. 4.1: "BLAKE3 targets 128-bit security for all of its security goals." +impl Algorithm for BLAKE3 { + const ALG_NAME: &'static str = BLAKE3_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// BLAKE3 spec s. 4.1: "BLAKE3 targets 128-bit security for all of its security goals." The +/// 256-bit key is "an extra defense layer", not a higher target. +impl Algorithm for BLAKE3KeyedHash { + const ALG_NAME: &'static str = BLAKE3_KEYED_HASH_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// BLAKE3 spec s. 2.1: the default output is 32 bytes ("The chaining value of the root node, +/// encoded as 32 bytes ... is the default-length BLAKE3 hash"); s. 2.2: 64-byte message blocks. +/// BLAKE3 has no assigned OID. +impl HashAlgParams for BLAKE3 { + const OUTPUT_LEN: usize = 32; + const BLOCK_LEN: usize = 64; +} diff --git a/crypto/blake3/tests/blake3_tests.rs b/crypto/blake3/tests/blake3_tests.rs new file mode 100644 index 000000000..e3d107803 --- /dev/null +++ b/crypto/blake3/tests/blake3_tests.rs @@ -0,0 +1,566 @@ +//! Tests for [`BLAKE3`] (`hash` and `derive_key` modes) and [`BLAKE3Squeezer`]. +//! +//! Vector sources: the official BLAKE3 test vectors (see `vectors/mod.rs`). bc-test-data has no +//! BLAKE3 data, and Wycheproof has no BLAKE vectors (C2SP/wycheproof testvectors_v1 has no blake* +//! file), so the "against bc-test-data and wycheproof" requirement is met vacuously. + +mod vectors; + +#[cfg(test)] +mod blake3_tests { + use super::vectors::{CASES, CONTEXT, OUTPUT_LEN, input}; + use bouncycastle_blake3::*; + use bouncycastle_core::errors::{HashError, SuspendableError}; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::traits::{ + Algorithm, Hash, HashAlgParams, Suspendable, XOF, XOFSqueezer, + }; + use bouncycastle_core_test_framework::hash::TestFrameworkHash; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; + use bouncycastle_core_test_framework::xof::TestFrameworkXOF; + use bouncycastle_hex as hex; + + fn h(s: &str) -> Vec { + hex::decode(s).unwrap() + } + + /* ----------------------------------- official vectors ----------------------------------- */ + + /// Every `hash` case: the 32-byte default output, the full 131 bytes through every XOF entry + /// point, and split reads across the 64-byte output block boundary. + #[test] + fn official_vectors_hash() { + for case in CASES { + let msg = input(case.input_len); + let expected = h(case.hash); + assert_eq!(expected.len(), OUTPUT_LEN); + let len = case.input_len; + + assert_eq!(BLAKE3::new().hash(&msg), expected[..32], "hash / len {len}"); + assert_eq!(BLAKE3::new().xof(&msg, OUTPUT_LEN), expected, "xof / len {len}"); + + let mut b = BLAKE3::new(); + b.do_update(&msg); + assert_eq!(b.into_squeezer().do_output(OUTPUT_LEN), expected, "squeeze / len {len}"); + + // Split reads: inside block 0, exactly at the block boundary, and across it. + for split in [1usize, 31, 63, 64, 65, 128, 130] { + let mut b = BLAKE3::new(); + b.do_update(&msg); + let mut s = b.into_squeezer(); + let first = s.do_output(split); + let second = s.do_output(OUTPUT_LEN - split); + assert_eq!([first, second].concat(), expected, "split {split} / len {len}"); + } + } + } + + /// Every `derive_key` case, via [`BLAKE3::new_derive_key`], at 32 and 131 bytes. + #[test] + fn official_vectors_derive_key() { + for case in CASES { + let msg = input(case.input_len); + let expected = h(case.derive_key); + let len = case.input_len; + + assert_eq!(BLAKE3::new_derive_key(CONTEXT).hash(&msg), expected[..32], "len {len}"); + assert_eq!( + BLAKE3::new_derive_key(CONTEXT).xof(&msg, OUTPUT_LEN), + expected, + "len {len}" + ); + } + } + + /// The derive_key context goes through the full tree, so a context longer than one chunk must + /// differ from one that is merely a prefix of it, and the two modes must differ from `hash`. + #[test] + fn derive_key_context_is_bound() { + let long_context = input(2049); + let a = BLAKE3::new_derive_key(&long_context).hash(b"km"); + let b = BLAKE3::new_derive_key(&long_context[..2048]).hash(b"km"); + assert_ne!(a, b); + assert_ne!(BLAKE3::new_derive_key(b"").hash(b"km"), BLAKE3::new().hash(b"km")); + } + + /* --------------------------------- core-test-framework ---------------------------------- */ + + #[test] + fn core_test_framework_hash() { + let tf = TestFrameworkHash { enable_partial_byte_tests: false }; + for case in CASES.iter().filter(|c| c.input_len <= 8193) { + let msg = input(case.input_len); + tf.test_hash::(&msg, &h(case.hash)[..32]); + } + for len in [0usize, 1, 64, 1024, 1025, 4097] { + tf.test_hash_output_buffers(BLAKE3::new, &input(len)); + tf.test_hash_output_buffers(|| BLAKE3::new_derive_key(CONTEXT), &input(len)); + } + } + + #[test] + fn core_test_framework_xof() { + let tf = TestFrameworkXOF { + enable_partial_byte_tests: false, + do_final_binds_output_length: false, + }; + for case in CASES.iter().filter(|c| c.input_len <= 8193) { + let msg = input(case.input_len); + tf.test_xof(BLAKE3::new, &msg, &h(case.hash)); + tf.test_xof(|| BLAKE3::new_derive_key(CONTEXT), &msg, &h(case.derive_key)); + } + } + + /* ------------------------------------- behaviour ---------------------------------------- */ + + /// Streaming in odd piece sizes equals the one-shot, at every vector length around the chunk + /// and tree boundaries. + #[test] + fn streaming_piece_sizes() { + for case in CASES.iter().filter(|c| c.input_len >= 1023 && c.input_len <= 8193) { + let msg = input(case.input_len); + let expected = h(case.hash); + for piece in [1usize, 63, 64, 65, 1023, 1024, 1025] { + let mut b = BLAKE3::new(); + for chunk in msg.chunks(piece) { + b.do_update(chunk); + } + assert_eq!( + b.into_squeezer().do_output(OUTPUT_LEN), + expected, + "len {} piece {piece}", + case.input_len + ); + } + } + } + + /// Empty updates are no-ops, wherever they fall. + #[test] + fn empty_updates() { + let msg = input(1025); + let mut b = BLAKE3::new(); + b.do_update(&[]); + b.do_update(&msg[..1024]); + b.do_update(&[]); + b.do_update(&msg[1024..]); + b.do_update(&[]); + assert_eq!(b.do_final(), BLAKE3::new().hash(&msg)); + } + + /// A clone continues independently, including from a state with a non-empty CV stack. + #[test] + fn clone_forks() { + let msg = input(3073); + let mut a = BLAKE3::new(); + a.do_update(&msg[..2500]); + let mut b = a.clone(); + a.do_update(&msg[2500..]); + b.do_update(&msg[2500..]); + let expected = BLAKE3::new().hash(&msg); + assert_eq!(a.clone().do_final(), expected); + assert_eq!(b.do_final(), expected); + a.do_update(b"x"); + assert_ne!(a.do_final(), expected); + + // A squeezer clone continues the same stream. + let mut s = BLAKE3::new().into_squeezer(); + let _ = s.do_output(70); + let mut t = s.clone(); + assert_eq!(s.do_output(100), t.do_output(100)); + } + + /// The partial-bit policy: 0 is the whole-byte call, 1..=7 is InvalidInput (byte-oriented), + /// and 8+ is InvalidLength. An `_out` call zeroizes the buffer first, even when it is + /// rejected. + #[test] + fn partial_bits_policy() { + let expected = BLAKE3::new().hash(b"abc"); + let mk = || { + let mut b = BLAKE3::new(); + b.do_update(b"abc"); + b + }; + + for partial_byte in [0x00u8, 0xFF] { + assert_eq!(mk().do_final_partial_bits(partial_byte, 0).unwrap(), expected); + let mut out = [0xAAu8; 40]; + assert_eq!(mk().do_final_partial_bits_out(partial_byte, 0, &mut out).unwrap(), 32); + assert_eq!(out[..32], expected); + assert_eq!(out[32..], [0u8; 8]); + let mut s = mk().into_squeezer_partial_bits(partial_byte, 0).unwrap(); + assert_eq!(s.do_output(32), expected); + } + + for num_bits in 1..=7usize { + assert!(matches!( + mk().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidInput(_)) + )); + let mut out = [0xA5u8; 32]; + assert!(matches!( + mk().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidInput(_)) + )); + assert_eq!(out, [0u8; 32], "a rejected call must leave the buffer zeroized"); + assert!(matches!( + mk().into_squeezer_partial_bits(0x80, num_bits), + Err(HashError::InvalidInput(_)) + )); + } + + // The frameworks' partial-bit tests are disabled for BLAKE3, so their > 7 values are pinned + // here for every entry point. + for num_bits in [8usize, 9, 15, 16, 64, usize::MAX] { + assert!(matches!( + mk().do_final_partial_bits(0, num_bits), + Err(HashError::InvalidLength(_)) + )); + let mut out = [0xA5u8; 32]; + assert!(matches!( + mk().do_final_partial_bits_out(0, num_bits, &mut out), + Err(HashError::InvalidLength(_)) + )); + assert_eq!(out, [0u8; 32], "a rejected call must leave the buffer zeroized"); + assert!(matches!( + mk().into_squeezer_partial_bits(0, num_bits), + Err(HashError::InvalidLength(_)) + )); + } + } + + /// `Hash::do_final_out` writes at most 32 bytes and zeroizes the rest; a zero-length buffer + /// gets nothing. + #[test] + fn hash_do_final_out_lengths() { + let expected = BLAKE3::new().hash(b"abc"); + let mut out = [0xAAu8; 100]; + assert_eq!(BLAKE3::new().hash_out(b"abc", &mut out), 32); + assert_eq!(out[..32], expected); + assert!(out[32..].iter().all(|&b| b == 0)); + assert_eq!(BLAKE3::new().hash_out(b"abc", &mut []), 0); + let mut out = [0u8; 5]; + assert_eq!(BLAKE3::new().hash_out(b"abc", &mut out), 5); + assert_eq!(out, expected[..5]); + } + + #[test] + fn constants_and_params() { + assert_eq!(BLAKE3_NAME, "BLAKE3"); + assert_eq!(BLAKE3::ALG_NAME, BLAKE3_NAME); + assert_eq!(BLAKE3::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(BLAKE3::new().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(BLAKE3::OUTPUT_LEN, 32); + assert_eq!(BLAKE3::BLOCK_LEN, 64); + assert_eq!(BLAKE3::new().output_len(), 32); + assert_eq!(BLAKE3::new().block_bitlen(), 512); + assert_eq!(BLAKE3::default().hash(b"abc"), BLAKE3::new().hash(b"abc")); + assert_eq!(SUSPENDED_BLAKE3_STATE_LEN, 1874); + assert_eq!(SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN, 180); + } + + /// Prints, and pins, the `size_of` figures quoted in the crate docs' Memory Usage table. + #[test] + fn print_sizes() { + assert_eq!(size_of::(), 1880); + assert_eq!(size_of::(), 1880); + assert_eq!(size_of::(), 192); + assert_eq!(size_of::(), 192); + println!("size_of::() = {}", size_of::()); + println!("size_of::() = {}", size_of::()); + println!("size_of::() = {}", size_of::()); + println!("size_of::() = {}", size_of::()); + } + + /* ------------------------------------ suspend/resume ------------------------------------ */ + + /// Suspends after `prefix` bytes of `msg`, resumes, absorbs the rest, and compares to the + /// vector. + fn round_trip(make: impl Fn() -> BLAKE3, msg: &[u8], prefix: usize, expected: &[u8]) { + let mut b = make(); + b.do_update(&msg[..prefix]); + let mut resumed = BLAKE3::from_suspended(b.suspend()).unwrap(); + resumed.do_update(&msg[prefix..]); + assert_eq!(resumed.into_squeezer().do_output(OUTPUT_LEN), expected, "prefix {prefix}"); + } + + #[test] + fn suspend_round_trip() { + // 8193 bytes: past several chunk and tree boundaries, so the CV stack is non-empty. + let case = CASES.iter().find(|c| c.input_len == 8193).unwrap(); + let msg = input(case.input_len); + for prefix in [0usize, 1, 63, 64, 65, 1023, 1024, 1025, 2048, 3000, 4096, 7000, 8193] { + round_trip(BLAKE3::new, &msg, prefix, &h(case.hash)); + round_trip(|| BLAKE3::new_derive_key(CONTEXT), &msg, prefix, &h(case.derive_key)); + } + } + + #[test] + fn core_test_framework_suspendable() { + let tf = TestFrameworkSuspendableState::new(); + let msg = input(5000); + for len in [0usize, 100, 1024, 1025, 5000] { + let mut b = BLAKE3::new(); + b.do_update(&msg[..len]); + tf.test(&b); + let mut d = BLAKE3::new_derive_key(CONTEXT); + d.do_update(&msg[..len]); + tf.test(&d); + } + + let mut b = BLAKE3::new(); + b.do_update(&msg); + let mut s = b.into_squeezer(); + tf.test(&s); + let _ = s.do_output(70); + tf.test(&s); + } + + /// A suspended squeezer resumes mid-stream, including exactly at and past a block boundary. + #[test] + fn squeezer_suspend_round_trip() { + for case in CASES.iter().filter(|c| [0usize, 64, 1025, 4097].contains(&c.input_len)) { + let msg = input(case.input_len); + let expected = h(case.hash); + for split in [0usize, 1, 63, 64, 65, 128, 131] { + let mut b = BLAKE3::new(); + b.do_update(&msg); + let mut s = b.into_squeezer(); + let first = s.do_output(split); + let mut resumed = BLAKE3Squeezer::from_suspended(s.suspend()).unwrap(); + let second = resumed.do_output(OUTPUT_LEN - split); + assert_eq!([first, second].concat(), expected, "split {split}"); + } + + let mut d = BLAKE3::new_derive_key(CONTEXT); + d.do_update(&msg); + let mut s = d.into_squeezer(); + let first = s.do_output(70); + let mut resumed = BLAKE3Squeezer::from_suspended(s.suspend()).unwrap(); + assert_eq!([first, resumed.do_output(61)].concat(), h(case.derive_key)); + } + } + + // Offsets into a BLAKE3 state, after the 3-byte version header (see + // SUSPENDED_BLAKE3_STATE_LEN). + const FLAGS: usize = 3; + const KEY_WORDS: usize = 3 + 4; + const CHUNK_CV: usize = 3 + 36; + const CHUNK_COUNTER: usize = 3 + 68; + const BLOCK: usize = 3 + 76; + const BLOCK_LEN_AT: usize = 3 + 140; + const BLOCKS_COMPRESSED: usize = 3 + 141; + const CV_STACK_LEN: usize = 3 + 142; + const CV_STACK: usize = 3 + 143; + + fn state_after(len: usize) -> [u8; SUSPENDED_BLAKE3_STATE_LEN] { + let mut b = BLAKE3::new(); + b.do_update(&input(len)); + b.suspend() + } + + fn rejected(state: [u8; SUSPENDED_BLAKE3_STATE_LEN]) -> bool { + matches!(BLAKE3::from_suspended(state), Err(SuspendableError::InvalidData)) + } + + /// Pins the serialized layout: the hash-mode key words are IV, the chunk CV is zero before the + /// first compression, stale bytes and stale stack entries are not written. + #[test] + fn suspend_layout() { + let iv0 = 0x6a09e667u32.to_le_bytes(); + let s = state_after(0); + assert_eq!(s[FLAGS..FLAGS + 4], [0u8; 4]); + assert_eq!(s[KEY_WORDS..KEY_WORDS + 4], iv0); + assert_eq!(s[CHUNK_CV..CHUNK_CV + 32], [0u8; 32], "chunk CV omitted while it is the key"); + assert!(s[CHUNK_COUNTER..].iter().all(|&b| b == 0)); + + // 1100 bytes: one chunk on the stack, 76 bytes into chunk 1: one block compressed. + let s = state_after(1100); + assert_eq!(s[CHUNK_COUNTER..CHUNK_COUNTER + 8], 1u64.to_le_bytes()); + assert_eq!(s[BLOCK_LEN_AT], 12); + assert_eq!(s[BLOCKS_COMPRESSED], 1); + assert_eq!(s[CV_STACK_LEN], 1); + assert_ne!(s[CHUNK_CV..CHUNK_CV + 32], [0u8; 32]); + assert_eq!(s[BLOCK..BLOCK + 12], input(1100)[1088..1100]); + assert!(s[BLOCK + 12..BLOCK + 64].iter().all(|&b| b == 0), "no stale block bytes"); + assert_ne!(s[CV_STACK..CV_STACK + 32], [0u8; 32]); + assert!(s[CV_STACK + 32..].iter().all(|&b| b == 0), "no stale stack entries"); + + // derive_key mode records its flag and context key. + let mut d = BLAKE3::new_derive_key(CONTEXT); + d.do_update(b"x"); + let s = d.suspend(); + assert_eq!(s[FLAGS..FLAGS + 4], 64u32.to_le_bytes()); + assert_ne!(s[KEY_WORDS..KEY_WORDS + 4], iv0); + } + + /// Each InvalidData branch of `from_suspended`, with its boundary accepted. + #[test] + fn suspend_validation() { + let good = state_after(3000); // chunk_counter 2 (0b10), one CV on the stack + assert!(BLAKE3::from_suspended(good).is_ok()); + + // flags: only 0 (with IV) and DERIVE_KEY_MATERIAL. + for flags in [1u32, 16, 32, 64 | 1, 128] { + let mut s = good; + s[FLAGS..FLAGS + 4].copy_from_slice(&flags.to_le_bytes()); + assert!(rejected(s), "flags {flags}"); + } + let mut s = good; + s[FLAGS..FLAGS + 4].copy_from_slice(&64u32.to_le_bytes()); + assert!(BLAKE3::from_suspended(s).is_ok(), "DERIVE_KEY_MATERIAL with any key words"); + // hash mode with key words other than IV. + let mut s = good; + s[KEY_WORDS] ^= 1; + assert!(rejected(s), "hash mode requires IV key words"); + + // block_len <= 64 + let mut s = good; + s[BLOCK_LEN_AT] = 64; + assert!(BLAKE3::from_suspended(s).is_ok()); + s[BLOCK_LEN_AT] = 65; + assert!(rejected(s), "block_len 65"); + + // blocks_compressed <= 15 + let mut s = good; + s[BLOCKS_COMPRESSED] = 15; + assert!(BLAKE3::from_suspended(s).is_ok()); + s[BLOCKS_COMPRESSED] = 16; + assert!(rejected(s), "blocks_compressed 16"); + + // cv_stack_len must equal chunk_counter.count_ones() + let mut s = good; + s[CV_STACK_LEN] = 2; + assert!(rejected(s), "stack longer than the counter's 1-bits"); + s[CV_STACK_LEN] = 0; + assert!(rejected(s), "stack shorter than the counter's 1-bits"); + + // cv_stack_len <= 54, and chunk_counter < 2^54. + let mut s = good; + s[CHUNK_COUNTER..CHUNK_COUNTER + 8].copy_from_slice(&((1u64 << 54) - 1).to_le_bytes()); + s[CV_STACK_LEN] = 54; + assert!(BLAKE3::from_suspended(s).is_ok(), "the largest valid counter"); + s[CHUNK_COUNTER..CHUNK_COUNTER + 8].copy_from_slice(&((1u64 << 55) - 1).to_le_bytes()); + s[CV_STACK_LEN] = 55; + assert!(rejected(s), "cv_stack_len 55"); + // 54 one-bits, but not the low 54: the next chunk would push a 55th CV. + s[CHUNK_COUNTER..CHUNK_COUNTER + 8] + .copy_from_slice(&(((1u64 << 54) - 1) << 1).to_le_bytes()); + s[CV_STACK_LEN] = 54; + assert!(rejected(s), "chunk_counter >= 2^54"); + s[CHUNK_COUNTER..CHUNK_COUNTER + 8].copy_from_slice(&(1u64 << 54).to_le_bytes()); + s[CV_STACK_LEN] = 1; + assert!(rejected(s), "chunk_counter == 2^54"); + + // A compressed block is always followed by at least one buffered byte. + let mut s = good; + s[BLOCK_LEN_AT] = 1; + assert!(BLAKE3::from_suspended(s).is_ok()); + s[BLOCK_LEN_AT] = 0; + assert!(rejected(s), "blocks_compressed > 0 with an empty block"); + s[BLOCKS_COMPRESSED] = 0; + assert!(BLAKE3::from_suspended(s).is_ok(), "an empty chunk with nothing compressed"); + } + + /// While no block of the chunk is compressed, the chunk CV comes from the key words and the + /// serialized bytes are ignored. + #[test] + fn suspend_ignores_chunk_cv_before_first_compression() { + let msg = input(1030); + let mut b = BLAKE3::new(); + b.do_update(&msg[..1025]); // chunk 1 holds 1 byte, nothing compressed + let mut s = b.suspend(); + assert_eq!(s[BLOCKS_COMPRESSED], 0); + s[CHUNK_CV..CHUNK_CV + 32].fill(0x5A); + let mut resumed = BLAKE3::from_suspended(s).unwrap(); + resumed.do_update(&msg[1025..]); + assert_eq!(resumed.do_final(), BLAKE3::new().hash(&msg)); + } + + /// The largest valid stack resumes and keeps absorbing: completing the current chunk merges + /// all 54 entries without over- or underflowing. + #[test] + fn suspend_full_stack_resumes() { + let mut s = state_after(0); + s[CHUNK_COUNTER..CHUNK_COUNTER + 8].copy_from_slice(&((1u64 << 54) - 1).to_le_bytes()); + s[CV_STACK_LEN] = 54; + let mut b = BLAKE3::from_suspended(s).unwrap(); + b.do_update(&input(2048)); + assert_eq!(b.do_final().len(), 32); + } + + /// The 64-bit counter enters the compression as two words, t0 and t1 (spec s. 2.2). No real + /// input reaches 2^32 chunks or output blocks, so the high word is exercised by resuming a + /// squeezer at output block 2^32: with t1 dropped, that block would repeat block 0, and with + /// t0 dropped, block 2^32 + 1 would repeat block 1. + #[test] + fn squeezer_counter_high_word() { + let mut b = BLAKE3::new(); + b.do_update(b"abc"); + let s = b.into_squeezer(); + let blocks_0_1 = s.clone().do_output(128); + let mut state = s.suspend(); + // output_block_counter = 2^32, buffer position 64 (the next read computes a new block). + state[3 + 104..3 + 112].copy_from_slice(&(1u64 << 32).to_le_bytes()); + state[3 + 176] = 64; + let mut far = BLAKE3Squeezer::from_suspended(state).unwrap(); + let far_blocks = far.do_output(128); + assert_ne!(far_blocks[..64], blocks_0_1[..64], "block 2^32 must differ from block 0"); + assert_ne!(far_blocks[64..], blocks_0_1[64..], "block 2^32 + 1 must differ from block 1"); + assert_ne!(far_blocks[..64], far_blocks[64..]); + } + + #[test] + fn squeezer_suspend_validation() { + let mut b = BLAKE3::new(); + b.do_update(b"abc"); + let mut s = b.into_squeezer(); + let _ = s.do_output(10); + let state = s.suspend(); + assert_eq!(state[3 + 176], 10, "buffer position"); + + let mut bad = state; + bad[3 + 176] = 64; + assert!(BLAKE3Squeezer::from_suspended(bad).is_ok()); + bad[3 + 176] = 65; + assert!(matches!(BLAKE3Squeezer::from_suspended(bad), Err(SuspendableError::InvalidData))); + + // Root block_len <= 64 ("abc": 3). + assert_eq!(state[3 + 96..3 + 100], 3u32.to_le_bytes()); + let mut bad = state; + bad[3 + 96..3 + 100].copy_from_slice(&64u32.to_le_bytes()); + assert!(BLAKE3Squeezer::from_suspended(bad).is_ok()); + bad[3 + 96..3 + 100].copy_from_slice(&65u32.to_le_bytes()); + assert!(matches!(BLAKE3Squeezer::from_suspended(bad), Err(SuspendableError::InvalidData))); + + // output_block_counter == 0 is only valid at position 64 (nothing computed yet). + assert_eq!(state[3 + 104..3 + 112], 1u64.to_le_bytes()); + let mut bad = state; + bad[3 + 104..3 + 112].copy_from_slice(&0u64.to_le_bytes()); + assert!(matches!(BLAKE3Squeezer::from_suspended(bad), Err(SuspendableError::InvalidData))); + bad[3 + 176] = 63; + assert!(matches!(BLAKE3Squeezer::from_suspended(bad), Err(SuspendableError::InvalidData))); + bad[3 + 176] = 64; + assert!(BLAKE3Squeezer::from_suspended(bad).is_ok(), "a fresh squeezer"); + let fresh = BLAKE3::new().into_squeezer().suspend(); + assert_eq!(fresh[3 + 104..3 + 112], 0u64.to_le_bytes()); + assert_eq!(fresh[3 + 176], 64); + assert!(BLAKE3Squeezer::from_suspended(fresh).is_ok()); + + // Root flags: a node-type part plus mode 0 or DERIVE_KEY_MATERIAL; never ROOT or + // KEYED_HASH. + // "abc" is a one-block chunk root: CHUNK_START | CHUNK_END. + assert_eq!(state[3 + 100..3 + 104], 3u32.to_le_bytes()); + let with_flags = |flags: u32| { + let mut st = state; + st[3 + 100..3 + 104].copy_from_slice(&flags.to_le_bytes()); + BLAKE3Squeezer::from_suspended(st) + }; + for ok in [0u32, 1, 2, 3, 4, 64, 64 | 3, 64 | 4] { + assert!(with_flags(ok).is_ok(), "flags {ok}"); + } + for bad in [8u32, 16, 16 | 3, 32, 64 | 16, 64 | 8, 128, 1 << 31] { + assert!(matches!(with_flags(bad), Err(SuspendableError::InvalidData)), "flags {bad}"); + } + } +} diff --git a/crypto/blake3/tests/keyed_hash_tests.rs b/crypto/blake3/tests/keyed_hash_tests.rs new file mode 100644 index 000000000..813b55260 --- /dev/null +++ b/crypto/blake3/tests/keyed_hash_tests.rs @@ -0,0 +1,389 @@ +//! Tests for [`BLAKE3KeyedHashKey`], the [`BLAKE3KeyedHash`] worker it hands out (`keyed_hash` +//! mode) and [`BLAKE3KeyedSqueezer`]. +//! +//! Vector sources: the official BLAKE3 test vectors (see `vectors/mod.rs`). bc-test-data has no +//! BLAKE3 data, and Wycheproof has no BLAKE vectors (C2SP/wycheproof testvectors_v1 has no blake* +//! file), so the "against bc-test-data and wycheproof" requirement is met vacuously. + +mod vectors; + +#[cfg(test)] +mod keyed_hash_tests { + use super::vectors::{CASES, KEY, OUTPUT_LEN, input}; + use bouncycastle_blake3::*; + use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; + use bouncycastle_core::key_material::{ + KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, + }; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::symmetric_key::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; + use bouncycastle_core::traits::{Algorithm, Hash, MAC, SuspendableKeyed, XOFSqueezer}; + use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; + use bouncycastle_core_test_framework::mac::TestFrameworkMAC; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; + use bouncycastle_hex as hex; + + fn h(s: &str) -> Vec { + hex::decode(s).unwrap() + } + + fn material() -> KeyMaterial256 { + KeyMaterial256::from_bytes_as_type(KEY, KeyType::MACKey).unwrap() + } + + fn key() -> BLAKE3KeyedHashKey { + BLAKE3KeyedHashKey::from_key_material(&material()).unwrap() + } + + fn other_key() -> BLAKE3KeyedHashKey { + let m = KeyMaterial256::from_bytes_as_type(&[9u8; 32], KeyType::MACKey).unwrap(); + BLAKE3KeyedHashKey::from_key_material(&m).unwrap() + } + + /* ----------------------------------- official vectors ----------------------------------- */ + + /// Every `keyed_hash` case: the 32-byte tag through the MAC path, and the full 131 bytes (and + /// split reads) through `into_squeezer`. + #[test] + fn official_vectors_keyed_hash() { + let k = key(); + for case in CASES { + let msg = input(case.input_len); + let expected = h(case.keyed_hash); + let len = case.input_len; + + assert_eq!(k.mac(&msg), expected[..32], "len {len}"); + assert!(k.verify(&msg, &expected[..32])); + + let mut m = k.create_generator(); + m.do_update(&msg); + assert_eq!(m.into_squeezer().do_output(OUTPUT_LEN), expected, "len {len}"); + + for split in [1usize, 64, 65] { + let mut m = k.create_generator(); + m.do_update(&msg); + let mut s = m.into_squeezer(); + let first = s.do_output(split); + let mut second = vec![0xAAu8; OUTPUT_LEN - split]; + assert_eq!(s.do_output_out(&mut second), OUTPUT_LEN - split); + assert_eq!([first, second].concat(), expected, "split {split} / len {len}"); + } + } + } + + /// The default `do_output_final` of the keyed squeezer continues the stream. + #[test] + fn keyed_squeezer_do_output_final() { + let expected = h(CASES[3].keyed_hash); + let mut m = key().create_generator(); + m.do_update(&input(CASES[3].input_len)); + let mut s = m.into_squeezer(); + let first = s.do_output(10); + assert_eq!([first, s.clone().do_output_final(121)].concat(), expected); + let mut rest = vec![0u8; 121]; + assert_eq!(s.do_output_final_out(&mut rest), 121); + assert_eq!(rest, expected[10..]); + } + + /* --------------------------------- core-test-framework ---------------------------------- */ + + #[test] + fn core_test_framework_mac_key() { + let tf = TestFrameworkMAC::new(); + let k = key(); + for case in CASES.iter().filter(|c| c.input_len <= 4097) { + tf.test_mac_key(&k, &input(case.input_len), &h(case.keyed_hash)[..32]); + } + } + + /// Key type, the strength policy (including strength-before-length for a 64-byte key of no + /// strength) and `generate`. + #[test] + fn core_test_framework_from_any_key_material() { + TestFrameworkFromAnyKeyMaterial::new().test::(&material()); + } + + #[test] + fn core_test_framework_suspendable_keyed() { + let tf = TestFrameworkSuspendableKeyedState::new(); + let k = key(); + let msg = input(5000); + for len in [0usize, 1, 64, 65, 1024, 1025, 5000] { + let mut m = k.create_generator(); + m.do_update(&msg[..len]); + tf.test(&m, &k); + } + } + + /* ------------------------------------- MAC behaviour ------------------------------------ */ + + /// Output buffers: a short buffer truncates down to MIN_MAC_OUTPUT_LEN, below that is an + /// error, a long buffer gets 32 bytes and zeros. + #[test] + fn mac_output_buffers() { + assert_eq!(MIN_MAC_OUTPUT_LEN, 4); + let k = key(); + let tag = k.mac(b"abc"); + assert_eq!(k.create_generator().output_len(), 32); + + for len in [4usize, 5, 31, 32] { + let mut out = vec![0xAAu8; len]; + assert_eq!(k.mac_out(b"abc", &mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + + let mut m = k.create_generator(); + m.do_update(b"abc"); + let mut out = vec![0xAAu8; len]; + assert_eq!(m.do_final_out(&mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + } + + // Below the floor: an error, and the buffer still comes back zeroized. + for len in [0usize, 1, 3] { + let mut out = vec![0xA5u8; len]; + assert!(matches!(k.mac_out(b"abc", &mut out), Err(MACError::InvalidLength(_)))); + assert!(out.iter().all(|&b| b == 0), "key mac_out / len {len}"); + + let mut out = vec![0xA5u8; len]; + assert!(matches!( + k.create_generator().mac_out(b"abc", &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0), "worker mac_out / len {len}"); + + let mut out = vec![0xA5u8; len]; + let mut m = k.create_generator(); + m.do_update(b"abc"); + assert!(matches!(m.do_final_out(&mut out), Err(MACError::InvalidLength(_)))); + assert!(out.iter().all(|&b| b == 0), "do_final_out / len {len}"); + } + + let mut out = [0xAAu8; 50]; + assert_eq!(k.create_generator().mac_out(b"abc", &mut out).unwrap(), 32); + assert_eq!(out[..32], tag); + assert_eq!(out[32..], [0u8; 18]); + } + + /// Verification needs the exact, full-length tag. + #[test] + fn verify_exact_match() { + let k = key(); + let tag = k.mac(b"abc"); + assert!(k.verify(b"abc", &tag)); + assert!(!k.verify(b"abd", &tag)); + assert!(!k.verify(b"abc", &tag[..31]), "truncated tag"); + assert!(!k.verify(b"abc", &[&tag[..], &[0u8]].concat()), "extended tag"); + let mut flipped = tag.clone(); + flipped[31] ^= 1; + assert!(!k.verify(b"abc", &flipped)); + + let mut m = k.create_verifier(); + m.do_update(b"abc"); + assert!(m.clone().do_verify_final(&tag)); + assert!(!m.do_verify_final(&tag[..16])); + + // The worker's own one-shot. + assert!(k.create_verifier().verify(b"abc", &tag)); + assert!(!k.create_verifier().verify(b"abd", &tag)); + assert!(!k.create_verifier().verify(b"abc", &tag[..31])); + } + + /// The keyed mode differs from the hash mode, and from other keys. + #[test] + fn keyed_differs() { + let a = key().mac(b"abc"); + assert_ne!(a, other_key().mac(b"abc")); + assert_ne!(a, BLAKE3::new().hash(b"abc")); + } + + /// The BLAKE3-specific construction rule the shared suite cannot know: exactly 32 bytes + /// (s. 2.3), whatever the strength. A Zeroized (all-zero) key is a MAC key, though a weak one. + #[test] + fn key_length_rules() { + for len in [0usize, 1, 16, 31, 33, 64] { + let mut k = KeyMaterial512::from_bytes_as_type(&[7u8; 64], KeyType::MACKey).unwrap(); + k.set_key_len(len).unwrap(); + if len > 0 { + k.set_key_type(KeyType::MACKey).unwrap(); + k.set_security_strength(SecurityStrength::_128bit).unwrap_or(()); + } + assert!( + matches!( + BLAKE3KeyedHashKey::from_key_material_allow_weak(&k), + Err(MACError::InvalidLength(_)) + ), + "key length {len}" + ); + } + + let zero = KeyMaterial256::from_bytes_as_type(&[0u8; 32], KeyType::Zeroized).unwrap(); + assert_eq!(zero.key_type(), KeyType::Zeroized); + assert!(BLAKE3KeyedHashKey::from_key_material_allow_weak(&zero).is_ok()); + + // 128 bits is enough; the strength tag does not change the computation. + let mut ok = material(); + ok.set_security_strength(SecurityStrength::_128bit).unwrap(); + let strong = BLAKE3KeyedHashKey::from_key_material(&ok).unwrap(); + let mut weak = material(); + weak.set_security_strength(SecurityStrength::_112bit).unwrap(); + let weak = BLAKE3KeyedHashKey::from_key_material_allow_weak(&weak).unwrap(); + assert_eq!(strong.mac(b"abc"), weak.mac(b"abc")); + assert_eq!(strong.mac(b"abc"), key().mac(b"abc")); + } + + /// `generate` draws 32 bytes from the RNG and tags them at the RNG's strength (capped at the + /// length's), so an RNG below 128 bits cannot produce a key. + #[test] + fn generate_strength_and_bytes() { + let seed: [u8; 64] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(3)); + let from_seed = KeyMaterial256::from_bytes_as_type(&seed[..32], KeyType::MACKey).unwrap(); + let expected = BLAKE3KeyedHashKey::from_key_material(&from_seed).unwrap().mac(b"abc"); + + for strength in [SecurityStrength::_128bit, SecurityStrength::_256bit] { + let mut rng = FixedSeedRNG::<64>::new(seed); + rng.set_security_strength(strength); + let key = BLAKE3KeyedHashKey::generate(&mut rng).unwrap(); + assert_eq!(key.mac(b"abc"), expected, "the first 32 RNG bytes are the key"); + } + + for strength in [SecurityStrength::None, SecurityStrength::_112bit] { + let mut rng = FixedSeedRNG::<64>::new(seed); + rng.set_security_strength(strength); + assert!(matches!( + BLAKE3KeyedHashKey::generate(&mut rng), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + } + } + + #[test] + fn constants_and_params() { + assert_eq!(BLAKE3_KEYED_HASH_NAME, "BLAKE3-KeyedHash"); + assert_eq!(BLAKE3KeyedHash::ALG_NAME, BLAKE3_KEYED_HASH_NAME); + assert_eq!(BLAKE3KeyedHash::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(key().create_generator().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN, 1838); + assert_eq!(size_of::(), 32); + } + + /* ------------------------------------ suspend/resume ------------------------------------ */ + + /// No byte sequence of the key may appear in a keyed state: checked for a fresh MAC (whose + /// chunk chaining value *is* the key), mid-first-block, after exactly one compressed block, + /// and at chunk boundaries. + #[test] + fn keyed_state_excludes_key() { + let k = key(); + let msg = input(3000); + for len in [0usize, 1, 63, 64, 65, 129, 1024, 1025, 2048, 2049, 3000] { + let mut m = k.create_generator(); + m.do_update(&msg[..len]); + let state = m.suspend(); + for w in KEY.chunks(4) { + assert!( + !state.windows(4).any(|x| x == w), + "key bytes {w:?} found in state after {len} bytes" + ); + } + if len <= 64 { + assert_eq!(state[3..3 + 32], [0u8; 32], "chunk CV is zeroed / {len}"); + } + } + } + + /// Resume at many points, including before any input (the key-is-the-CV case) and after one + /// compressed block. + #[test] + fn keyed_suspend_round_trip() { + let case = CASES.iter().find(|c| c.input_len == 4097).unwrap(); + let msg = input(case.input_len); + let expected = h(case.keyed_hash); + let k = key(); + for prefix in [0usize, 1, 64, 65, 1024, 1025, 2048, 4096, 4097] { + let mut m = k.create_generator(); + m.do_update(&msg[..prefix]); + let mut resumed = BLAKE3KeyedHash::from_suspended(m.suspend(), &k).unwrap(); + resumed.do_update(&msg[prefix..]); + assert_eq!(resumed.into_squeezer().do_output(OUTPUT_LEN), expected, "prefix {prefix}"); + } + } + + /// A wrong key resumes but gives a different tag whenever the key is still needed (see + /// `mid_chunk_state_finishes_without_key` for when it is not). A key of the wrong type or + /// length cannot be supplied: resume takes a `BLAKE3KeyedHashKey`. + #[test] + fn keyed_resume_wrong_key() { + let k = key(); + let msg = input(3000); + // prefix 0: the chunk CV is the key. prefix 100: the rest of the message starts new chunks. + for prefix in [0usize, 100] { + let mut m = k.create_generator(); + m.do_update(&msg[..prefix]); + let state = m.suspend(); + let finish = |mut m: BLAKE3KeyedHash| { + m.do_update(&msg[prefix..]); + m.do_final() + }; + let right = finish(BLAKE3KeyedHash::from_suspended(state, &k).unwrap()); + let wrong = finish(BLAKE3KeyedHash::from_suspended(state, &other_key()).unwrap()); + assert_eq!(right, k.mac(&msg)); + assert_ne!(wrong, right, "prefix {prefix}"); + } + } + + /// Pins a property of BLAKE3 itself that the docs warn about: the key words enter only as the + /// first chaining value of each chunk and of each parent (spec s. 2.4, 2.5). For a prefix of + /// 65..=1024 bytes (first chunk, a block compressed, empty CV stack), a state resumed with the + /// WRONG key still produces the right tag for any extension that keeps the message at or under + /// 1024 bytes -- and does not for an extension that crosses the chunk boundary, nor for any + /// multi-chunk prefix. + #[test] + fn mid_chunk_state_finishes_without_key() { + let k = key(); + let other = other_key(); + let msg = input(3000); + // (prefix length, total length, does the wrong key reproduce the right tag?) + for (prefix, total, key_free) in [ + (65usize, 66usize, true), + (100, 1024, true), + (1024, 1024, true), + (64, 100, false), // nothing compressed yet: the chunk CV is the key + (100, 1025, false), // crosses into a second chunk + (1024, 1025, false), + (1100, 1200, false), // CV stack non-empty: the root is a parent node + (2100, 2200, false), + ] { + let mut m = k.create_generator(); + m.do_update(&msg[..prefix]); + let mut resumed = BLAKE3KeyedHash::from_suspended(m.suspend(), &other).unwrap(); + resumed.do_update(&msg[prefix..total]); + let right = k.mac(&msg[..total]); + assert_eq!(resumed.do_final() == right, key_free, "prefix {prefix}, total {total}"); + } + } + + /// The keyed state shares the chunk/stack validation with BLAKE3; spot-check that it is wired + /// in (the full branch-by-branch test is in blake3_tests.rs). + #[test] + fn keyed_suspend_validation() { + let k = key(); + let mut m = k.create_generator(); + m.do_update(&input(100)); + let state = m.suspend(); + let mut bad = state; + bad[3 + 104] = 65; // block_len + assert!(matches!( + BLAKE3KeyedHash::from_suspended(bad, &k), + Err(SuspendableError::InvalidData) + )); + let mut bad = state; + bad[3 + 106] = 1; // cv_stack_len vs chunk_counter 0 + assert!(matches!( + BLAKE3KeyedHash::from_suspended(bad, &k), + Err(SuspendableError::InvalidData) + )); + } +} diff --git a/crypto/blake3/tests/vectors/mod.rs b/crypto/blake3/tests/vectors/mod.rs new file mode 100644 index 000000000..b9519fb93 --- /dev/null +++ b/crypto/blake3/tests/vectors/mod.rs @@ -0,0 +1,245 @@ +//! The official BLAKE3 test vectors, generated by a script from +//! `test_vectors/test_vectors.json` of github.com/BLAKE3-team/BLAKE3 (never hand-edited). +//! +//! From the file's `_comment`: "The input in each case is filled with a repeating sequence of +//! 251 bytes: 0, 1, 2, ..., 249, 250, 0, 1, ..., and so on." Each output is 131 bytes of extended +//! output, and "implementations should also check that the first 32 bytes match their +//! default-length output." + +#![allow(dead_code)] + +/// The keyed_hash key (the JSON `key` field). +pub const KEY: &[u8; 32] = b"whats the Elvish word for friend"; + +/// The derive_key context string (the JSON `context_string` field). +pub const CONTEXT: &[u8] = b"BLAKE3 2019-12-27 16:29:52 test vectors context"; + +/// Length of every expected output in the file. +pub const OUTPUT_LEN: usize = 131; + +/// One case: input length and the hex of the hash, keyed_hash and derive_key outputs. +pub struct Case { + pub input_len: usize, + pub hash: &'static str, + pub keyed_hash: &'static str, + pub derive_key: &'static str, +} + +/// The input for a case: the repeating 0..=250 byte pattern. +pub fn input(len: usize) -> Vec { + (0..len).map(|i| (i % 251) as u8).collect() +} + +#[rustfmt::skip] +pub const CASES: &[Case] = &[ + Case { + input_len: 0, + hash: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a26f5487789e8f660afe6c99ef9e0c52b92e7393024a80459cf91f476f9ffdbda7001c22e159b402631f277ca96f2defdf1078282314e763699a31c5363165421cce14d", + keyed_hash: "92b2b75604ed3c761f9d6f62392c8a9227ad0ea3f09573e783f1498a4ed60d26b18171a2f22a4b94822c701f107153dba24918c4bae4d2945c20ece13387627d3b73cbf97b797d5e59948c7ef788f54372df45e45e4293c7dc18c1d41144a9758be58960856be1eabbe22c2653190de560ca3b2ac4aa692a9210694254c371e851bc8f", + derive_key: "2cc39783c223154fea8dfb7c1b1660f2ac2dcbd1c1de8277b0b0dd39b7e50d7d905630c8be290dfcf3e6842f13bddd573c098c3f17361f1f206b8cad9d088aa4a3f746752c6b0ce6a83b0da81d59649257cdf8eb3e9f7d4998e41021fac119deefb896224ac99f860011f73609e6e0e4540f93b273e56547dfd3aa1a035ba6689d89a0", + }, + Case { + input_len: 1, + hash: "2d3adedff11b61f14c886e35afa036736dcd87a74d27b5c1510225d0f592e213c3a6cb8bf623e20cdb535f8d1a5ffb86342d9c0b64aca3bce1d31f60adfa137b358ad4d79f97b47c3d5e79f179df87a3b9776ef8325f8329886ba42f07fb138bb502f4081cbcec3195c5871e6c23e2cc97d3c69a613eba131e5f1351f3f1da786545e5", + keyed_hash: "6d7878dfff2f485635d39013278ae14f1454b8c0a3a2d34bc1ab38228a80c95b6568c0490609413006fbd428eb3fd14e7756d90f73a4725fad147f7bf70fd61c4e0cf7074885e92b0e3f125978b4154986d4fb202a3f331a3fb6cf349a3a70e49990f98fe4289761c8602c4e6ab1138d31d3b62218078b2f3ba9a88e1d08d0dd4cea11", + derive_key: "b3e2e340a117a499c6cf2398a19ee0d29cca2bb7404c73063382693bf66cb06c5827b91bf889b6b97c5477f535361caefca0b5d8c4746441c57617111933158950670f9aa8a05d791daae10ac683cbef8faf897c84e6114a59d2173c3f417023a35d6983f2c7dfa57e7fc559ad751dbfb9ffab39c2ef8c4aafebc9ae973a64f0c76551", + }, + Case { + input_len: 2, + hash: "7b7015bb92cf0b318037702a6cdd81dee41224f734684c2c122cd6359cb1ee63d8386b22e2ddc05836b7c1bb693d92af006deb5ffbc4c70fb44d0195d0c6f252faac61659ef86523aa16517f87cb5f1340e723756ab65efb2f91964e14391de2a432263a6faf1d146937b35a33621c12d00be8223a7f1919cec0acd12097ff3ab00ab1", + keyed_hash: "5392ddae0e0a69d5f40160462cbd9bd889375082ff224ac9c758802b7a6fd20a9ffbf7efd13e989a6c246f96d3a96b9d279f2c4e63fb0bdff633957acf50ee1a5f658be144bab0f6f16500dee4aa5967fc2c586d85a04caddec90fffb7633f46a60786024353b9e5cebe277fcd9514217fee2267dcda8f7b31697b7c54fab6a939bf8f", + derive_key: "1f166565a7df0098ee65922d7fea425fb18b9943f19d6161e2d17939356168e6daa59cae19892b2d54f6fc9f475d26031fd1c22ae0a3e8ef7bdb23f452a15e0027629d2e867b1bb1e6ab21c71297377750826c404dfccc2406bd57a83775f89e0b075e59a7732326715ef912078e213944f490ad68037557518b79c0086de6d6f6cdd2", + }, + Case { + input_len: 3, + hash: "e1be4d7a8ab5560aa4199eea339849ba8e293d55ca0a81006726d184519e647f5b49b82f805a538c68915c1ae8035c900fd1d4b13902920fd05e1450822f36de9454b7e9996de4900c8e723512883f93f4345f8a58bfe64ee38d3ad71ab027765d25cdd0e448328a8e7a683b9a6af8b0af94fa09010d9186890b096a08471e4230a134", + keyed_hash: "39e67b76b5a007d4921969779fe666da67b5213b096084ab674742f0d5ec62b9b9142d0fab08e1b161efdbb28d18afc64d8f72160c958e53a950cdecf91c1a1bbab1a9c0f01def762a77e2e8545d4dec241e98a89b6db2e9a5b070fc110caae2622690bd7b76c02ab60750a3ea75426a6bb8803c370ffe465f07fb57def95df772c39f", + derive_key: "440aba35cb006b61fc17c0529255de438efc06a8c9ebf3f2ddac3b5a86705797f27e2e914574f4d87ec04c379e12789eccbfbc15892626042707802dbe4e97c3ff59dca80c1e54246b6d055154f7348a39b7d098b2b4824ebe90e104e763b2a447512132cede16243484a55a4e40a85790038bb0dcf762e8c053cabae41bbe22a5bff7", + }, + Case { + input_len: 4, + hash: "f30f5ab28fe047904037f77b6da4fea1e27241c5d132638d8bedce9d40494f328f603ba4564453e06cdcee6cbe728a4519bbe6f0d41e8a14b5b225174a566dbfa61b56afb1e452dc08c804f8c3143c9e2cc4a31bb738bf8c1917b55830c6e65797211701dc0b98daa1faeaa6ee9e56ab606ce03a1a881e8f14e87a4acf4646272cfd12", + keyed_hash: "7671dde590c95d5ac9616651ff5aa0a27bee5913a348e053b8aa9108917fe070116c0acff3f0d1fa97ab38d813fd46506089118147d83393019b068a55d646251ecf81105f798d76a10ae413f3d925787d6216a7eb444e510fd56916f1d753a5544ecf0072134a146b2615b42f50c179f56b8fae0788008e3e27c67482349e249cb86a", + derive_key: "f46085c8190d69022369ce1a18880e9b369c135eb93f3c63550d3e7630e91060fbd7d8f4258bec9da4e05044f88b91944f7cab317a2f0c18279629a3867fad0662c9ad4d42c6f27e5b124da17c8c4f3a94a025ba5d1b623686c6099d202a7317a82e3d95dae46a87de0555d727a5df55de44dab799a20dffe239594d6e99ed17950910", + }, + Case { + input_len: 5, + hash: "b40b44dfd97e7a84a996a91af8b85188c66c126940ba7aad2e7ae6b385402aa2ebcfdac6c5d32c31209e1f81a454751280db64942ce395104e1e4eaca62607de1c2ca748251754ea5bbe8c20150e7f47efd57012c63b3c6a6632dc1c7cd15f3e1c999904037d60fac2eb9397f2adbe458d7f264e64f1e73aa927b30988e2aed2f03620", + keyed_hash: "73ac69eecf286894d8102018a6fc729f4b1f4247d3703f69bdc6a5fe3e0c84616ab199d1f2f3e53bffb17f0a2209fe8b4f7d4c7bae59c2bc7d01f1ff94c67588cc6b38fa6024886f2c078bfe09b5d9e6584cd6c521c3bb52f4de7687b37117a2dbbec0d59e92fa9a8cc3240d4432f91757aabcae03e87431dac003e7d73574bfdd8218", + derive_key: "1f24eda69dbcb752847ec3ebb5dd42836d86e58500c7c98d906ecd82ed9ae47f6f48a3f67e4e43329c9a89b1ca526b9b35cbf7d25c1e353baffb590fd79be58ddb6c711f1a6b60e98620b851c688670412fcb0435657ba6b638d21f0f2a04f2f6b0bd8834837b10e438d5f4c7c2c71299cf7586ea9144ed09253d51f8f54dd6bff719d", + }, + Case { + input_len: 6, + hash: "06c4e8ffb6872fad96f9aaca5eee1553eb62aed0ad7198cef42e87f6a616c844611a30c4e4f37fe2fe23c0883cde5cf7059d88b657c7ed2087e3d210925ede716435d6d5d82597a1e52b9553919e804f5656278bd739880692c94bff2824d8e0b48cac1d24682699e4883389dc4f2faa2eb3b4db6e39debd5061ff3609916f3e07529a", + keyed_hash: "82d3199d0013035682cc7f2a399d4c212544376a839aa863a0f4c91220ca7a6dc2ffb3aa05f2631f0fa9ac19b6e97eb7e6669e5ec254799350c8b8d189e8807800842a5383c4d907c932f34490aaf00064de8cdb157357bde37c1504d2960034930887603abc5ccb9f5247f79224baff6120a3c622a46d7b1bcaee02c5025460941256", + derive_key: "be96b30b37919fe4379dfbe752ae77b4f7e2ab92f7ff27435f76f2f065f6a5f435ae01a1d14bd5a6b3b69d8cbd35f0b01ef2173ff6f9b640ca0bd4748efa398bf9a9c0acd6a66d9332fdc9b47ffe28ba7ab6090c26747b85f4fab22f936b71eb3f64613d8bd9dfabe9bb68da19de78321b481e5297df9e40ec8a3d662f3e1479c65de0", + }, + Case { + input_len: 7, + hash: "3f8770f387faad08faa9d8414e9f449ac68e6ff0417f673f602a646a891419fe66036ef6e6d1a8f54baa9fed1fc11c77cfb9cff65bae915045027046ebe0c01bf5a941f3bb0f73791d3fc0b84370f9f30af0cd5b0fc334dd61f70feb60dad785f070fef1f343ed933b49a5ca0d16a503f599a365a4296739248b28d1a20b0e2cc8975c", + keyed_hash: "af0a7ec382aedc0cfd626e49e7628bc7a353a4cb108855541a5651bf64fbb28a7c5035ba0f48a9c73dabb2be0533d02e8fd5d0d5639a18b2803ba6bf527e1d145d5fd6406c437b79bcaad6c7bdf1cf4bd56a893c3eb9510335a7a798548c6753f74617bede88bef924ba4b334f8852476d90b26c5dc4c3668a2519266a562c6c8034a6", + derive_key: "dc3b6485f9d94935329442916b0d059685ba815a1fa2a14107217453a7fc9f0e66266db2ea7c96843f9d8208e600a73f7f45b2f55b9e6d6a7ccf05daae63a3fdd10b25ac0bd2e224ce8291f88c05976d575df998477db86fb2cfbbf91725d62cb57acfeb3c2d973b89b503c2b60dde85a7802b69dc1ac2007d5623cbea8cbfb6b181f5", + }, + Case { + input_len: 8, + hash: "2351207d04fc16ade43ccab08600939c7c1fa70a5c0aaca76063d04c3228eaeb725d6d46ceed8f785ab9f2f9b06acfe398c6699c6129da084cb531177445a682894f9685eaf836999221d17c9a64a3a057000524cd2823986db378b074290a1a9b93a22e135ed2c14c7e20c6d045cd00b903400374126676ea78874d79f2dd7883cf5c", + keyed_hash: "be2f5495c61cba1bb348a34948c004045e3bd4dae8f0fe82bf44d0da245a060048eb5e68ce6dea1eb0229e144f578b3aa7e9f4f85febd135df8525e6fe40c6f0340d13dd09b255ccd5112a94238f2be3c0b5b7ecde06580426a93e0708555a265305abf86d874e34b4995b788e37a823491f25127a502fe0704baa6bfdf04e76c13276", + derive_key: "2b166978cef14d9d438046c720519d8b1cad707e199746f1562d0c87fbd32940f0e2545a96693a66654225ebbaac76d093bfa9cd8f525a53acb92a861a98c42e7d1c4ae82e68ab691d510012edd2a728f98cd4794ef757e94d6546961b4f280a51aac339cc95b64a92b83cc3f26d8af8dfb4c091c240acdb4d47728d23e7148720ef04", + }, + Case { + input_len: 63, + hash: "e9bc37a594daad83be9470df7f7b3798297c3d834ce80ba85d6e207627b7db7b1197012b1e7d9af4d7cb7bdd1f3bb49a90a9b5dec3ea2bbc6eaebce77f4e470cbf4687093b5352f04e4a4570fba233164e6acc36900e35d185886a827f7ea9bdc1e5c3ce88b095a200e62c10c043b3e9bc6cb9b6ac4dfa51794b02ace9f98779040755", + keyed_hash: "bb1eb5d4afa793c1ebdd9fb08def6c36d10096986ae0cfe148cd101170ce37aea05a63d74a840aecd514f654f080e51ac50fd617d22610d91780fe6b07a26b0847abb38291058c97474ef6ddd190d30fc318185c09ca1589d2024f0a6f16d45f11678377483fa5c005b2a107cb9943e5da634e7046855eaa888663de55d6471371d55d", + derive_key: "b6451e30b953c206e34644c6803724e9d2725e0893039cfc49584f991f451af3b89e8ff572d3da4f4022199b9563b9d70ebb616efff0763e9abec71b550f1371e233319c4c4e74da936ba8e5bbb29a598e007a0bbfa929c99738ca2cc098d59134d11ff300c39f82e2fce9f7f0fa266459503f64ab9913befc65fddc474f6dc1c67669", + }, + Case { + input_len: 64, + hash: "4eed7141ea4a5cd4b788606bd23f46e212af9cacebacdc7d1f4c6dc7f2511b98fc9cc56cb831ffe33ea8e7e1d1df09b26efd2767670066aa82d023b1dfe8ab1b2b7fbb5b97592d46ffe3e05a6a9b592e2949c74160e4674301bc3f97e04903f8c6cf95b863174c33228924cdef7ae47559b10b294acd660666c4538833582b43f82d74", + keyed_hash: "ba8ced36f327700d213f120b1a207a3b8c04330528586f414d09f2f7d9ccb7e68244c26010afc3f762615bbac552a1ca909e67c83e2fd5478cf46b9e811efccc93f77a21b17a152ebaca1695733fdb086e23cd0eb48c41c034d52523fc21236e5d8c9255306e48d52ba40b4dac24256460d56573d1312319afcf3ed39d72d0bfc69acb", + derive_key: "a5c4a7053fa86b64746d4bb688d06ad1f02a18fce9afd3e818fefaa7126bf73e9b9493a9befebe0bf0c9509fb3105cfa0e262cde141aa8e3f2c2f77890bb64a4cca96922a21ead111f6338ad5244f2c15c44cb595443ac2ac294231e31be4a4307d0a91e874d36fc9852aeb1265c09b6e0cda7c37ef686fbbcab97e8ff66718be048bb", + }, + Case { + input_len: 65, + hash: "de1e5fa0be70df6d2be8fffd0e99ceaa8eb6e8c93a63f2d8d1c30ecb6b263dee0e16e0a4749d6811dd1d6d1265c29729b1b75a9ac346cf93f0e1d7296dfcfd4313b3a227faaaaf7757cc95b4e87a49be3b8a270a12020233509b1c3632b3485eef309d0abc4a4a696c9decc6e90454b53b000f456a3f10079072baaf7a981653221f2c", + keyed_hash: "c0a4edefa2d2accb9277c371ac12fcdbb52988a86edc54f0716e1591b4326e72d5e795f46a596b02d3d4bfb43abad1e5d19211152722ec1f20fef2cd413e3c22f2fc5da3d73041275be6ede3517b3b9f0fc67ade5956a672b8b75d96cb43294b9041497de92637ed3f2439225e683910cb3ae923374449ca788fb0f9bea92731bc26ad", + derive_key: "51fd05c3c1cfbc8ed67d139ad76f5cf8236cd2acd26627a30c104dfd9d3ff8a82b02e8bd36d8498a75ad8c8e9b15eb386970283d6dd42c8ae7911cc592887fdbe26a0a5f0bf821cd92986c60b2502c9be3f98a9c133a7e8045ea867e0828c7252e739321f7c2d65daee4468eb4429efae469a42763f1f94977435d10dccae3e3dce88d", + }, + Case { + input_len: 127, + hash: "d81293fda863f008c09e92fc382a81f5a0b4a1251cba1634016a0f86a6bd640de3137d477156d1fde56b0cf36f8ef18b44b2d79897bece12227539ac9ae0a5119da47644d934d26e74dc316145dcb8bb69ac3f2e05c242dd6ee06484fcb0e956dc44355b452c5e2bbb5e2b66e99f5dd443d0cbcaaafd4beebaed24ae2f8bb672bcef78", + keyed_hash: "c64200ae7dfaf35577ac5a9521c47863fb71514a3bcad18819218b818de85818ee7a317aaccc1458f78d6f65f3427ec97d9c0adb0d6dacd4471374b621b7b5f35cd54663c64dbe0b9e2d95632f84c611313ea5bd90b71ce97b3cf645776f3adc11e27d135cbadb9875c2bf8d3ae6b02f8a0206aba0c35bfe42574011931c9a255ce6dc", + derive_key: "c91c090ceee3a3ac81902da31838012625bbcd73fcb92e7d7e56f78deba4f0c3feeb3974306966ccb3e3c69c337ef8a45660ad02526306fd685c88542ad00f759af6dd1adc2e50c2b8aac9f0c5221ff481565cf6455b772515a69463223202e5c371743e35210bbbbabd89651684107fd9fe493c937be16e39cfa7084a36207c99bea3", + }, + Case { + input_len: 128, + hash: "f17e570564b26578c33bb7f44643f539624b05df1a76c81f30acd548c44b45efa69faba091427f9c5c4caa873aa07828651f19c55bad85c47d1368b11c6fd99e47ecba5820a0325984d74fe3e4058494ca12e3f1d3293d0010a9722f7dee64f71246f75e9361f44cc8e214a100650db1313ff76a9f93ec6e84edb7add1cb4a95019b0c", + keyed_hash: "b04fe15577457267ff3b6f3c947d93be581e7e3a4b018679125eaf86f6a628ecd86bbe0001f10bda47e6077b735016fca8119da11348d93ca302bbd125bde0db2b50edbe728a620bb9d3e6f706286aedea973425c0b9eedf8a38873544cf91badf49ad92a635a93f71ddfcee1eae536c25d1b270956be16588ef1cfef2f1d15f650bd5", + derive_key: "81720f34452f58a0120a58b6b4608384b5c51d11f39ce97161a0c0e442ca022550e7cd651e312f0b4c6afb3c348ae5dd17d2b29fab3b894d9a0034c7b04fd9190cbd90043ff65d1657bbc05bfdecf2897dd894c7a1b54656d59a50b51190a9da44db426266ad6ce7c173a8c0bbe091b75e734b4dadb59b2861cd2518b4e7591e4b83c9", + }, + Case { + input_len: 129, + hash: "683aaae9f3c5ba37eaaf072aed0f9e30bac0865137bae68b1fde4ca2aebdcb12f96ffa7b36dd78ba321be7e842d364a62a42e3746681c8bace18a4a8a79649285c7127bf8febf125be9de39586d251f0d41da20980b70d35e3dac0eee59e468a894fa7e6a07129aaad09855f6ad4801512a116ba2b7841e6cfc99ad77594a8f2d181a7", + keyed_hash: "d4a64dae6cdccbac1e5287f54f17c5f985105457c1a2ec1878ebd4b57e20d38f1c9db018541eec241b748f87725665b7b1ace3e0065b29c3bcb232c90e37897fa5aaee7e1e8a2ecfcd9b51463e42238cfdd7fee1aecb3267fa7f2128079176132a412cd8aaf0791276f6b98ff67359bd8652ef3a203976d5ff1cd41885573487bcd683", + derive_key: "938d2d4435be30eafdbb2b7031f7857c98b04881227391dc40db3c7b21f41fc18d72d0f9c1de5760e1941aebf3100b51d64644cb459eb5d20258e233892805eb98b07570ef2a1787cd48e117c8d6a63a68fd8fc8e59e79dbe63129e88352865721c8d5f0cf183f85e0609860472b0d6087cefdd186d984b21542c1c780684ed6832d8d", + }, + Case { + input_len: 1023, + hash: "10108970eeda3eb932baac1428c7a2163b0e924c9a9e25b35bba72b28f70bd11a182d27a591b05592b15607500e1e8dd56bc6c7fc063715b7a1d737df5bad3339c56778957d870eb9717b57ea3d9fb68d1b55127bba6a906a4a24bbd5acb2d123a37b28f9e9a81bbaae360d58f85e5fc9d75f7c370a0cc09b6522d9c8d822f2f28f485", + keyed_hash: "c951ecdf03288d0fcc96ee3413563d8a6d3589547f2c2fb36d9786470f1b9d6e890316d2e6d8b8c25b0a5b2180f94fb1a158ef508c3cde45e2966bd796a696d3e13efd86259d756387d9becf5c8bf1ce2192b87025152907b6d8cc33d17826d8b7b9bc97e38c3c85108ef09f013e01c229c20a83d9e8efac5b37470da28575fd755a10", + derive_key: "74a16c1c3d44368a86e1ca6df64be6a2f64cce8f09220787450722d85725dea59c413264404661e9e4d955409dfe4ad3aa487871bcd454ed12abfe2c2b1eb7757588cf6cb18d2eccad49e018c0d0fec323bec82bf1644c6325717d13ea712e6840d3e6e730d35553f59eff5377a9c350bcc1556694b924b858f329c44ee64b884ef00d", + }, + Case { + input_len: 1024, + hash: "42214739f095a406f3fc83deb889744ac00df831c10daa55189b5d121c855af71cf8107265ecdaf8505b95d8fcec83a98a6a96ea5109d2c179c47a387ffbb404756f6eeae7883b446b70ebb144527c2075ab8ab204c0086bb22b7c93d465efc57f8d917f0b385c6df265e77003b85102967486ed57db5c5ca170ba441427ed9afa684e", + keyed_hash: "75c46f6f3d9eb4f55ecaaee480db732e6c2105546f1e675003687c31719c7ba4a78bc838c72852d4f49c864acb7adafe2478e824afe51c8919d06168414c265f298a8094b1ad813a9b8614acabac321f24ce61c5a5346eb519520d38ecc43e89b5000236df0597243e4d2493fd626730e2ba17ac4d8824d09d1a4a8f57b8227778e2de", + derive_key: "7356cd7720d5b66b6d0697eb3177d9f8d73a4a5c5e968896eb6a6896843027066c23b601d3ddfb391e90d5c8eccdef4ae2a264bce9e612ba15e2bc9d654af1481b2e75dbabe615974f1070bba84d56853265a34330b4766f8e75edd1f4a1650476c10802f22b64bd3919d246ba20a17558bc51c199efdec67e80a227251808d8ce5bad", + }, + Case { + input_len: 1025, + hash: "d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444f4c4a22b4b399155358a994e52bf255de60035742ec71bd08ac275a1b51cc6bfe332b0ef84b409108cda080e6269ed4b3e2c3f7d722aa4cdc98d16deb554e5627be8f955c98e1d5f9565a9194cad0c4285f93700062d9595adb992ae68ff12800ab67a", + keyed_hash: "357dc55de0c7e382c900fd6e320acc04146be01db6a8ce7210b7189bd664ea69362396b77fdc0d2634a552970843722066c3c15902ae5097e00ff53f1e116f1cd5352720113a837ab2452cafbde4d54085d9cf5d21ca613071551b25d52e69d6c81123872b6f19cd3bc1333edf0c52b94de23ba772cf82636cff4542540a7738d5b930", + derive_key: "effaa245f065fbf82ac186839a249707c3bddf6d3fdda22d1b95a3c970379bcb5d31013a167509e9066273ab6e2123bc835b408b067d88f96addb550d96b6852dad38e320b9d940f86db74d398c770f462118b35d2724efa13da97194491d96dd37c3c09cbef665953f2ee85ec83d88b88d11547a6f911c8217cca46defa2751e7f3ad", + }, + Case { + input_len: 2048, + hash: "e776b6028c7cd22a4d0ba182a8bf62205d2ef576467e838ed6f2529b85fba24a9a60bf80001410ec9eea6698cd537939fad4749edd484cb541aced55cd9bf54764d063f23f6f1e32e12958ba5cfeb1bf618ad094266d4fc3c968c2088f677454c288c67ba0dba337b9d91c7e1ba586dc9a5bc2d5e90c14f53a8863ac75655461cea8f9", + keyed_hash: "879cf1fa2ea0e79126cb1063617a05b6ad9d0b696d0d757cf053439f60a99dd10173b961cd574288194b23ece278c330fbb8585485e74967f31352a8183aa782b2b22f26cdcadb61eed1a5bc144b8198fbb0c13abbf8e3192c145d0a5c21633b0ef86054f42809df823389ee40811a5910dcbd1018af31c3b43aa55201ed4edaac74fe", + derive_key: "7b2945cb4fef70885cc5d78a87bf6f6207dd901ff239201351ffac04e1088a23e2c11a1ebffcea4d80447867b61badb1383d842d4e79645d48dd82ccba290769caa7af8eaa1bd78a2a5e6e94fbdab78d9c7b74e894879f6a515257ccf6f95056f4e25390f24f6b35ffbb74b766202569b1d797f2d4bd9d17524c720107f985f4ddc583", + }, + Case { + input_len: 2049, + hash: "5f4d72f40d7a5f82b15ca2b2e44b1de3c2ef86c426c95c1af0b687952256303096de31d71d74103403822a2e0bc1eb193e7aecc9643a76b7bbc0c9f9c52e8783aae98764ca468962b5c2ec92f0c74eb5448d519713e09413719431c802f948dd5d90425a4ecdadece9eb178d80f26efccae630734dff63340285adec2aed3b51073ad3", + keyed_hash: "9f29700902f7c86e514ddc4df1e3049f258b2472b6dd5267f61bf13983b78dd5f9a88abfefdfa1e00b418971f2b39c64ca621e8eb37fceac57fd0c8fc8e117d43b81447be22d5d8186f8f5919ba6bcc6846bd7d50726c06d245672c2ad4f61702c646499ee1173daa061ffe15bf45a631e2946d616a4c345822f1151284712f76b2b0e", + derive_key: "2ea477c5515cc3dd606512ee72bb3e0e758cfae7232826f35fb98ca1bcbdf27316d8e9e79081a80b046b60f6a263616f33ca464bd78d79fa18200d06c7fc9bffd808cc4755277a7d5e09da0f29ed150f6537ea9bed946227ff184cc66a72a5f8c1e4bd8b04e81cf40fe6dc4427ad5678311a61f4ffc39d195589bdbc670f63ae70f4b6", + }, + Case { + input_len: 3072, + hash: "b98cb0ff3623be03326b373de6b9095218513e64f1ee2edd2525c7ad1e5cffd29a3f6b0b978d6608335c09dc94ccf682f9951cdfc501bfe47b9c9189a6fc7b404d120258506341a6d802857322fbd20d3e5dae05b95c88793fa83db1cb08e7d8008d1599b6209d78336e24839724c191b2a52a80448306e0daa84a3fdb566661a37e11", + keyed_hash: "044a0e7b172a312dc02a4c9a818c036ffa2776368d7f528268d2e6b5df19177022f302d0529e4174cc507c463671217975e81dab02b8fdeb0d7ccc7568dd22574c783a76be215441b32e91b9a904be8ea81f7a0afd14bad8ee7c8efc305ace5d3dd61b996febe8da4f56ca0919359a7533216e2999fc87ff7d8f176fbecb3d6f34278b", + derive_key: "050df97f8c2ead654d9bb3ab8c9178edcd902a32f8495949feadcc1e0480c46b3604131bbd6e3ba573b6dd682fa0a63e5b165d39fc43a625d00207607a2bfeb65ff1d29292152e26b298868e3b87be95d6458f6f2ce6118437b632415abe6ad522874bcd79e4030a5e7bad2efa90a7a7c67e93f0a18fb28369d0a9329ab5c24134ccb0", + }, + Case { + input_len: 3073, + hash: "7124b49501012f81cc7f11ca069ec9226cecb8a2c850cfe644e327d22d3e1cd39a27ae3b79d68d89da9bf25bc27139ae65a324918a5f9b7828181e52cf373c84f35b639b7fccbb985b6f2fa56aea0c18f531203497b8bbd3a07ceb5926f1cab74d14bd66486d9a91eba99059a98bd1cd25876b2af5a76c3e9eed554ed72ea952b603bf", + keyed_hash: "68dede9bef00ba89e43f31a6825f4cf433389fedae75c04ee9f0cf16a427c95a96d6da3fe985054d3478865be9a092250839a697bbda74e279e8a9e69f0025e4cfddd6cfb434b1cd9543aaf97c635d1b451a4386041e4bb100f5e45407cbbc24fa53ea2de3536ccb329e4eb9466ec37093a42cf62b82903c696a93a50b702c80f3c3c5", + derive_key: "72613c9ec9ff7e40f8f5c173784c532ad852e827dba2bf85b2ab4b76f7079081576288e552647a9d86481c2cae75c2dd4e7c5195fb9ada1ef50e9c5098c249d743929191441301c69e1f48505a4305ec1778450ee48b8e69dc23a25960fe33070ea549119599760a8a2d28aeca06b8c5e9ba58bc19e11fe57b6ee98aa44b2a8e6b14a5", + }, + Case { + input_len: 4096, + hash: "015094013f57a5277b59d8475c0501042c0b642e531b0a1c8f58d2163229e9690289e9409ddb1b99768eafe1623da896faf7e1114bebeadc1be30829b6f8af707d85c298f4f0ff4d9438aef948335612ae921e76d411c3a9111df62d27eaf871959ae0062b5492a0feb98ef3ed4af277f5395172dbe5c311918ea0074ce0036454f620", + keyed_hash: "befc660aea2f1718884cd8deb9902811d332f4fc4a38cf7c7300d597a081bfc0bbb64a36edb564e01e4b4aaf3b060092a6b838bea44afebd2deb8298fa562b7b597c757b9df4c911c3ca462e2ac89e9a787357aaf74c3b56d5c07bc93ce899568a3eb17d9250c20f6c5f6c1e792ec9a2dcb715398d5a6ec6d5c54f586a00403a1af1de", + derive_key: "1e0d7f3db8c414c97c6307cbda6cd27ac3b030949da8e23be1a1a924ad2f25b9d78038f7b198596c6cc4a9ccf93223c08722d684f240ff6569075ed81591fd93f9fff1110b3a75bc67e426012e5588959cc5a4c192173a03c00731cf84544f65a2fb9378989f72e9694a6a394a8a30997c2e67f95a504e631cd2c5f55246024761b245", + }, + Case { + input_len: 4097, + hash: "9b4052b38f1c5fc8b1f9ff7ac7b27cd242487b3d890d15c96a1c25b8aa0fb99505f91b0b5600a11251652eacfa9497b31cd3c409ce2e45cfe6c0a016967316c426bd26f619eab5d70af9a418b845c608840390f361630bd497b1ab44019316357c61dbe091ce72fc16dc340ac3d6e009e050b3adac4b5b2c92e722cffdc46501531956", + keyed_hash: "00df940cd36bb9fa7cbbc3556744e0dbc8191401afe70520ba292ee3ca80abbc606db4976cfdd266ae0abf667d9481831ff12e0caa268e7d3e57260c0824115a54ce595ccc897786d9dcbf495599cfd90157186a46ec800a6763f1c59e36197e9939e900809f7077c102f888caaf864b253bc41eea812656d46742e4ea42769f89b83f", + derive_key: "aca51029626b55fda7117b42a7c211f8c6e9ba4fe5b7a8ca922f34299500ead8a897f66a400fed9198fd61dd2d58d382458e64e100128075fc54b860934e8de2e84170734b06e1d212a117100820dbc48292d148afa50567b8b84b1ec336ae10d40c8c975a624996e12de31abbe135d9d159375739c333798a80c64ae895e51e22f3ad", + }, + Case { + input_len: 5120, + hash: "9cadc15fed8b5d854562b26a9536d9707cadeda9b143978f319ab34230535833acc61c8fdc114a2010ce8038c853e121e1544985133fccdd0a2d507e8e615e611e9a0ba4f47915f49e53d721816a9198e8b30f12d20ec3689989175f1bf7a300eee0d9321fad8da232ece6efb8e9fd81b42ad161f6b9550a069e66b11b40487a5f5059", + keyed_hash: "2c493e48e9b9bf31e0553a22b23503c0a3388f035cece68eb438d22fa1943e209b4dc9209cd80ce7c1f7c9a744658e7e288465717ae6e56d5463d4f80cdb2ef56495f6a4f5487f69749af0c34c2cdfa857f3056bf8d807336a14d7b89bf62bef2fb54f9af6a546f818dc1e98b9e07f8a5834da50fa28fb5874af91bf06020d1bf0120e", + derive_key: "7a7acac8a02adcf3038d74cdd1d34527de8a0fcc0ee3399d1262397ce5817f6055d0cefd84d9d57fe792d65a278fd20384ac6c30fdb340092f1a74a92ace99c482b28f0fc0ef3b923e56ade20c6dba47e49227166251337d80a037e987ad3a7f728b5ab6dfafd6e2ab1bd583a95d9c895ba9c2422c24ea0f62961f0dca45cad47bfa0d", + }, + Case { + input_len: 5121, + hash: "628bd2cb2004694adaab7bbd778a25df25c47b9d4155a55f8fbd79f2fe154cff96adaab0613a6146cdaabe498c3a94e529d3fc1da2bd08edf54ed64d40dcd6777647eac51d8277d70219a9694334a68bc8f0f23e20b0ff70ada6f844542dfa32cd4204ca1846ef76d811cdb296f65e260227f477aa7aa008bac878f72257484f2b6c95", + keyed_hash: "6ccf1c34753e7a044db80798ecd0782a8f76f33563accaddbfbb2e0ea4b2d0240d07e63f13667a8d1490e5e04f13eb617aea16a8c8a5aaed1ef6fbde1b0515e3c81050b361af6ead126032998290b563e3caddeaebfab592e155f2e161fb7cba939092133f23f9e65245e58ec23457b78a2e8a125588aad6e07d7f11a85b88d375b72d", + derive_key: "b07f01e518e702f7ccb44a267e9e112d403a7b3f4883a47ffbed4b48339b3c341a0add0ac032ab5aaea1e4e5b004707ec5681ae0fcbe3796974c0b1cf31a194740c14519273eedaabec832e8a784b6e7cfc2c5952677e6c3f2c3914454082d7eb1ce1766ac7d75a4d3001fc89544dd46b5147382240d689bbbaefc359fb6ae30263165", + }, + Case { + input_len: 6144, + hash: "3e2e5b74e048f3add6d21faab3f83aa44d3b2278afb83b80b3c35164ebeca2054d742022da6fdda444ebc384b04a54c3ac5839b49da7d39f6d8a9db03deab32aade156c1c0311e9b3435cde0ddba0dce7b26a376cad121294b689193508dd63151603c6ddb866ad16c2ee41585d1633a2cea093bea714f4c5d6b903522045b20395c83", + keyed_hash: "3d6b6d21281d0ade5b2b016ae4034c5dec10ca7e475f90f76eac7138e9bc8f1dc35754060091dc5caf3efabe0603c60f45e415bb3407db67e6beb3d11cf8e4f7907561f05dace0c15807f4b5f389c841eb114d81a82c02a00b57206b1d11fa6e803486b048a5ce87105a686dee041207e095323dfe172df73deb8c9532066d88f9da7e", + derive_key: "2a95beae63ddce523762355cf4b9c1d8f131465780a391286a5d01abb5683a1597099e3c6488aab6c48f3c15dbe1942d21dbcdc12115d19a8b8465fb54e9053323a9178e4275647f1a9927f6439e52b7031a0b465c861a3fc531527f7758b2b888cf2f20582e9e2c593709c0a44f9c6e0f8b963994882ea4168827823eef1f64169fef", + }, + Case { + input_len: 6145, + hash: "f1323a8631446cc50536a9f705ee5cb619424d46887f3c376c695b70e0f0507f18a2cfdd73c6e39dd75ce7c1c6e3ef238fd54465f053b25d21044ccb2093beb015015532b108313b5829c3621ce324b8e14229091b7c93f32db2e4e63126a377d2a63a3597997d4f1cba59309cb4af240ba70cebff9a23d5e3ff0cdae2cfd54e070022", + keyed_hash: "9ac301e9e39e45e3250a7e3b3df701aa0fb6889fbd80eeecf28dbc6300fbc539f3c184ca2f59780e27a576c1d1fb9772e99fd17881d02ac7dfd39675aca918453283ed8c3169085ef4a466b91c1649cc341dfdee60e32231fc34c9c4e0b9a2ba87ca8f372589c744c15fd6f985eec15e98136f25beeb4b13c4e43dc84abcc79cd4646c", + derive_key: "379bcc61d0051dd489f686c13de00d5b14c505245103dc040d9e4dd1facab8e5114493d029bdbd295aaa744a59e31f35c7f52dba9c3642f773dd0b4262a9980a2aef811697e1305d37ba9d8b6d850ef07fe41108993180cf779aeece363704c76483458603bbeeb693cffbbe5588d1f3535dcad888893e53d977424bb707201569a8d2", + }, + Case { + input_len: 7168, + hash: "61da957ec2499a95d6b8023e2b0e604ec7f6b50e80a9678b89d2628e99ada77a5707c321c83361793b9af62a40f43b523df1c8633cecb4cd14d00bdc79c78fca5165b863893f6d38b02ff7236c5a9a8ad2dba87d24c547cab046c29fc5bc1ed142e1de4763613bb162a5a538e6ef05ed05199d751f9eb58d332791b8d73fb74e4fce95", + keyed_hash: "b42835e40e9d4a7f42ad8cc04f85a963a76e18198377ed84adddeaecacc6f3fca2f01d5277d69bb681c70fa8d36094f73ec06e452c80d2ff2257ed82e7ba348400989a65ee8daa7094ae0933e3d2210ac6395c4af24f91c2b590ef87d7788d7066ea3eaebca4c08a4f14b9a27644f99084c3543711b64a070b94f2c9d1d8a90d035d52", + derive_key: "11c37a112765370c94a51415d0d651190c288566e295d505defdad895dae223730d5a5175a38841693020669c7638f40b9bc1f9f39cf98bda7a5b54ae24218a800a2116b34665aa95d846d97ea988bfcb53dd9c055d588fa21ba78996776ea6c40bc428b53c62b5f3ccf200f647a5aae8067f0ea1976391fcc72af1945100e2a6dcb88", + }, + Case { + input_len: 7169, + hash: "a003fc7a51754a9b3c7fae0367ab3d782dccf28855a03d435f8cfe74605e781798a8b20534be1ca9eb2ae2df3fae2ea60e48c6fb0b850b1385b5de0fe460dbe9d9f9b0d8db4435da75c601156df9d047f4ede008732eb17adc05d96180f8a73548522840779e6062d643b79478a6e8dbce68927f36ebf676ffa7d72d5f68f050b119c8", + keyed_hash: "ed9b1a922c046fdb3d423ae34e143b05ca1bf28b710432857bf738bcedbfa5113c9e28d72fcbfc020814ce3f5d4fc867f01c8f5b6caf305b3ea8a8ba2da3ab69fabcb438f19ff11f5378ad4484d75c478de425fb8e6ee809b54eec9bdb184315dc856617c09f5340451bf42fd3270a7b0b6566169f242e533777604c118a6358250f54", + derive_key: "554b0a5efea9ef183f2f9b931b7497995d9eb26f5c5c6dad2b97d62fc5ac31d99b20652c016d88ba2a611bbd761668d5eda3e568e940faae24b0d9991c3bd25a65f770b89fdcadabcb3d1a9c1cb63e69721cacf1ae69fefdcef1e3ef41bc5312ccc17222199e47a26552c6adc460cf47a72319cb5039369d0060eaea59d6c65130f1dd", + }, + Case { + input_len: 8192, + hash: "aae792484c8efe4f19e2ca7d371d8c467ffb10748d8a5a1ae579948f718a2a635fe51a27db045a567c1ad51be5aa34c01c6651c4d9b5b5ac5d0fd58cf18dd61a47778566b797a8c67df7b1d60b97b19288d2d877bb2df417ace009dcb0241ca1257d62712b6a4043b4ff33f690d849da91ea3bf711ed583cb7b7a7da2839ba71309bbf", + keyed_hash: "dc9637c8845a770b4cbf76b8daec0eebf7dc2eac11498517f08d44c8fc00d58a4834464159dcbc12a0ba0c6d6eb41bac0ed6585cabfe0aca36a375e6c5480c22afdc40785c170f5a6b8a1107dbee282318d00d915ac9ed1143ad40765ec120042ee121cd2baa36250c618adaf9e27260fda2f94dea8fb6f08c04f8f10c78292aa46102", + derive_key: "ad01d7ae4ad059b0d33baa3c01319dcf8088094d0359e5fd45d6aeaa8b2d0c3d4c9e58958553513b67f84f8eac653aeeb02ae1d5672dcecf91cd9985a0e67f4501910ecba25555395427ccc7241d70dc21c190e2aadee875e5aae6bf1912837e53411dabf7a56cbf8e4fb780432b0d7fe6cec45024a0788cf5874616407757e9e6bef7", + }, + Case { + input_len: 8193, + hash: "bab6c09cb8ce8cf459261398d2e7aef35700bf488116ceb94a36d0f5f1b7bc3bb2282aa69be089359ea1154b9a9286c4a56af4de975a9aa4a5c497654914d279bea60bb6d2cf7225a2fa0ff5ef56bbe4b149f3ed15860f78b4e2ad04e158e375c1e0c0b551cd7dfc82f1b155c11b6b3ed51ec9edb30d133653bb5709d1dbd55f4e1ff6", + keyed_hash: "954a2a75420c8d6547e3ba5b98d963e6fa6491addc8c023189cc519821b4a1f5f03228648fd983aef045c2fa8290934b0866b615f585149587dda2299039965328835a2b18f1d63b7e300fc76ff260b571839fe44876a4eae66cbac8c67694411ed7e09df51068a22c6e67d6d3dd2cca8ff12e3275384006c80f4db68023f24eebba57", + derive_key: "af1e0346e389b17c23200270a64aa4e1ead98c61695d917de7d5b00491c9b0f12f20a01d6d622edf3de026a4db4e4526225debb93c1237934d71c7340bb5916158cbdafe9ac3225476b6ab57a12357db3abbad7a26c6e66290e44034fb08a20a8d0ec264f309994d2810c49cfba6989d7abb095897459f5425adb48aba07c5fb3c83c0", + }, + Case { + input_len: 16384, + hash: "f875d6646de28985646f34ee13be9a576fd515f76b5b0a26bb324735041ddde49d764c270176e53e97bdffa58d549073f2c660be0e81293767ed4e4929f9ad34bbb39a529334c57c4a381ffd2a6d4bfdbf1482651b172aa883cc13408fa67758a3e47503f93f87720a3177325f7823251b85275f64636a8f1d599c2e49722f42e93893", + keyed_hash: "9e9fc4eb7cf081ea7c47d1807790ed211bfec56aa25bb7037784c13c4b707b0df9e601b101e4cf63a404dfe50f2e1865bb12edc8fca166579ce0c70dba5a5c0fc960ad6f3772183416a00bd29d4c6e651ea7620bb100c9449858bf14e1ddc9ecd35725581ca5b9160de04060045993d972571c3e8f71e9d0496bfa744656861b169d65", + derive_key: "160e18b5878cd0df1c3af85eb25a0db5344d43a6fbd7a8ef4ed98d0714c3f7e160dc0b1f09caa35f2f417b9ef309dfe5ebd67f4c9507995a531374d099cf8ae317542e885ec6f589378864d3ea98716b3bbb65ef4ab5e0ab5bb298a501f19a41ec19af84a5e6b428ecd813b1a47ed91c9657c3fba11c406bc316768b58f6802c9e9b57", + }, + Case { + input_len: 31744, + hash: "62b6960e1a44bcc1eb1a611a8d6235b6b4b78f32e7abc4fb4c6cdcce94895c47860cc51f2b0c28a7b77304bd55fe73af663c02d3f52ea053ba43431ca5bab7bfea2f5e9d7121770d88f70ae9649ea713087d1914f7f312147e247f87eb2d4ffef0ac978bf7b6579d57d533355aa20b8b77b13fd09748728a5cc327a8ec470f4013226f", + keyed_hash: "efa53b389ab67c593dba624d898d0f7353ab99e4ac9d42302ee64cbf9939a4193a7258db2d9cd32a7a3ecfce46144114b15c2fcb68a618a976bd74515d47be08b628be420b5e830fade7c080e351a076fbc38641ad80c736c8a18fe3c66ce12f95c61c2462a9770d60d0f77115bbcd3782b593016a4e728d4c06cee4505cb0c08a42ec", + derive_key: "39772aef80e0ebe60596361e45b061e8f417429d529171b6764468c22928e28e9759adeb797a3fbf771b1bcea30150a020e317982bf0d6e7d14dd9f064bc11025c25f31e81bd78a921db0174f03dd481d30e93fd8e90f8b2fee209f849f2d2a52f31719a490fb0ba7aea1e09814ee912eba111a9fde9d5c274185f7bae8ba85d300a2b", + }, + Case { + input_len: 102400, + hash: "bc3e3d41a1146b069abffad3c0d44860cf664390afce4d9661f7902e7943e085e01c59dab908c04c3342b816941a26d69c2605ebee5ec5291cc55e15b76146e6745f0601156c3596cb75065a9c57f35585a52e1ac70f69131c23d611ce11ee4ab1ec2c009012d236648e77be9295dd0426f29b764d65de58eb7d01dd42248204f45f8e", + keyed_hash: "1c35d1a5811083fd7119f5d5d1ba027b4d01c0c6c49fb6ff2cf75393ea5db4a7f9dbdd3e1d81dcbca3ba241bb18760f207710b751846faaeb9dff8262710999a59b2aa1aca298a032d94eacfadf1aa192418eb54808db23b56e34213266aa08499a16b354f018fc4967d05f8b9d2ad87a7278337be9693fc638a3bfdbe314574ee6fc4", + derive_key: "4652cff7a3f385a6103b5c260fc1593e13c778dbe608efb092fe7ee69df6e9c6d83a3e041bc3a48df2879f4a0a3ed40e7c961c73eff740f3117a0504c2dff4786d44fb17f1549eb0ba585e40ec29bf7732f0b7e286ff8acddc4cb1e23b87ff5d824a986458dcc6a04ac83969b80637562953df51ed1a7e90a7926924d2763778be8560", + }, +]; diff --git a/crypto/factory/Cargo.toml b/crypto/factory/Cargo.toml index 7350cb154..550b1adaa 100644 --- a/crypto/factory/Cargo.toml +++ b/crypto/factory/Cargo.toml @@ -5,6 +5,8 @@ edition.workspace = true [dependencies] bouncycastle-ascon.workspace = true +bouncycastle-blake2.workspace = true +bouncycastle-blake3.workspace = true bouncycastle-core.workspace = true bouncycastle-hmac.workspace = true bouncycastle-sha2.workspace = true diff --git a/crypto/factory/src/hash_factory.rs b/crypto/factory/src/hash_factory.rs index 5c13b3b60..837fdd228 100644 --- a/crypto/factory/src/hash_factory.rs +++ b/crypto/factory/src/hash_factory.rs @@ -30,6 +30,10 @@ use crate::{AlgorithmFactory, FactoryError}; use crate::{DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT}; use bouncycastle_ascon as ascon; use bouncycastle_ascon::ASCON_HASH256_NAME; +use bouncycastle_blake2 as blake2; +use bouncycastle_blake2::{BLAKE2B_512_NAME, BLAKE2S_256_NAME}; +use bouncycastle_blake3 as blake3; +use bouncycastle_blake3::BLAKE3_NAME; use bouncycastle_core::errors::HashError; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{Algorithm, Hash}; @@ -71,6 +75,19 @@ pub enum HashFactory { SM3(sm3::SM3), /// AsconHash256(ascon::AsconHash256), + /// BLAKE2b with a 64-byte digest (RFC 7693), unkeyed. The keyed mode is in + /// [`MACFactory`](crate::mac_factory::MACFactory). + #[allow(non_camel_case_types)] + BLAKE2b_512(blake2::BLAKE2b_512), + /// BLAKE2s with a 32-byte digest (RFC 7693), unkeyed. The keyed mode is in + /// [`MACFactory`](crate::mac_factory::MACFactory). + #[allow(non_camel_case_types)] + BLAKE2s_256(blake2::BLAKE2s_256), + /// BLAKE3 in `hash` mode, with the default 32-byte output. For longer output use + /// [`XOFFactory`](crate::xof_factory::XOFFactory); for the `derive_key` mode, which needs a + /// context string the factory has no channel for, construct + /// `bouncycastle_blake3::BLAKE3::new_derive_key` directly. + BLAKE3(blake3::BLAKE3), } impl Default for HashFactory { @@ -104,6 +121,9 @@ impl AlgorithmFactory for HashFactory { SHA3_512_NAME => Ok(Self::SHA3_512(sha3::SHA3_512::new())), SM3_NAME => Ok(Self::SM3(sm3::SM3::new())), ASCON_HASH256_NAME => Ok(Self::AsconHash256(ascon::AsconHash256::new())), + BLAKE2B_512_NAME => Ok(Self::BLAKE2b_512(blake2::BLAKE2b_512::new())), + BLAKE2S_256_NAME => Ok(Self::BLAKE2s_256(blake2::BLAKE2s_256::new())), + BLAKE3_NAME => Ok(Self::BLAKE3(blake3::BLAKE3::new())), _ => Err(FactoryError::UnsupportedAlgorithm(format!( "The algorithm: \"{}\" is not a known Hash", alg_name @@ -136,6 +156,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.block_bitlen(), Self::SM3(h) => h.block_bitlen(), Self::AsconHash256(h) => h.block_bitlen(), + Self::BLAKE2b_512(h) => h.block_bitlen(), + Self::BLAKE2s_256(h) => h.block_bitlen(), + Self::BLAKE3(h) => h.block_bitlen(), } } @@ -153,6 +176,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.output_len(), Self::SM3(h) => h.output_len(), Self::AsconHash256(h) => h.output_len(), + Self::BLAKE2b_512(h) => h.output_len(), + Self::BLAKE2s_256(h) => h.output_len(), + Self::BLAKE3(h) => h.output_len(), } } @@ -170,6 +196,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.hash(data), Self::SM3(h) => h.hash(data), Self::AsconHash256(h) => h.hash(data), + Self::BLAKE2b_512(h) => h.hash(data), + Self::BLAKE2s_256(h) => h.hash(data), + Self::BLAKE3(h) => h.hash(data), } } @@ -189,6 +218,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.hash_out(data, output), Self::SM3(h) => h.hash_out(data, output), Self::AsconHash256(h) => h.hash_out(data, output), + Self::BLAKE2b_512(h) => h.hash_out(data, output), + Self::BLAKE2s_256(h) => h.hash_out(data, output), + Self::BLAKE3(h) => h.hash_out(data, output), } } @@ -206,6 +238,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.do_update(data), Self::SM3(h) => h.do_update(data), Self::AsconHash256(h) => h.do_update(data), + Self::BLAKE2b_512(h) => h.do_update(data), + Self::BLAKE2s_256(h) => h.do_update(data), + Self::BLAKE3(h) => h.do_update(data), } } @@ -223,6 +258,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.do_final(), Self::SM3(h) => h.do_final(), Self::AsconHash256(h) => h.do_final(), + Self::BLAKE2b_512(h) => h.do_final(), + Self::BLAKE2s_256(h) => h.do_final(), + Self::BLAKE3(h) => h.do_final(), } } @@ -242,6 +280,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.do_final_out(output), Self::SM3(h) => h.do_final_out(output), Self::AsconHash256(h) => h.do_final_out(output), + Self::BLAKE2b_512(h) => h.do_final_out(output), + Self::BLAKE2s_256(h) => h.do_final_out(output), + Self::BLAKE3(h) => h.do_final_out(output), } } @@ -263,6 +304,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), Self::SM3(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), Self::AsconHash256(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), + Self::BLAKE2b_512(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), + Self::BLAKE2s_256(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), + Self::BLAKE3(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), } } @@ -299,6 +343,13 @@ impl Hash for HashFactory { Self::AsconHash256(h) => { h.do_final_partial_bits_out(partial_byte, num_partial_bits, output) } + Self::BLAKE2b_512(h) => { + h.do_final_partial_bits_out(partial_byte, num_partial_bits, output) + } + Self::BLAKE2s_256(h) => { + h.do_final_partial_bits_out(partial_byte, num_partial_bits, output) + } + Self::BLAKE3(h) => h.do_final_partial_bits_out(partial_byte, num_partial_bits, output), } } @@ -316,6 +367,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.max_security_strength(), Self::SM3(h) => h.max_security_strength(), Self::AsconHash256(h) => h.max_security_strength(), + Self::BLAKE2b_512(h) => h.max_security_strength(), + Self::BLAKE2s_256(h) => h.max_security_strength(), + Self::BLAKE3(h) => h.max_security_strength(), } } } diff --git a/crypto/factory/src/mac_factory.rs b/crypto/factory/src/mac_factory.rs index 72957af68..12358d6fd 100644 --- a/crypto/factory/src/mac_factory.rs +++ b/crypto/factory/src/mac_factory.rs @@ -72,6 +72,10 @@ //! ``` use crate::{DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT, FactoryError}; +use bouncycastle_blake2 as blake2; +use bouncycastle_blake2::{BLAKE2B_512_MAC_NAME, BLAKE2S_256_MAC_NAME}; +use bouncycastle_blake3 as blake3; +use bouncycastle_blake3::BLAKE3_KEYED_HASH_NAME; use bouncycastle_core::errors::MACError; use bouncycastle_core::key_material::KeyMaterialTrait; use bouncycastle_core::security_strength::SecurityStrength; @@ -138,6 +142,17 @@ pub enum MACFactory { HMAC_SHA3_512(HmacKey), /// HMAC_SM3(HmacKey), + /// BLAKE2b-512's built-in keyed mode (RFC 7693 s. 3.3), not HMAC-BLAKE2b. The key must be + /// 1 to 64 bytes. + BLAKE2b_512_MAC(blake2::BLAKE2b_512_MACKey), + /// BLAKE2s-256's built-in keyed mode (RFC 7693 s. 3.3), not HMAC-BLAKE2s. The key must be + /// 1 to 32 bytes. + BLAKE2s_256_MAC(blake2::BLAKE2s_256_MACKey), + /// BLAKE3 in `keyed_hash` mode, with the default 32-byte tag. The key must be exactly + /// 32 bytes (BLAKE3 spec s. 2.3, "a 256-bit key"); any other length is refused with + /// [`MACError::InvalidLength`]. For keyed output longer than 32 bytes, construct + /// `bouncycastle_blake3::BLAKE3KeyedHashKey` directly and use its worker's `into_squeezer`. + BLAKE3KeyedHash(blake3::BLAKE3KeyedHashKey), } /// The worker a [`MACFactory`] key hands out: the named algorithm's own worker, borrowing the key @@ -171,6 +186,12 @@ pub enum MACFactoryWorker<'k> { HMAC_SHA3_512(HMAC<'k, sha3::SHA3_512>), /// HMAC_SM3(HMAC<'k, sm3::SM3>), + /// BLAKE2b-512 keyed-mode worker. + BLAKE2b_512_MAC(blake2::BLAKE2b_512_MAC), + /// BLAKE2s-256 keyed-mode worker. + BLAKE2s_256_MAC(blake2::BLAKE2s_256_MAC), + /// BLAKE3 `keyed_hash` worker. + BLAKE3KeyedHash(blake3::BLAKE3KeyedHash), } impl MACFactory { @@ -205,6 +226,15 @@ impl MACFactory { HMAC_SHA3_384_NAME => Ok(Self::HMAC_SHA3_384(HmacKey::from_key_material(key)?)), HMAC_SHA3_512_NAME => Ok(Self::HMAC_SHA3_512(HmacKey::from_key_material(key)?)), HMAC_SM3_NAME => Ok(Self::HMAC_SM3(HmacKey::from_key_material(key)?)), + BLAKE2B_512_MAC_NAME => { + Ok(Self::BLAKE2b_512_MAC(blake2::BLAKE2b_512_MACKey::from_key_material(key)?)) + } + BLAKE2S_256_MAC_NAME => { + Ok(Self::BLAKE2s_256_MAC(blake2::BLAKE2s_256_MACKey::from_key_material(key)?)) + } + BLAKE3_KEYED_HASH_NAME => { + Ok(Self::BLAKE3KeyedHash(blake3::BLAKE3KeyedHashKey::from_key_material(key)?)) + } _ => Err(FactoryError::UnsupportedAlgorithm(format!( "The algorithm: \"{}\" is not a known MAC", alg_name @@ -228,6 +258,9 @@ impl MACFactory { Self::HMAC_SHA3_384(k) => MACFactoryWorker::HMAC_SHA3_384(k.create_generator()), Self::HMAC_SHA3_512(k) => MACFactoryWorker::HMAC_SHA3_512(k.create_generator()), Self::HMAC_SM3(k) => MACFactoryWorker::HMAC_SM3(k.create_generator()), + Self::BLAKE2b_512_MAC(k) => MACFactoryWorker::BLAKE2b_512_MAC(k.create_generator()), + Self::BLAKE2s_256_MAC(k) => MACFactoryWorker::BLAKE2s_256_MAC(k.create_generator()), + Self::BLAKE3KeyedHash(k) => MACFactoryWorker::BLAKE3KeyedHash(k.create_generator()), } } } @@ -271,6 +304,9 @@ macro_rules! forward { MACFactoryWorker::HMAC_SHA3_384($w) => $e, MACFactoryWorker::HMAC_SHA3_512($w) => $e, MACFactoryWorker::HMAC_SM3($w) => $e, + MACFactoryWorker::BLAKE2b_512_MAC($w) => $e, + MACFactoryWorker::BLAKE2s_256_MAC($w) => $e, + MACFactoryWorker::BLAKE3KeyedHash($w) => $e, } }; } diff --git a/crypto/factory/src/xof_factory.rs b/crypto/factory/src/xof_factory.rs index 67fb33baf..3fd917a47 100644 --- a/crypto/factory/src/xof_factory.rs +++ b/crypto/factory/src/xof_factory.rs @@ -38,6 +38,7 @@ use crate::{AlgorithmFactory, FactoryError}; use bouncycastle_ascon::ASCON_XOF128_NAME; use bouncycastle_ascon::AsconXof128; +use bouncycastle_blake3::{BLAKE3, BLAKE3_NAME}; use bouncycastle_core::errors::HashError; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{Algorithm, Hash, XOF, XOFSqueezer}; @@ -63,6 +64,11 @@ pub enum XOFFactory { SHAKE256(sha3::SHAKE256), /// AsconXof128(AsconXof128), + /// BLAKE3 in `hash` mode. Its [`Hash`] view is the default 32-byte output, which is the + /// first 32 bytes of the XOF stream (BLAKE3 spec s. 2.6). For the `derive_key` mode, which + /// needs a context string the factory has no channel for, construct + /// `bouncycastle_blake3::BLAKE3::new_derive_key` directly. + BLAKE3(BLAKE3), } impl Default for XOFFactory { @@ -85,6 +91,7 @@ impl AlgorithmFactory for XOFFactory { SHAKE128_NAME => Ok(Self::SHAKE128(sha3::SHAKE128::new())), SHAKE256_NAME => Ok(Self::SHAKE256(sha3::SHAKE256::new())), ASCON_XOF128_NAME => Ok(Self::AsconXof128(AsconXof128::new())), + BLAKE3_NAME => Ok(Self::BLAKE3(BLAKE3::new())), _ => Err(FactoryError::UnsupportedAlgorithm(format!( "The algorithm: \"{}\" is not a known XOF", alg_name @@ -115,6 +122,9 @@ pub enum XOFFactorySqueezer { /// Ascon-XOF128 output. AsconXof128(::Squeezer), + + /// BLAKE3 output. + BLAKE3(::Squeezer), } impl XOFSqueezer for XOFFactorySqueezer { @@ -123,6 +133,7 @@ impl XOFSqueezer for XOFFactorySqueezer { Self::SHAKE128(o) => o.do_output(num_bytes), Self::SHAKE256(o) => o.do_output(num_bytes), Self::AsconXof128(o) => o.do_output(num_bytes), + Self::BLAKE3(o) => o.do_output(num_bytes), } } @@ -131,6 +142,7 @@ impl XOFSqueezer for XOFFactorySqueezer { Self::SHAKE128(o) => o.do_output_out(output), Self::SHAKE256(o) => o.do_output_out(output), Self::AsconXof128(o) => o.do_output_out(output), + Self::BLAKE3(o) => o.do_output_out(output), } } } @@ -141,6 +153,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.block_bitlen(), Self::SHAKE256(h) => h.block_bitlen(), Self::AsconXof128(h) => h.block_bitlen(), + Self::BLAKE3(h) => h.block_bitlen(), } } @@ -149,6 +162,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.output_len(), Self::SHAKE256(h) => h.output_len(), Self::AsconXof128(h) => h.output_len(), + Self::BLAKE3(h) => h.output_len(), } } @@ -157,6 +171,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.hash(data), Self::SHAKE256(h) => h.hash(data), Self::AsconXof128(h) => h.hash(data), + Self::BLAKE3(h) => h.hash(data), } } @@ -165,6 +180,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.hash_out(data, output), Self::SHAKE256(h) => h.hash_out(data, output), Self::AsconXof128(h) => h.hash_out(data, output), + Self::BLAKE3(h) => h.hash_out(data, output), } } @@ -173,6 +189,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_update(data), Self::SHAKE256(h) => h.do_update(data), Self::AsconXof128(h) => h.do_update(data), + Self::BLAKE3(h) => h.do_update(data), } } @@ -181,6 +198,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_final(), Self::SHAKE256(h) => h.do_final(), Self::AsconXof128(h) => h.do_final(), + Self::BLAKE3(h) => h.do_final(), } } @@ -189,6 +207,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_final_out(output), Self::SHAKE256(h) => h.do_final_out(output), Self::AsconXof128(h) => h.do_final_out(output), + Self::BLAKE3(h) => h.do_final_out(output), } } @@ -201,6 +220,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_final_partial_bits(partial_byte, num_bits), Self::SHAKE256(h) => h.do_final_partial_bits(partial_byte, num_bits), Self::AsconXof128(h) => h.do_final_partial_bits(partial_byte, num_bits), + Self::BLAKE3(h) => h.do_final_partial_bits(partial_byte, num_bits), } } @@ -214,6 +234,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_final_partial_bits_out(partial_byte, num_bits, output), Self::SHAKE256(h) => h.do_final_partial_bits_out(partial_byte, num_bits, output), Self::AsconXof128(h) => h.do_final_partial_bits_out(partial_byte, num_bits, output), + Self::BLAKE3(h) => h.do_final_partial_bits_out(partial_byte, num_bits, output), } } @@ -222,6 +243,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => Hash::max_security_strength(h), Self::SHAKE256(h) => Hash::max_security_strength(h), Self::AsconXof128(h) => Hash::max_security_strength(h), + Self::BLAKE3(h) => Hash::max_security_strength(h), } } } @@ -234,6 +256,7 @@ impl XOF for XOFFactory { Self::SHAKE128(h) => XOFFactorySqueezer::SHAKE128(h.into_squeezer()), Self::SHAKE256(h) => XOFFactorySqueezer::SHAKE256(h.into_squeezer()), Self::AsconXof128(h) => XOFFactorySqueezer::AsconXof128(h.into_squeezer()), + Self::BLAKE3(h) => XOFFactorySqueezer::BLAKE3(h.into_squeezer()), } } @@ -252,6 +275,9 @@ impl XOF for XOFFactory { Self::AsconXof128(h) => XOFFactorySqueezer::AsconXof128( h.into_squeezer_partial_bits(partial_byte, num_bits)?, ), + Self::BLAKE3(h) => { + XOFFactorySqueezer::BLAKE3(h.into_squeezer_partial_bits(partial_byte, num_bits)?) + } }) } @@ -260,6 +286,7 @@ impl XOF for XOFFactory { Self::SHAKE128(h) => h.xof(data, result_len), Self::SHAKE256(h) => h.xof(data, result_len), Self::AsconXof128(h) => h.xof(data, result_len), + Self::BLAKE3(h) => h.xof(data, result_len), } } @@ -270,6 +297,7 @@ impl XOF for XOFFactory { Self::SHAKE128(h) => h.xof_out(data, output), Self::SHAKE256(h) => h.xof_out(data, output), Self::AsconXof128(h) => h.xof_out(data, output), + Self::BLAKE3(h) => h.xof_out(data, output), } } } diff --git a/crypto/factory/tests/hash_factory_tests.rs b/crypto/factory/tests/hash_factory_tests.rs index 14911812d..d03f042c2 100644 --- a/crypto/factory/tests/hash_factory_tests.rs +++ b/crypto/factory/tests/hash_factory_tests.rs @@ -1,10 +1,12 @@ #[cfg(test)] mod hash_factory_tests { + use bouncycastle_core::errors::HashError; + use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{Hash, XOF}; use bouncycastle_core_test_framework::DUMMY_SEED; - use bouncycastle_factory::AlgorithmFactory; use bouncycastle_factory::hash_factory::HashFactory; use bouncycastle_factory::xof_factory::XOFFactory; + use bouncycastle_factory::{AlgorithmFactory, FactoryError}; mod sha3_tests { use super::*; @@ -168,7 +170,6 @@ mod hash_factory_tests { fn ascon_hash_tests() { use bouncycastle_ascon::ASCON_HASH256_NAME; use bouncycastle_ascon::AsconHash256; - use bouncycastle_factory::FactoryError; let direct = AsconHash256::new().hash(&DUMMY_SEED[..512]); @@ -188,6 +189,136 @@ mod hash_factory_tests { )); } + /// Checks every `Hash` pass-through of the `name` variant against `expected`, the digest of + /// `msg`; and the byte-oriented partial-bit policy of the BLAKE crates (0 bits is the + /// whole-byte digest, 1..=7 is `InvalidInput`, more than 7 is `InvalidLength`). + fn check_blake_hash( + name: &str, + msg: &[u8], + expected: &[u8], + block_bitlen: usize, + strength: SecurityStrength, + ) { + let make = || HashFactory::new(name).unwrap(); + + assert_eq!(make().output_len(), expected.len(), "{name}: output_len"); + assert_eq!(make().block_bitlen(), block_bitlen, "{name}: block_bitlen"); + assert_eq!(make().max_security_strength(), strength, "{name}: max_security_strength"); + + assert_eq!(make().hash(msg), expected, "{name}: hash"); + + let mut out = vec![0xffu8; expected.len()]; + assert_eq!(make().hash_out(msg, &mut out), expected.len(), "{name}: hash_out length"); + assert_eq!(out, expected, "{name}: hash_out"); + + let mut h = make(); + msg.chunks(7).for_each(|c| h.do_update(c)); + assert_eq!(h.do_final(), expected, "{name}: do_update then do_final"); + + let mut h = make(); + h.do_update(msg); + let mut out = vec![0xffu8; expected.len()]; + assert_eq!(h.do_final_out(&mut out), expected.len(), "{name}: do_final_out length"); + assert_eq!(out, expected, "{name}: do_final_out"); + + let mut h = make(); + h.do_update(msg); + assert_eq!(h.do_final_partial_bits(0x00, 0).unwrap(), expected, "{name}: 0 bits"); + + let mut h = make(); + h.do_update(msg); + let mut out = vec![0xffu8; expected.len()]; + assert_eq!( + h.do_final_partial_bits_out(0x00, 0, &mut out).unwrap(), + expected.len(), + "{name}: do_final_partial_bits_out length" + ); + assert_eq!(out, expected, "{name}: do_final_partial_bits_out"); + + let mut h = make(); + h.do_update(msg); + assert!( + matches!(h.do_final_partial_bits(0x05, 3), Err(HashError::InvalidInput(_))), + "{name}: BLAKE is byte-oriented" + ); + let mut out = vec![0u8; expected.len()]; + assert!( + matches!( + make().do_final_partial_bits_out(0x05, 3, &mut out), + Err(HashError::InvalidInput(_)) + ), + "{name}: BLAKE is byte-oriented (_out)" + ); + assert!( + matches!(make().do_final_partial_bits(0xff, 8), Err(HashError::InvalidLength(_))), + "{name}: eight partial bits is not a partial byte" + ); + } + + /// BLAKE2b-512, BLAKE2s-256 and BLAKE3 through the factory, by name constant and by string. + /// + /// Expected values are copied from the vectors embedded in the algorithm crates' tests: + /// - BLAKE2b-512 / BLAKE2s-256 of "abc": RFC 7693 Appendix A / B (`RFC_ABC` in + /// crypto/blake2/tests/blake2{b,s}_tests.rs); + /// - BLAKE2b-512 / BLAKE2s-256 of the 255 bytes 0, 1, ..., 254: the official BLAKE2 KAT, + /// blake2-kat.json (the `KAT` entry for length 255 in the same files); + /// - BLAKE3 of the 1025-byte input `i % 251`: the official BLAKE3 test_vectors.json (the + /// first 32 bytes of the `hash` field of the 1025 case in + /// crypto/blake3/tests/vectors/mod.rs). + #[test] + fn blake_hash_tests() { + use bouncycastle_blake2::{BLAKE2B_512_NAME, BLAKE2S_256_NAME}; + use bouncycastle_blake3::BLAKE3_NAME; + use bouncycastle_hex as hex; + + let blake2b_abc = hex::decode("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923").unwrap(); + let blake2b_255 = hex::decode("5b21c5fd8868367612474fa2e70e9cfa2201ffeee8fafab5797ad58fefa17c9b5b107da4a3db6320baaf2c8617d5a51df914ae88da3867c2d41f0cc14fa67928").unwrap(); + let blake2s_abc = + hex::decode("508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982") + .unwrap(); + let blake2s_255 = + hex::decode("f03f5789d3336b80d002d59fdf918bdb775b00956ed5528e86aa994acb38fe2d") + .unwrap(); + let blake3_1025 = + hex::decode("d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444") + .unwrap(); + let blake3_input: Vec = (0..1025).map(|i| (i % 251) as u8).collect(); + + for name in ["BLAKE2b-512", BLAKE2B_512_NAME] { + check_blake_hash(name, b"abc", &blake2b_abc, 1024, SecurityStrength::_256bit); + check_blake_hash( + name, + &DUMMY_SEED[..255], + &blake2b_255, + 1024, + SecurityStrength::_256bit, + ); + } + for name in ["BLAKE2s-256", BLAKE2S_256_NAME] { + check_blake_hash(name, b"abc", &blake2s_abc, 512, SecurityStrength::_128bit); + check_blake_hash( + name, + &DUMMY_SEED[..255], + &blake2s_255, + 512, + SecurityStrength::_128bit, + ); + } + for name in ["BLAKE3", BLAKE3_NAME] { + check_blake_hash(name, &blake3_input, &blake3_1025, 512, SecurityStrength::_128bit); + } + + // Names are case-sensitive, and the MAC names are not hashes. + for name in + ["blake2b-512", "BLAKE2b-256", "BLAKE2b-512-MAC", "blake3", "BLAKE3-KeyedHash"] + { + assert!( + matches!(HashFactory::new(name), Err(FactoryError::UnsupportedAlgorithm(_))), + "{name:?} must not construct a Hash" + ); + } + } + #[test] fn test_defaults() { // All the ways to get "default" diff --git a/crypto/factory/tests/mac_factory_tests.rs b/crypto/factory/tests/mac_factory_tests.rs index 8e3bf0775..04b3b1dbc 100644 --- a/crypto/factory/tests/mac_factory_tests.rs +++ b/crypto/factory/tests/mac_factory_tests.rs @@ -1,8 +1,11 @@ #[cfg(test)] mod hash_factory_tests { - use bouncycastle_core::key_material::{KeyMaterial, KeyType}; + use bouncycastle_core::errors::MACError; + use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; + use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::symmetric_key::{MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::MAC; + use bouncycastle_factory::FactoryError; use bouncycastle_factory::mac_factory::MACFactory; use bouncycastle_hex as hex; @@ -152,6 +155,159 @@ mod hash_factory_tests { // TODO: at least one test for each type } + /// Checks every pass-through of the `name` key and its workers against `expected`, the tag + /// of `msg` under `key`: the key's one-shots, and each `MAC` method on a fresh worker. + fn check_blake_mac( + name: &str, + key: &impl KeyMaterialTrait, + msg: &[u8], + expected: &[u8], + strength: SecurityStrength, + ) { + let mac_key = MACFactory::new(name, key).unwrap(); + let make = || mac_key.create_generator(); + + assert_eq!(make().output_len(), expected.len(), "{name}: output_len"); + assert_eq!(make().max_security_strength(), strength, "{name}: max_security_strength"); + + assert_eq!(make().mac(msg), expected, "{name}: mac"); + assert_eq!(mac_key.mac(msg), expected, "{name}: the key's mac"); + + let mut out = vec![0xffu8; expected.len()]; + assert_eq!(make().mac_out(msg, &mut out).unwrap(), expected.len(), "{name}: mac_out"); + assert_eq!(out, expected, "{name}: mac_out"); + + let mut mac = make(); + msg.chunks(7).for_each(|c| mac.do_update(c)); + assert_eq!(mac.do_final(), expected, "{name}: do_update then do_final"); + + let mut mac = make(); + mac.do_update(msg); + let mut out = vec![0xffu8; expected.len()]; + assert_eq!(mac.do_final_out(&mut out).unwrap(), expected.len(), "{name}: do_final_out"); + assert_eq!(out, expected, "{name}: do_final_out"); + + let mut wrong = expected.to_vec(); + wrong[0] ^= 1; + assert!(make().verify(msg, expected), "{name}: verify"); + assert!(!make().verify(msg, &wrong), "{name}: verify a wrong tag"); + assert!(mac_key.verify(msg, expected), "{name}: the key's verify"); + assert!(!mac_key.verify(msg, &wrong), "{name}: the key's verify, wrong tag"); + let mut mac = mac_key.create_verifier(); + mac.do_update(msg); + assert!(mac.do_verify_final(expected), "{name}: do_verify_final"); + let mut mac = mac_key.create_verifier(); + mac.do_update(msg); + assert!(!mac.do_verify_final(&wrong), "{name}: do_verify_final a wrong tag"); + } + + /// The keyed modes of BLAKE2b-512, BLAKE2s-256 and BLAKE3 through the factory, by name + /// constant and by string. + /// + /// Expected values are copied from the vectors embedded in the algorithm crates' tests: + /// - BLAKE2b-512-MAC / BLAKE2s-256-MAC: the official BLAKE2 KAT, blake2-kat.json, keyed + /// with the bytes 0, 1, ..., kk - 1 (kk = 64 / 32) over the message 0, 1, ..., len - 1 + /// (the `KAT` entries for lengths 3 and 255 in crypto/blake2/tests/blake2{b,s}_tests.rs); + /// - BLAKE3-KeyedHash: the official BLAKE3 test_vectors.json, key + /// "whats the Elvish word for friend", input `i % 251` of 1025 bytes (the first 32 bytes + /// of the `keyed_hash` field of the 1025 case in crypto/blake3/tests/vectors/mod.rs). + #[test] + fn blake_mac_tests() { + use bouncycastle_blake2::{BLAKE2B_512_MAC_NAME, BLAKE2S_256_MAC_NAME}; + use bouncycastle_blake3::BLAKE3_KEYED_HASH_NAME; + use bouncycastle_core_test_framework::DUMMY_SEED; + + let key64 = + KeyMaterial::<64>::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); + let key32 = + KeyMaterial::<32>::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); + let blake3_key = KeyMaterial::<32>::from_bytes_as_type( + b"whats the Elvish word for friend", + KeyType::MACKey, + ) + .unwrap(); + let blake3_input: Vec = (0..1025).map(|i| (i % 251) as u8).collect(); + + for name in ["BLAKE2b-512-MAC", BLAKE2B_512_MAC_NAME] { + check_blake_mac( + name, + &key64, + &DUMMY_SEED[..3], + &hex::decode("33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1").unwrap(), + SecurityStrength::_256bit, + ); + check_blake_mac( + name, + &key64, + &DUMMY_SEED[..255], + &hex::decode("142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461").unwrap(), + SecurityStrength::_256bit, + ); + } + for name in ["BLAKE2s-256-MAC", BLAKE2S_256_MAC_NAME] { + check_blake_mac( + name, + &key32, + &DUMMY_SEED[..3], + &hex::decode( + "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b", + ) + .unwrap(), + SecurityStrength::_128bit, + ); + check_blake_mac( + name, + &key32, + &DUMMY_SEED[..255], + &hex::decode( + "3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd", + ) + .unwrap(), + SecurityStrength::_128bit, + ); + } + for name in ["BLAKE3-KeyedHash", BLAKE3_KEYED_HASH_NAME] { + check_blake_mac( + name, + &blake3_key, + &blake3_input, + &hex::decode( + "357dc55de0c7e382c900fd6e320acc04146be01db6a8ce7210b7189bd664ea69", + ) + .unwrap(), + SecurityStrength::_128bit, + ); + } + + // The factory builds keys with `from_key_material`, so the key objects' checks surface as + // `FactoryError::MACError`: BLAKE3 keyed_hash needs exactly 32 bytes (BLAKE3 spec + // s. 2.3, "a 256-bit key"); BLAKE2s takes at most 32 and BLAKE2b at most 64 (RFC 7693 + // s. 2.1 table, "Key bytes": "0 <= kk <= 64" / "0 <= kk <= 32"). + let key33 = + KeyMaterial::<64>::from_bytes_as_type(&DUMMY_SEED[..33], KeyType::MACKey).unwrap(); + for name in [BLAKE3_KEYED_HASH_NAME, BLAKE2S_256_MAC_NAME] { + assert!( + matches!( + MACFactory::new(name, &key33), + Err(FactoryError::MACError(MACError::InvalidLength(_))) + ), + "{name}: a 33-byte key must be refused" + ); + } + assert!(MACFactory::new(BLAKE2B_512_MAC_NAME, &key33).is_ok()); + + // Names are case-sensitive, and the hash names are not MACs. + for name in ["blake2b-512-mac", "BLAKE2b-512", "BLAKE3", "blake3-keyedhash"] { + assert!( + matches!( + MACFactory::new(name, &key32), + Err(FactoryError::UnsupportedAlgorithm(_)) + ), + "{name:?} must not construct a MAC" + ); + } + } + #[test] fn hmac_sm3_tests() { // RFC4231 Test Case 1 key/message; expected value from `openssl dgst -sm3 -mac HMAC`, diff --git a/crypto/factory/tests/xof_factory_tests.rs b/crypto/factory/tests/xof_factory_tests.rs index d6550547a..c59604e9b 100644 --- a/crypto/factory/tests/xof_factory_tests.rs +++ b/crypto/factory/tests/xof_factory_tests.rs @@ -4,11 +4,13 @@ use bouncycastle_ascon::ASCON_XOF128_NAME; use bouncycastle_ascon::AsconXof128; +use bouncycastle_blake3::{BLAKE3, BLAKE3_NAME}; use bouncycastle_core::errors::HashError; use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; use bouncycastle_core_test_framework::xof::TestFrameworkXOF; use bouncycastle_factory::xof_factory::XOFFactory; use bouncycastle_factory::{AlgorithmFactory, FactoryError}; +use bouncycastle_hex as hex; use bouncycastle_sha3::{SHAKE128, SHAKE128_NAME, SHAKE256, SHAKE256_NAME}; const MSG: &[u8] = b"The quick brown fox jumps over the lazy dog"; @@ -140,6 +142,102 @@ fn ascon_xof128_by_name_matches_the_direct_type() { ); } +/// The official BLAKE3 vector (test_vectors.json, github.com/BLAKE3-team/BLAKE3) for the +/// 1025-byte input `i % 251` -- one full chunk plus one byte, so the root is a parent node: the +/// 131-byte `hash` output, copied from crypto/blake3/tests/vectors/mod.rs. +const BLAKE3_1025_HASH: &str = "d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444f4c4a22b4b399155358a994e52bf255de60035742ec71bd08ac275a1b51cc6bfe332b0ef84b409108cda080e6269ed4b3e2c3f7d722aa4cdc98d16deb554e5627be8f955c98e1d5f9565a9194cad0c4285f93700062d9595adb992ae68ff12800ab67a"; + +/// BLAKE3 by name: the official extended-output vector through every `Hash`, `XOF` and +/// `XOFSqueezer` pass-through, plus BLAKE3's byte-oriented partial-bit policy (which is why it +/// cannot use `check_against`, whose partial-bit checks assume SHAKE's bit-oriented input). +#[test] +fn blake3_by_name_matches_the_official_vector() { + let input: Vec = (0..1025).map(|i| (i % 251) as u8).collect(); + let long = hex::decode(BLAKE3_1025_HASH).unwrap(); + // BLAKE3 spec s. 2.6: "Shorter outputs are prefixes of longer ones." The default-length hash + // is 32 bytes (s. 2.1), so the Hash view is the first 32 bytes of the stream. + let n = 32; + + for (name, ctx) in [(BLAKE3_NAME, "BLAKE3 by constant"), ("BLAKE3", "BLAKE3 by string")] { + let make = || XOFFactory::new(name).unwrap(); + + // metadata + assert_eq!(make().block_bitlen(), 512, "{ctx}: block_bitlen"); + assert_eq!(make().output_len(), n, "{ctx}: output_len"); + assert_eq!( + Hash::max_security_strength(&make()), + Hash::max_security_strength(&BLAKE3::new()), + "{ctx}: max_security_strength" + ); + + // the Hash view + assert_eq!(make().hash(&input), &long[..n], "{ctx}: hash"); + let mut out = vec![0xFFu8; n]; + assert_eq!(make().hash_out(&input, &mut out), n, "{ctx}: hash_out returns the length"); + assert_eq!(out, &long[..n], "{ctx}: hash_out"); + + let mut f = make(); + input.chunks(100).for_each(|c| f.do_update(c)); + assert_eq!(f.do_final(), &long[..n], "{ctx}: do_update then do_final"); + + let mut f = make(); + f.do_update(&input); + let mut out = vec![0xFFu8; n]; + assert_eq!(f.do_final_out(&mut out), n, "{ctx}: do_final_out returns the length"); + assert_eq!(out, &long[..n], "{ctx}: do_final_out"); + + // byte-oriented: 0 partial bits is the whole-byte digest, 1..=7 refused, > 7 refused + let mut f = make(); + f.do_update(&input); + assert_eq!(f.do_final_partial_bits(0, 0).unwrap(), &long[..n], "{ctx}: 0 bits"); + + let mut f = make(); + f.do_update(&input); + let mut out = vec![0xFFu8; n]; + assert_eq!(f.do_final_partial_bits_out(0, 0, &mut out).unwrap(), n, "{ctx}: ..._out"); + assert_eq!(out, &long[..n], "{ctx}: do_final_partial_bits_out"); + + assert!(matches!(make().do_final_partial_bits(0x05, 3), Err(HashError::InvalidInput(_)))); + let mut out = vec![0u8; n]; + assert!(matches!( + make().do_final_partial_bits_out(0x05, 3, &mut out), + Err(HashError::InvalidInput(_)) + )); + assert!(matches!(make().do_final_partial_bits(0xFF, 8), Err(HashError::InvalidLength(_)))); + + // the XOF view: one continuous stream across reads + let mut f = make(); + f.do_update(&input); + let mut fo = f.into_squeezer(); + assert_eq!(fo.do_output(n), &long[..n], "{ctx}: do_output"); + let mut buf = vec![0u8; long.len() - n]; + assert_eq!(fo.do_output_out(&mut buf), long.len() - n, "{ctx}: do_output_out length"); + assert_eq!(buf, &long[n..], "{ctx}: do_output_out continues the stream"); + + let mut f = make(); + f.do_update(&input); + assert_eq!( + f.into_squeezer_partial_bits(0, 0).unwrap().do_output(long.len()), + long, + "{ctx}: into_squeezer_partial_bits(0, 0)" + ); + assert!(matches!( + make().into_squeezer_partial_bits(0x05, 3), + Err(HashError::InvalidInput(_)) + )); + assert!(matches!( + make().into_squeezer_partial_bits(0xFF, 8), + Err(HashError::InvalidLength(_)) + )); + + // the one-shots + assert_eq!(make().xof(&input, long.len()), long, "{ctx}: xof"); + let mut out = vec![0xFFu8; long.len()]; + assert_eq!(make().xof_out(&input, &mut out), long.len(), "{ctx}: xof_out length"); + assert_eq!(out, long, "{ctx}: xof_out"); + } +} + /// The configured defaults: SHAKE128 for the general and 128-bit defaults, SHAKE256 for 256-bit. #[test] fn defaults() { @@ -150,7 +248,9 @@ fn defaults() { #[test] fn unknown_names_are_refused() { - for name in ["SHAKE512", "shake128", "", "cSHAKE128", "Ascon-XOF999"] { + for name in + ["SHAKE512", "shake128", "", "cSHAKE128", "Ascon-XOF999", "blake3", "BLAKE3-KeyedHash"] + { assert!( matches!(XOFFactory::new(name), Err(FactoryError::UnsupportedAlgorithm(_))), "{name:?} must not construct a XOF" @@ -174,4 +274,13 @@ fn test_framework_xof() { MSG, &SHAKE256::new().xof(MSG, 100), ); + + // BLAKE3 is byte-oriented, so the suite's partial-byte checks do not apply to it. + let byte_oriented = + TestFrameworkXOF { enable_partial_byte_tests: false, do_final_binds_output_length: false }; + byte_oriented.test_xof( + || XOFFactory::new(BLAKE3_NAME).unwrap(), + MSG, + &BLAKE3::new().xof(MSG, 100), + ); } diff --git a/mem_usage_benches/Cargo.toml b/mem_usage_benches/Cargo.toml index fb55fb094..9411b576a 100644 --- a/mem_usage_benches/Cargo.toml +++ b/mem_usage_benches/Cargo.toml @@ -30,3 +30,11 @@ path = "src/bench_ccm_mem_usage.rs" [[bin]] name = "bench_ascon_mem_usage" path = "src/bench_ascon_mem_usage.rs" + +[[bin]] +name = "bench_blake2_mem_usage" +path = "src/bench_blake2_mem_usage.rs" + +[[bin]] +name = "bench_blake3_mem_usage" +path = "src/bench_blake3_mem_usage.rs" diff --git a/mem_usage_benches/src/bench_blake2_mem_usage.rs b/mem_usage_benches/src/bench_blake2_mem_usage.rs new file mode 100644 index 000000000..03faf1bca --- /dev/null +++ b/mem_usage_benches/src/bench_blake2_mem_usage.rs @@ -0,0 +1,170 @@ +//! The purpose of this binary is to perform a single run of the primitive under test so that +//! its peak memory usage can be measured with: +//! +//! ```text +//! valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_blake2_mem_usage > /dev/null +//! +//! ms_print massif.out.* +//! ``` +//! +//! or, shoved all into one line: +//! +//! ```text +//! clear; clear; valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_blake2_mem_usage > /dev/null; ms_print massif.out.*; rm massif.out.* +//! ``` +//! +//! Make sure you build in release mode! +//! +//! The code is using print!() to force the compiler not to optimize away the actual code. +//! It is printing important outputs for benchmarking to stderr so that the rest can be mapped to /dev/null +//! (this is because /usr/bin/time prints useful outputs to stderr as well) +//! +//! Main is at the bottom, controls which this was actually run. `print_struct_sizes()` is the source of +//! the numbers in the "Memory Usage" table in the bouncycastle-blake2 crate docs. + +#![allow(dead_code)] +#![allow(unused_imports)] + +use bouncycastle::blake2::{ + BLAKE2b_512, BLAKE2b_512_MAC, BLAKE2b_512_MACKey, BLAKE2s_256, BLAKE2s_256_MAC, + BLAKE2s_256_MACKey, SUSPENDED_BLAKE2B_512_MAC_STATE_LEN, SUSPENDED_BLAKE2B_512_STATE_LEN, + SUSPENDED_BLAKE2S_256_MAC_STATE_LEN, SUSPENDED_BLAKE2S_256_STATE_LEN, +}; +use bouncycastle::core::key_material::{KeyMaterial256, KeyMaterial512, KeyType}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle::core::traits::{Hash, MAC, Suspendable, SuspendableKeyed}; + +/// A 1 KiB message: 8 BLAKE2b blocks, 16 BLAKE2s blocks. +const MSG: [u8; 1024] = [0xA5; 1024]; + +/// This prints the in-memory size of all the hash / MAC objects and the suspended states. +fn print_struct_sizes() { + use core::mem::size_of; + + println!("\nBLAKE2"); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("SUSPENDED_BLAKE2B_512_STATE_LEN: {}", SUSPENDED_BLAKE2B_512_STATE_LEN); + println!("SUSPENDED_BLAKE2B_512_MAC_STATE_LEN: {}", SUSPENDED_BLAKE2B_512_MAC_STATE_LEN); + println!("SUSPENDED_BLAKE2S_256_STATE_LEN: {}", SUSPENDED_BLAKE2S_256_STATE_LEN); + println!("SUSPENDED_BLAKE2S_256_MAC_STATE_LEN: {}", SUSPENDED_BLAKE2S_256_MAC_STATE_LEN); +} + +fn bench_do_nothing() { + eprintln!("DoNothing"); + + print!("{}", 1 + 1); +} + +fn bench_blake2b_512_hash() { + eprintln!("BLAKE2b-512/hash"); + + let mut out = [0u8; 64]; + BLAKE2b_512::new().hash_out(&MSG, &mut out); + println!("{:x?}", out); +} + +fn bench_blake2s_256_hash() { + eprintln!("BLAKE2s-256/hash"); + + let mut out = [0u8; 32]; + BLAKE2s_256::new().hash_out(&MSG, &mut out); + println!("{:x?}", out); +} + +fn bench_blake2b_512_streaming() { + eprintln!("BLAKE2b-512/do_update+do_final_out"); + + let mut h = BLAKE2b_512::new(); + for chunk in MSG.chunks(100) { + h.do_update(chunk); + } + let mut out = [0u8; 64]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake2s_256_streaming() { + eprintln!("BLAKE2s-256/do_update+do_final_out"); + + let mut h = BLAKE2s_256::new(); + for chunk in MSG.chunks(100) { + h.do_update(chunk); + } + let mut out = [0u8; 32]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake2b_512_mac() { + eprintln!("BLAKE2b-512-MAC/mac_out"); + + let key = KeyMaterial512::from_bytes_as_type(&[0x0Bu8; 64], KeyType::MACKey) + .expect("a 64-byte MAC key"); + let mac_key = + BLAKE2b_512_MACKey::from_key_material_allow_weak(&key).expect("a 64-byte MAC key"); + let mut out = [0u8; 64]; + mac_key.create_generator().mac_out(&MSG, &mut out).expect("a 64-byte output buffer"); + println!("{:x?}", out); +} + +fn bench_blake2s_256_mac() { + eprintln!("BLAKE2s-256-MAC/mac_out"); + + let key = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey) + .expect("a 32-byte MAC key"); + let mac_key = + BLAKE2s_256_MACKey::from_key_material_allow_weak(&key).expect("a 32-byte MAC key"); + let mut out = [0u8; 32]; + mac_key.create_generator().mac_out(&MSG, &mut out).expect("a 32-byte output buffer"); + println!("{:x?}", out); +} + +fn bench_blake2b_512_suspend_resume() { + eprintln!("BLAKE2b-512/suspend+from_suspended"); + + let mut h = BLAKE2b_512::new(); + h.do_update(&MSG[..500]); + let state = h.suspend(); + let mut h = + BLAKE2b_512::from_suspended(state).expect("round-trip of a freshly suspended state"); + h.do_update(&MSG[500..]); + let mut out = [0u8; 64]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake2s_256_mac_suspend_resume() { + eprintln!("BLAKE2s-256-MAC/suspend+from_suspended"); + + let key = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey) + .expect("a 32-byte MAC key"); + let mac_key = + BLAKE2s_256_MACKey::from_key_material_allow_weak(&key).expect("a 32-byte MAC key"); + let mut mac = mac_key.create_generator(); + mac.do_update(&MSG[..500]); + let state = mac.suspend(); + let mut mac = BLAKE2s_256_MAC::from_suspended(state, &mac_key) + .expect("round-trip of a freshly suspended state"); + mac.do_update(&MSG[500..]); + let mut out = [0u8; 32]; + mac.do_final_out(&mut out).expect("a 32-byte output buffer"); + println!("{:x?}", out); +} + +fn main() { + print_struct_sizes() + // bench_do_nothing() + // bench_blake2b_512_hash() + // bench_blake2s_256_hash() + // bench_blake2b_512_streaming() + // bench_blake2s_256_streaming() + // bench_blake2b_512_mac() + // bench_blake2s_256_mac() + // bench_blake2b_512_suspend_resume() + // bench_blake2s_256_mac_suspend_resume() +} diff --git a/mem_usage_benches/src/bench_blake3_mem_usage.rs b/mem_usage_benches/src/bench_blake3_mem_usage.rs new file mode 100644 index 000000000..f2673bfed --- /dev/null +++ b/mem_usage_benches/src/bench_blake3_mem_usage.rs @@ -0,0 +1,166 @@ +//! The purpose of this binary is to perform a single run of the primitive under test so that +//! its peak memory usage can be measured with: +//! +//! ```text +//! valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_blake3_mem_usage > /dev/null +//! +//! ms_print massif.out.* +//! ``` +//! +//! or, shoved all into one line: +//! +//! ```text +//! clear; clear; valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_blake3_mem_usage > /dev/null; ms_print massif.out.*; rm massif.out.* +//! ``` +//! +//! Make sure you build in release mode! +//! +//! The code is using print!() to force the compiler not to optimize away the actual code. +//! It is printing important outputs for benchmarking to stderr so that the rest can be mapped to /dev/null +//! (this is because /usr/bin/time prints useful outputs to stderr as well) +//! +//! Main is at the bottom, controls which this was actually run. `print_struct_sizes()` is the source of +//! the numbers in the "Memory Usage" table in the bouncycastle-blake3 crate docs. + +#![allow(dead_code)] +#![allow(unused_imports)] + +use bouncycastle::blake3::{ + BLAKE3, BLAKE3KeyedHash, BLAKE3KeyedHashKey, BLAKE3KeyedSqueezer, BLAKE3Squeezer, + SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN, SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN, + SUSPENDED_BLAKE3_STATE_LEN, +}; +use bouncycastle::core::key_material::{KeyMaterial256, KeyType}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle::core::traits::{Hash, MAC, Suspendable, XOF, XOFSqueezer}; + +/// A 4 KiB message: four 1 KiB chunks, so the CV stack is pushed and merged (BLAKE3 spec s. 5.1.2). +const MSG: [u8; 4096] = [0xA5; 4096]; + +/// This prints the in-memory size of all the hasher / squeezer objects and the suspended states. +fn print_struct_sizes() { + use core::mem::size_of; + + println!("\nBLAKE3"); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("SUSPENDED_BLAKE3_STATE_LEN: {}", SUSPENDED_BLAKE3_STATE_LEN); + println!("SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN: {}", SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN); + println!("SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN: {}", SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN); +} + +fn bench_do_nothing() { + eprintln!("DoNothing"); + + print!("{}", 1 + 1); +} + +fn bench_blake3_hash() { + eprintln!("BLAKE3/hash"); + + let mut out = [0u8; 32]; + BLAKE3::new().hash_out(&MSG, &mut out); + println!("{:x?}", out); +} + +fn bench_blake3_streaming() { + eprintln!("BLAKE3/do_update+do_final_out"); + + let mut h = BLAKE3::new(); + for chunk in MSG.chunks(100) { + h.do_update(chunk); + } + let mut out = [0u8; 32]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake3_xof() { + eprintln!("BLAKE3/absorb+squeeze_out"); + + let mut x = BLAKE3::new(); + x.do_update(&MSG); + let mut out = [0u8; 512]; + let mut x = x.into_squeezer(); + x.do_output_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake3_keyed_hash() { + eprintln!("BLAKE3-KeyedHash/mac_out"); + + let key = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey) + .expect("a 32-byte MAC key"); + let mac_key = + BLAKE3KeyedHashKey::from_key_material_allow_weak(&key).expect("a 32-byte MAC key"); + let mut out = [0u8; 32]; + mac_key.create_generator().mac_out(&MSG, &mut out).expect("a 32-byte output buffer"); + println!("{:x?}", out); +} + +fn bench_blake3_keyed_hash_xof() { + eprintln!("BLAKE3-KeyedHash/absorb+into_squeezer+squeeze_out"); + + let key = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey) + .expect("a 32-byte MAC key"); + let mac_key = + BLAKE3KeyedHashKey::from_key_material_allow_weak(&key).expect("a 32-byte MAC key"); + let mut mac = mac_key.create_generator(); + mac.do_update(&MSG); + let mut out = [0u8; 512]; + mac.into_squeezer().do_output_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake3_derive_key() { + eprintln!("BLAKE3/new_derive_key+hash"); + + let mut out = [0u8; 32]; + BLAKE3::new_derive_key(b"bc-rust 2026-10-22 mem_usage_benches example context") + .hash_out(&MSG[..32], &mut out); + println!("{:x?}", out); +} + +fn bench_blake3_suspend_resume() { + eprintln!("BLAKE3/suspend+from_suspended"); + + let mut h = BLAKE3::new(); + h.do_update(&MSG[..2500]); + let state = h.suspend(); + let mut h = BLAKE3::from_suspended(state).expect("round-trip of a freshly suspended state"); + h.do_update(&MSG[2500..]); + let mut out = [0u8; 32]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake3_squeezer_suspend_resume() { + eprintln!("BLAKE3Squeezer/suspend+from_suspended"); + + let mut h = BLAKE3::new(); + h.do_update(&MSG); + let mut s = h.into_squeezer(); + let mut out = [0u8; 100]; + s.do_output_out(&mut out); + let state = s.suspend(); + let mut s = + BLAKE3Squeezer::from_suspended(state).expect("round-trip of a freshly suspended state"); + s.do_output_out(&mut out); + println!("{:x?}", out); +} + +fn main() { + print_struct_sizes() + // bench_do_nothing() + // bench_blake3_hash() + // bench_blake3_streaming() + // bench_blake3_xof() + // bench_blake3_keyed_hash() + // bench_blake3_keyed_hash_xof() + // bench_blake3_derive_key() + // bench_blake3_suspend_resume() + // bench_blake3_squeezer_suspend_resume() +} diff --git a/mem_usage_benches/src/lib.rs b/mem_usage_benches/src/lib.rs index 8c3f55926..b29171e87 100644 --- a/mem_usage_benches/src/lib.rs +++ b/mem_usage_benches/src/lib.rs @@ -1,5 +1,7 @@ mod bench_aes_mem_usage; mod bench_ascon_mem_usage; +mod bench_blake2_mem_usage; +mod bench_blake3_mem_usage; mod bench_ccm_mem_usage; mod bench_mldsa_mem_usage; mod bench_mlkem_mem_usage; diff --git a/src/lib.rs b/src/lib.rs index ae250f332..edcdbf3ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ pub use bouncycastle_aes as aes; pub use bouncycastle_ascon as ascon; pub use bouncycastle_base64 as base64; +pub use bouncycastle_blake2 as blake2; +pub use bouncycastle_blake3 as blake3; pub use bouncycastle_cipher as cipher; pub use bouncycastle_core as core; pub use bouncycastle_factory as factory;