libvctrl_sha512
Minimal-dependency cryptographic primitives: SHA-512, HMAC-SHA512, HKDF-SHA512, and optional
SHA-384. A pure-Rust, auditable cryptography crate that serves as the content-addressing and
message-authentication backbone for the libvctrl workspace while remaining usable as a
standalone crypto library.
- Crate:
libvctrl_sha5123.2.0 (library,no_std-compatible by default) - Language: Rust, edition 2024 — MSRV 1.96.0 (declared via
rust-version) - License: ISC (distinct from the MIT license used by the rest of the workspace)
- Repository: https://github.com/mroczect/libvctrl
- Documentation: https://docs.rs/libvctrl_sha512
The crate has exactly one external dependency:
zeroize(withdefault-features = false), used to guarantee zeroization of sensitive intermediate state in ano_std-compatible manner. The core hash, HMAC, and HKDF types use fixed-size arrays and are allocation-free. The crate isno_stdwhen built outside of test targets (#![cfg_attr(not(test), no_std)]).
Overview
libvctrl_sha512 provides four cryptographic primitives in a single, dependency-light
crate:
- SHA-512 — the FIPS 180-4 hash function used for content addressing.
- HMAC-SHA512 — keyed message authentication per RFC 2104.
- HKDF-SHA512 — key derivation per RFC 5869.
- SHA-384 (optional) — the FIPS 180-4 truncated variant of SHA-512, plus its HMAC-SHA-384 and HKDF-SHA-384 companions.
The implementation prioritises auditability (minimal dependencies, readable code),
security (constant-time verification, zeroization of intermediate state via zeroize), and
performance (aggressive inlining with an optional size-optimisation feature). The HMAC and
HKDF types are generated by exported macros, so downstream crates can instantiate them with
other hash functions.
Architecture
The crate is organised as a thin layer over the SHA-512 core. The sha512 module is the
foundation; hmac and hkdf are generated from it by macros; sha384 wraps the SHA-512
core with a different initialisation vector and truncates the output; utils provides
shared byte-order and constant-time comparison helpers.
flowchart TD
LIB["lib.rs<br/>crate root, macro exports, re-exports"]
LIB --> SHA512["sha512<br/>SHA-512 (FIPS 180-4)<br/>Hash struct"]
LIB --> HMAC["hmac<br/>HMAC-SHA512 (RFC 2104)<br/>via impl_hmac! macro"]
LIB --> HKDF["hkdf<br/>HKDF-SHA512 (RFC 5869)<br/>via impl_hkdf! macro"]
LIB --> UTILS["utils<br/>load_be / store_be / verify<br/>BLOCKBYTES / BYTES"]
LIB --> SHA384["sha384 (feature-gated)<br/>SHA-384 + HMAC-SHA-384 + HKDF-SHA-384"]
LIB --> ZEROIZE["zeroize<br/>Zeroize trait / Zeroizing wrapper"]
HMAC -.instantiated from.-> SHA512
HKDF -.delegates to.-> HMAC
SHA384 -.wraps.-> SHA512
SHA384 -.instantiates.-> HMAC
SHA384 -.instantiates.-> HKDF
HMAC -.uses.-> UTILS
SHA512 -.uses.-> UTILS
SHA512 -.uses.-> ZEROIZE
HMAC -.uses.-> ZEROIZE
SHA384 -.uses.-> ZEROIZE
HMAC-SHA512 construction (RFC 2104)
HMAC normalises the key to the 128-byte block size, then computes the standard
inner/outer hash sandwich. A notable implementation detail: finalize transforms the
in-place ipad buffer into opad by XOR with 0x6a (since 0x36 ^ 0x5c == 0x6a),
avoiding a second key copy. The HMAC struct implements Drop to zeroize the inner
hasher and the padded key buffer.
flowchart LR
KEY[Secret key K] --> PREP[prepare_key<br/>hash if len > 128, else pad to 128]
PREP --> KPAD["K' block-sized (128 bytes)"]
KPAD --> IPAD["XOR with 0x36 (ipad)"]
IPAD --> INNER[feed inner Hash with ipad]
MSG[Message m] --> INNER
INNER --> INNERD["inner digest H(ipad || m)"]
KPAD --> OPAD["XOR with 0x5c (opad)<br/>via in-place 0x6a transform"]
OPAD --> OUTER[feed outer Hash with opad]
INNERD --> OUTER
OUTER --> TAG["Tag H(opad || H(ipad || m))<br/>64 bytes"]
TAG --> DROP[Drop: zeroize key + buffers]
SHA-512 incremental pipeline
The Hash struct maintains eight 64-bit working variables, a 128-byte block buffer, a
buffered-byte counter, and a u128 total length. Input is buffered until a full 128-byte
block is available, at which point the block is processed through the message schedule and
80-round compression function.
flowchart LR
IN[Input bytes] --> UPD[update_inner<br/>buffer into 128-byte block]
UPD -->|full block| BLK[State.blocks<br/>process complete blocks]
UPD -->|partial| BUF[keep in buffer]
BLK --> SCH["W::new -> 16 words<br/>W::expand -> 80-word schedule"]
SCH --> COMP["g x5 -> 80 rounds<br/>Ch / Maj / Sigma / sigma + constants"]
COMP --> ADD[State.add<br/>Merkle-Damgard feedback]
BUF --> FIN[finalize<br/>pad 0x80 + 128-bit length]
FIN --> OUT["64-byte digest<br/>big-endian state"]
Core Features
- Minimal dependencies. Only
zeroizewith default features disabled; the entire hash core is pure Rust overcore. - SHA-512 (FIPS 180-4). Merkle–Damgård construction, 128-byte block, 64-byte output, 80 round constants. Incremental and one-shot APIs.
- HMAC-SHA512 (RFC 2104). One-shot and incremental APIs, key normalisation, in-place
ipad/opad transform, and
Drop-based zeroization of key material. - HKDF-SHA512 (RFC 5869). Extract-and-expand key derivation with enforced output length limits.
- Optional SHA-384. Wraps the SHA-512 core with a different IV and truncates to 48 bytes; also generates HMAC-SHA-384 and HKDF-SHA-384.
- Constant-time verification.
verifyaccumulates XOR differences across all bytes and usescore::hint::black_boxto inhibit compiler short-circuiting; a WebAssembly target receives an additional hash-based mask. - Guaranteed zeroization.
HashandHMACtypes implementzeroize::Zeroize, and sensitive intermediate arrays are wrapped inzeroize::Zeroizingso that the compiler cannot elide the clearing writes. - Exported macros.
impl_hmac!andimpl_hkdf!are#[macro_export], allowing downstream crates to instantiate HMAC and HKDF with their own hash structs. - Size optimisation. The
opt_sizefeature shrinks the binary by de-inlining the compression round functions, for embedded, WebAssembly, and minimal-CLI targets. no_stdcompatible. The crate compiles without the Rust standard library when not compiling tests; onlycoreandallocare used.
Technology Stack
- Language: Rust (edition 2024, MSRV 1.96.0 — explicitly declared)
- Dependencies:
zeroize1.8 (default-features = false); otherwise none. - Dev-dependencies:
criterion0.8 (default-features = false, withcargo_bench_support) for benchmarks. - Lint policy: workspace-inherited, with local allowances for
clippy::indexing_slicingandclippy::arithmetic_side_effectsin the performance-critical crypto paths where bounds are guaranteed by invariants. - Features:
default = ["sha384"],sha384,opt_size.
Project Structure
libvctrl_sha512/
├── Cargo.toml
├── LICENSE
├── README.md
├── benches/
│ ├── sha512_bench.rs
│ └── sha384_bench.rs
└── src/
├── lib.rs
├── sha512.rs
├── hmac.rs
├── hkdf.rs
├── sha384.rs
└── utils.rs
The published package includes src/**/*, Cargo.toml, README.md, and LICENSE
(per the include field); benchmarks remain in the repository for local use.
Getting Started
Prerequisites
- Rust toolchain 1.96.0 or newer (edition 2024 is required)
- Cargo
No system libraries or external services are required.
Installation
For most libvctrl workspace users, depend on the facade, which wires this crate with
default-features = false and re-exports it under the crypto namespace:
[]
= "2.2"
To depend on libvctrl_sha512 directly for standalone crypto use:
[]
= "3.2"
Or via Cargo:
Configuration
| Use case | Configuration |
|---|---|
| Default (SHA-512 + SHA-384) | default (includes sha384) |
| SHA-512 only | default-features = false |
| Size-optimised, full crypto | features = ["opt_size"] |
| Size-optimised, SHA-512 only | default-features = false, features = ["opt_size"] |
# Default: SHA-512 + SHA-384
= "3.2"
# Minimal: SHA-512 only
= { = "3.2", = false }
# Size-optimised, full crypto
= { = "3.2", = ["opt_size"] }
# Size-optimised, SHA-512 only
= { = "3.2", = false, = ["opt_size"] }
sha384(default): enables thesha384module, exposingsha384::Hash,sha384::HMAC, andsha384::HKDF(SHA-384 plus HMAC-SHA-384 and HKDF-SHA-384). Disable it to reduce compile time and code size when only SHA-512 is needed.opt_size: switches the SHA-512 compression round functions from#[inline(always)]to#[inline(never)]. The result is smaller code size at the cost of slower hashing. Intended for embedded, WebAssembly, and minimal-CLI targets. It is purely a code-size optimisation and does not affectno_stdstatus.
Usage
SHA-512 one-shot and incremental
use Hash;
// One-shot
let digest = hash;
assert_eq!;
// Incremental
let mut hasher = new;
hasher.update;
hasher.update;
assert_eq!;
SHA-512 constant-time verification
use Hash;
let expected = hash;
let mut hasher = new;
hasher.update;
assert!;
HMAC-SHA512 authentication
use HMAC;
// One-shot computation
let tag = HMACmac;
assert_eq!;
// Constant-time verification
let key = b"secret-key";
let tag = HMACmac;
assert!;
HKDF-SHA512 key derivation
use HKDF;
let prk = HKDFextract;
let mut okm = ;
HKDFexpand;
assert_eq!;
SHA-384 (requires the sha384 feature)
use Hash as Sha384;
let digest = hash;
assert_eq!;
Instantiating the macros for a custom hash (downstream crates)
The impl_hmac! and impl_hkdf! macros are exported so other crates can build HMAC and
HKDF on top of their own hash struct:
// Conceptual: assumes your crate provides a `MyHash` type with the same
// API surface as libvctrl_sha512::sha512::Hash (new / update / finalize /
// hash / zeroize), plus an output size and block size.
// impl_hmac!(MyHash, OUTPUT_SIZE, BLOCK_SIZE);
// impl_hkdf!(MyHash, OUTPUT_SIZE, BLOCK_SIZE);
API Reference / Core Modules
Full API documentation is published at https://docs.rs/libvctrl_sha512. The crate root
hosts the SHA-512 family; SHA-384 types live under the sha384 module.
Root re-exports (SHA-512 family)
| Item | Type | Description |
|---|---|---|
Hash |
struct | SHA-512 hasher (new, update, finalize, hash, verify, zeroize). |
HMAC |
struct | HMAC-SHA512 (mac, new, update, finalize, finalize_verify, verify). |
HKDF |
struct | HKDF-SHA512 (extract, expand). |
BYTES |
const | SHA-512 output size in bytes (64). |
BLOCKBYTES |
const | SHA-512 block size in bytes (128). |
BYTESandBLOCKBYTESrefer to SHA-512 sizes. SHA-384 shares the 128-byte block size but produces a 48-byte output.
sha512 — SHA-512 (FIPS 180-4)
Hash— the incremental hasher. Holds eight 64-bit working variables, a 128-byte block buffer, a buffered-byte counter, and au128total length.Cloneso that HMAC/HKDF can fork intermediate state.finalizeapplies standard padding (0x80, zero fill, 128-bit big-endian length) and returns the 64-byte digest.verifyfinalises and compares in constant time.zeroizeoverwrites state, buffer, and length using thezeroizecrate.Dropalso zeroizes all sensitive state.- Internal types
W(message schedule) andState(eightu64working variables) implement theCh/Maj/Σ0/Σ1/σ0/σ1logical functions, the 80-word expansion, and the 80-round compression function with the standard round constants.
hmac — HMAC-SHA512 (RFC 2104)
HMAC— generated byimpl_hmac!(Hash, 64, 128).prepare_keynormalises the key to the 128-byte block size (hashing it if too long). The inner hash is seeded withipad XOR key(0x36);finalizetransforms the buffer in place toopad XOR key(0x5c) via XOR0x6a, then computes the outer hash.Dropzeroizes the inner hasher and the padded key buffer usingzeroize.
hkdf — HKDF-SHA512 (RFC 5869)
HKDF— generated byimpl_hkdf!(Hash, 64, 128). A zero-sized type.extract(salt, ikm)returns a 64-byte PRK (HMAC with the salt as key).expand(out, prk, info)fills the output buffer with OKM of arbitrary length, enforcing the RFC 5869 limit (out.len() <= 255 * output_size) and requiring the PRK to be exactlyoutput_sizebytes.
sha384 — SHA-384, HMAC-SHA-384, HKDF-SHA-384 (feature-gated)
Available only when the sha384 feature is enabled.
sha384::Hash— wraps the SHA-512 core with the SHA-384 initialisation vector and truncates the 64-byte digest to 48 bytes. Same incremental API as SHA-512.sha384::HMAC— HMAC-SHA-384, generated byimpl_hmac!(Hash, 48, 128).sha384::HKDF— HKDF-SHA-384, generated byimpl_hkdf!(Hash, 48, 128).
utils — Shared helpers
| Item | Kind | Description |
|---|---|---|
load_be |
fn | Loads a u64 from a byte slice at an offset in big-endian order. |
store_be |
fn | Stores a u64 into a byte slice at an offset in big-endian order. |
verify |
fn | Constant-time comparison of two byte slices; black_box-guarded. |
BLOCKBYTES |
const | 128 — SHA-512 block size. |
BYTES |
const | 64 — SHA-512 output size. |
Exported macros
| Macro | Purpose |
|---|---|
impl_hmac! |
Generates an HMAC type for a given hash struct, output size, and block size. |
impl_hkdf! |
Generates an HKDF type for a given hash struct, output size, and block size. |
Both are #[macro_export] and use $crate for path stability when invoked from
external crates.
Testing
The crate ships with known-answer tests for HMAC and HKDF (RFC test vectors) plus SHA-512 and SHA-384 digest vectors. Run the test suite with:
To run only the SHA-512 or SHA-384 tests:
Benchmarks
Benchmarks use criterion and live under benches/. Run them with:
The sha384_bench benchmark requires the sha384 feature. With default features it runs
automatically; if you have disabled default features, enable it explicitly:
To run a single benchmark:
Contributing
Contributions are welcome. The crate enforces #![forbid(unsafe_code)] and inherits the
workspace lint policy. All public items must be documented.
For contribution guidelines, code style, and the full lint configuration, see the
repository's CONTRIBUTING.md and the workspace root README.md:
- Repository: https://github.com/mroczect/libvctrl
When contributing, preserve the minimal-dependency invariant: no new external crates may
be added without strong justification. New primitives should be implemented over core
APIs only, and zeroization should continue to use the zeroize crate.
Ecosystem
libvctrl_sha512 is the crypto engine of the libvctrl workspace. The related crates
are listed below; each has its own documentation.
| Crate | Role | Documentation |
|---|---|---|
libvctrl |
Facade: re-exports contracts, reference impl, and crypto | https://docs.rs/libvctrl |
libvctrl_handler |
Contract layer: traits, types, limits, validation | https://docs.rs/libvctrl_handler |
libvctrl_core |
Reference implementations (codec, builders, stores, hasher) | https://docs.rs/libvctrl_core |
libvctrl_plumbing |
Command-level VCS operations built on libvctrl_core |
https://docs.rs/libvctrl_plumbing |
libvctrl_porcelain |
High-level, user-facing VCS operations | https://docs.rs/libvctrl_porcelain |
The facade wires this crate with default-features = false and re-exports it under the
crypto namespace, so most workspace users never need to depend on libvctrl_sha512
directly.
License
Licensed under the ISC License. This differs from the rest of the libvctrl
workspace, which is MIT-licensed; the ISC license is a short, permissive license commonly
used for security-focused code.
Copyright (c) 2026, mroczect <mroczect@proton.me>. The full text is in the LICENSE
file of the repository. The substantive terms of the ISC License are:
ISC License
Copyright (c) 2020-2026, Frank Denis.
Copyright (c) 2026, mroczect
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.