Skip to content

Commit 1a99289

Browse files
authored
Add a Memory Usage section to SHA2/SHA3/HashDRBG docs (#51)
* Added a Memory Usage section to the crate docs for SHA2, SHA3, and HashDRBG * Added a Memory Usage section to the crate docs for SHA2, SHA3, and HashDRBG
1 parent bbfaab3 commit 1a99289

9 files changed

Lines changed: 465 additions & 26 deletions

File tree

alpha_0.1.2_release_notes.md

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,12 @@
66
* Check the crate release checklist and run claude against the style guide (maybe Francis could cross-check me)
77
* Run Crucible testing
88
* Add factories for ML-DSA and ML-KEM (if we are keeping factories, see below)
9-
* After merging the Signer/Verifier, Encrypter/Decrypter split, check if the keygen_from_rng() is still on the right
10-
trait.
11-
* Split the Signature trait into a Signer and a Verifier so that, for example, we can implement the verifier for MTC in
12-
a different struct from the signer; or so that you can get FIPS compliance on old algorithms that are currently only
13-
FIPS-allowed for verification of existing signatures but not for creation of new ones.
14-
* Check out Megan's email May 13 about KeyMaterial: "I was wondering if there might be scope for a closure based
15-
approach that could guarantee encapsulation of the state change from safe to hazardous back to safe again."
16-
* Go back to previous algs and apply memory optimization tricks like internal functions. And add a docs section "Memory
17-
Usage" that measures with valgrind.
189
* Ensure that all crates have `#![forbid(missing_docs)]`
1910
* Apply Secret trait consistently across the library --> study the `Zeroize` trait in RustCrypto
20-
* Change all "[u8;0]" to "[]" throughout the code and docs ... or better yet, change the APIs to take an Option<>
21-
* Change all `-> Vec<u8>` to `-> [u8; CONST_LEN]`, and the `output: &mut [u8]` to `output: &mut [u8; CONST_LEN]` where
22-
appropriate.
23-
* Probably it makes sense to leave Hex and Base64 as requiring std; ... or maybe add a no_std version that uses
24-
fixed-sized blocks?
25-
* Make this build on the stable compiler. IE Remove the rust-toolchain.toml file that builds with nightly. Will require
26-
some refactoring.
27-
* Create a cargo feature #[cfg(feature='rng')] and put it around things like keygen that takes an rng so that the build
28-
dependency on bouncycastle_rng is optional.
29-
* Factories ... Are they worth it? Michael Richardson says Very Yes. If we are keeping them, then we need a serious
30-
re-engineering of them because I really dislike that currently they make it hard for the underlying primitive to have
31-
static one-shot APIs.
3211
* Deal with as many of the inline TODOs as possible
3312
* Close all open github issues and document them in this file.
3413
* After everything is merged, circle back to crucible, and make sure that the harness still works (and maybe remove the
3514
nightly build toolchain)
36-
* Search for all the uses of .unwrap() in non-test code and replace each with either a comment or an expect with a
37-
meaningful error string.
3815

3916
# 0.1.2 Features / Changelog
4017

@@ -46,7 +23,7 @@
4623
* All public `*_out(.., out: &mut [u8])` functions now begin by zeroizing the entire output buffer with `.fill(0)`,
4724
preventing exposure of stale data in oversized output buffers or on early error returns.
4825
* Reworked the way KeyMaterial hazardous operations work; instead of a stateful .allow_hazardous_operations() /
49-
.drop_hazardous_operations(), it now uses a closure-based do_hazardous_operations(). Github issue #39.
26+
.drop_hazardous_operations(), it now uses a closure-based do_hazardous_operations(). Github issue #39.me
5027
* Renamed KeyMaterial::KeyType's and deleted KeyMaterial::concatenate in order to give a better intuition and
5128
FIPS-alignment.
5229
* Github issues resolved:

crypto/rng/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@
2626
//! This crate contains the [Sp80090ADrbg] trait, which is intentionally defined here and not in [bouncycastle_core::traits]
2727
//! since misuse of [Sp80090ADrbg::instantiate] can completely undermine the security of your entire
2828
//! cryptographic application.
29+
//!
30+
//! # Memory Footprint
31+
//!
32+
//! The following table lists the size of the struct in memory when using the stateful API between multiple calls to `.do_update()`.
33+
//!
34+
//! | Key Object | Struct size in memory (bytes) |
35+
//! |------------|-------------------------------|
36+
//! | HashDRBG_SHA256 | 144 |
37+
//! | HashDRBG_SHA512 | 144 |
2938
3039
#![forbid(unsafe_code)]
3140

crypto/sha2/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@
3333
//!
3434
//! let output: Vec<u8> = sha2.do_final();
3535
//! ```
36+
//!
37+
//! # Memory Footprint
38+
//!
39+
//! The following table lists the size of the struct in memory when using the stateful API between multiple calls to `.do_update()`.
40+
//!
41+
//! | Key Object | Struct size in memory (bytes) |
42+
//! |------------|-------------------------------|
43+
//! | SHA224 | 112 |
44+
//! | SHA256 | 112 |
45+
//! | SHA384 | 208 |
46+
//! | SHA512 | 208 |
3647
3748
#![forbid(unsafe_code)]
3849
#![allow(private_bounds)]

crypto/sha3/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,19 @@
103103
//! This would also be the case even if the input had type
104104
//! [KeyType::CryptographicRandom] since the input [KeyMaterial] is 16 bytes but [SHA3_256] needs at least 32 bytes of
105105
//! full-entropy input key material in order to be able to produce full entropy output key material.
106+
//!
107+
//! # Memory Footprint
108+
//!
109+
//! The following table lists the size of the struct in memory when using the stateful API between multiple calls to `.do_update()`.
110+
//!
111+
//! | Key Object | Struct size in memory (bytes) |
112+
//! |------------|-------------------------------|
113+
//! | SHA3_224 | 440 |
114+
//! | SHA3_256 | 440 |
115+
//! | SHA3_384 | 440 |
116+
//! | SHA3_512 | 440 |
117+
//! | SHAKE128 | 440 |
118+
//! | SHAKE256 | 440 |
106119
107120
#![forbid(unsafe_code)]
108121
#![allow(private_bounds)]

mem_usage_benches/Cargo.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,16 @@ path = "bench_mldsa_mem_usage.rs"
1313

1414
[[bin]]
1515
name = "bench_mlkem_mem_usage"
16-
path = "bench_mlkem_mem_usage.rs"
16+
path = "bench_mlkem_mem_usage.rs"
17+
18+
[[bin]]
19+
name = "bench_sha2_mem_usage"
20+
path = "bench_sha2_mem_usage.rs"
21+
22+
[[bin]]
23+
name = "bench_sha3_mem_usage"
24+
path = "bench_sha3_mem_usage.rs"
25+
26+
[[bin]]
27+
name = "bench_hashdrbg_mem_usage"
28+
path = "bench_hashdrbg_mem_usage.rs"
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//! The purpose of this binary is to perform a single run of the primitive under test so that
2+
//! its peak memory usage can be measured with:
3+
//!
4+
//! valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_hashdrbg_mem_usage > /dev/null
5+
//!
6+
//! ms_print massif.out.835000
7+
//!
8+
//! or, shoved all into one line:
9+
//!
10+
//! clear; clear; valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_hashdrbg_mem_usage > /dev/null; ms_print massif.out.*; rm massif.out.*
11+
//!
12+
//! Make sure you build in release mode!
13+
//!
14+
//! Note: I'm using print!() to force the compiler not to optimize away the actual code.
15+
//! I'm printing the important stuff for benchmarking to stderr so that I can pipe the junk to /dev/null
16+
//! (I'm not doing it the other way because /usr/bin/time prints its useful stuff to stderr as well)
17+
//!
18+
//! This exercises the HashDRBG (SP 800-90A) implementation from the `rng` crate. We seed from
19+
//! fixed dummy entropy (NOT from the OS) so the run is deterministic and reproducible under massif.
20+
//!
21+
//! Main is at the bottom, controls which this was actually run.
22+
23+
#![allow(dead_code)]
24+
#![allow(unused_imports)]
25+
26+
use bouncycastle::core::key_material::{KeyMaterial0, KeyMaterial256, KeyMaterial512, KeyType};
27+
use bouncycastle::core::traits::{RNG, SecurityStrength};
28+
use bouncycastle::rng::{HashDRBG_SHA256, HashDRBG_SHA512, Sp80090ADrbg};
29+
30+
/// Fixed dummy entropy for seeding. HashDRBG-SHA256 needs 32 bytes (128-bit strength);
31+
/// HashDRBG-SHA512 needs 64 bytes (256-bit strength). We take the prefix we need.
32+
const DUMMY_SEED: [u8; 64] = [
33+
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
34+
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
35+
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
36+
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
37+
];
38+
39+
/// This exists so I can use /usr/bin/time to measure the base memory footprint of the harness.
40+
fn bench_do_nothing() {
41+
eprintln!("DoNothing");
42+
43+
print!("{}", 1 + 1);
44+
}
45+
46+
/// This prints the in-memory size of the DRBG state structs.
47+
fn print_struct_sizes() {
48+
use core::mem::size_of;
49+
50+
println!("size_of<HashDRBG_SHA256>: {}", size_of::<HashDRBG_SHA256>());
51+
println!("size_of<HashDRBG_SHA512>: {}", size_of::<HashDRBG_SHA512>());
52+
}
53+
54+
fn bench_hashdrbg_sha256_generate() {
55+
eprintln!("HashDRBG-SHA256/Generate");
56+
57+
let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap();
58+
let mut rng = HashDRBG_SHA256::new_unititialized();
59+
rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit).unwrap();
60+
61+
let mut out = [0u8; 32];
62+
rng.generate_out(&[], &mut out).unwrap();
63+
print!("{:x?}", out);
64+
}
65+
66+
fn bench_hashdrbg_sha512_generate() {
67+
eprintln!("HashDRBG-SHA512/Generate");
68+
69+
let seed = KeyMaterial512::from_bytes_as_type(&DUMMY_SEED, KeyType::Seed).unwrap();
70+
let mut rng = HashDRBG_SHA512::new_unititialized();
71+
rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_256bit).unwrap();
72+
73+
let mut out = [0u8; 32];
74+
rng.generate_out(&[], &mut out).unwrap();
75+
print!("{:x?}", out);
76+
}
77+
78+
fn main() {
79+
// bench_do_nothing()
80+
print_struct_sizes()
81+
// bench_hashdrbg_sha256_generate()
82+
// bench_hashdrbg_sha512_generate()
83+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
//! The purpose of this binary is to perform a single run of the primitive under test so that
2+
//! its peak memory usage can be measured with:
3+
//!
4+
//! valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_sha2_mem_usage > /dev/null
5+
//!
6+
//! ms_print massif.out.835000
7+
//!
8+
//! or, shoved all into one line:
9+
//!
10+
//! clear; clear; valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_sha2_mem_usage > /dev/null; ms_print massif.out.*; rm massif.out.*
11+
//!
12+
//! Make sure you build in release mode!
13+
//!
14+
//! Note: I'm using print!() to force the compiler not to optimize away the actual code.
15+
//! I'm printing the important stuff for benchmarking to stderr so that I can pipe the junk to /dev/null
16+
//! (I'm not doing it the other way because /usr/bin/time prints its useful stuff to stderr as well)
17+
//!
18+
//! Main is at the bottom, controls which this was actually run.
19+
20+
#![allow(dead_code)]
21+
#![allow(unused_imports)]
22+
23+
use bouncycastle::core::traits::Hash;
24+
use bouncycastle::sha2::{SHA224, SHA256, SHA384, SHA512};
25+
26+
/// A fixed input larger than one block for every SHA-2 variant, so a single hash call
27+
/// exercises multiple compression rounds.
28+
const INPUT: [u8; 1024] = [0xAB; 1024];
29+
30+
/// Number of `do_update` calls in the streaming benches (total absorbed = 16 KiB).
31+
const STREAMING_ITERS: usize = 16;
32+
33+
/// This exists so I can use /usr/bin/time to measure the base memory footprint of the harness.
34+
fn bench_do_nothing() {
35+
eprintln!("DoNothing");
36+
37+
print!("{}", 1 + 1);
38+
}
39+
40+
/// This prints the in-memory size of all the SHA-2 state structs.
41+
fn print_struct_sizes() {
42+
use core::mem::size_of;
43+
44+
println!("size_of<SHA224>: {}", size_of::<SHA224>());
45+
println!("size_of<SHA256>: {}", size_of::<SHA256>());
46+
println!("size_of<SHA384>: {}", size_of::<SHA384>());
47+
println!("size_of<SHA512>: {}", size_of::<SHA512>());
48+
}
49+
50+
fn bench_sha224_oneshot() {
51+
eprintln!("SHA224/OneShot");
52+
53+
let mut digest = [0u8; 28];
54+
SHA224::new().hash_out(&INPUT, &mut digest);
55+
print!("{:x?}", digest);
56+
}
57+
58+
fn bench_sha224_streaming() {
59+
eprintln!("SHA224/Streaming");
60+
61+
let mut digest = [0u8; 28];
62+
let mut md = SHA224::new();
63+
for _ in 0..STREAMING_ITERS {
64+
md.do_update(&INPUT);
65+
}
66+
md.do_final_out(&mut digest);
67+
print!("{:x?}", digest);
68+
}
69+
70+
fn bench_sha256_oneshot() {
71+
eprintln!("SHA256/OneShot");
72+
73+
let mut digest = [0u8; 32];
74+
SHA256::new().hash_out(&INPUT, &mut digest);
75+
print!("{:x?}", digest);
76+
}
77+
78+
fn bench_sha256_streaming() {
79+
eprintln!("SHA256/Streaming");
80+
81+
let mut digest = [0u8; 32];
82+
let mut md = SHA256::new();
83+
for _ in 0..STREAMING_ITERS {
84+
md.do_update(&INPUT);
85+
}
86+
md.do_final_out(&mut digest);
87+
print!("{:x?}", digest);
88+
}
89+
90+
fn bench_sha384_oneshot() {
91+
eprintln!("SHA384/OneShot");
92+
93+
let mut digest = [0u8; 48];
94+
SHA384::new().hash_out(&INPUT, &mut digest);
95+
print!("{:x?}", digest);
96+
}
97+
98+
fn bench_sha384_streaming() {
99+
eprintln!("SHA384/Streaming");
100+
101+
let mut digest = [0u8; 48];
102+
let mut md = SHA384::new();
103+
for _ in 0..STREAMING_ITERS {
104+
md.do_update(&INPUT);
105+
}
106+
md.do_final_out(&mut digest);
107+
print!("{:x?}", digest);
108+
}
109+
110+
fn bench_sha512_oneshot() {
111+
eprintln!("SHA512/OneShot");
112+
113+
let mut digest = [0u8; 64];
114+
SHA512::new().hash_out(&INPUT, &mut digest);
115+
print!("{:x?}", digest);
116+
}
117+
118+
fn bench_sha512_streaming() {
119+
eprintln!("SHA512/Streaming");
120+
121+
let mut digest = [0u8; 64];
122+
let mut md = SHA512::new();
123+
for _ in 0..STREAMING_ITERS {
124+
md.do_update(&INPUT);
125+
}
126+
md.do_final_out(&mut digest);
127+
print!("{:x?}", digest);
128+
}
129+
130+
fn main() {
131+
// print_struct_sizes()
132+
// bench_do_nothing()
133+
// bench_sha224_oneshot()
134+
// bench_sha224_streaming()
135+
// bench_sha256_oneshot()
136+
// bench_sha256_streaming()
137+
// bench_sha384_oneshot()
138+
// bench_sha384_streaming()
139+
bench_sha512_oneshot()
140+
// bench_sha512_streaming()
141+
}

0 commit comments

Comments
 (0)