Skip to main content

core.security::hash — SHA-2 family

The SHA-2 family of cryptographic hash functions, per FIPS PUB 180-4. Three variants ship:

VariantBlock sizeOutput sizeTypical use
SHA-25664 B32 BTLS 1.3 HKDF default, most signatures, checksums
SHA-384128 B48 BTLS_AES_256_GCM_SHA384 suite, ML-DSA-65
SHA-512128 B64 BEd25519 internal hash, HKDF-SHA512, wide-output variants

All three use the same compression-function core (64-bit words for SHA-384/512, 32-bit words for SHA-256), natively constant-time, with a @cfg(feature = "crypto-accel") substitution point that binds to SHA-NI (x86_64) or ARMv8 SHA-2 Crypto Extensions.

API surface

Type + constructors

mount core.security.hash.sha256.{Sha256, BLOCK_SIZE, OUTPUT_SIZE};
mount core.security.hash.sha384.{Sha384};
mount core.security.hash.sha512.{Sha512};

// Streaming state
let mut s: Sha256 = Sha256.new();

// One-shot
let digest: [Byte; 32] = Sha256.digest(data);

Streaming operations

impl Sha256 {
public fn new() -> Sha256;
public fn update(&mut self, data: &[Byte]);
public fn finalize(self) -> [Byte; 32];
public fn digest(data: &[Byte]) -> [Byte; 32]; // one-shot
}

// Sha384 — identical shape, `finalize` returns [Byte; 48]
// Sha512 — identical shape, `finalize` returns [Byte; 64]

Size constants

core.security.hash.sha256.BLOCK_SIZE // 64
core.security.hash.sha256.OUTPUT_SIZE // 32

core.security.hash.sha384.BLOCK_SIZE // 128 (inherited from SHA-512)
core.security.hash.sha384.OUTPUT_SIZE // 48

core.security.hash.sha512.BLOCK_SIZE // 128
core.security.hash.sha512.OUTPUT_SIZE // 64

Low-level compression primitive (SHA-512)

Exposed for SHA-384, SHA-512/256, and any other SHA-512-based scheme that shares the compression function:

public fn compress_block(
h: &mut [UInt64; 8],
block: &[Byte; 128],
);

Basic usage

One-shot hash

mount core.security.hash.sha256.{Sha256};

fn example_one_shot() {
let data = b"The quick brown fox jumps over the lazy dog";
let digest: [Byte; 32] = Sha256.digest(data);
// digest == d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
}

Streaming (for large inputs)

fn hash_file<R: Read>(reader: &mut R) -> Result<[Byte; 32], IoError> {
let mut s = Sha256.new();
let mut buf: [Byte; 8192] = [0; 8192];
loop {
let n = reader.read(&mut buf)?;
if n == 0 { break; }
s.update(&buf[..n]);
}
Ok(s.finalize())
}

SHA-512 for wider outputs

mount core.security.hash.sha512.{Sha512};

fn pbkdf2_wide_key(password: &[Byte], salt: &[Byte]) -> [Byte; 64] {
// Suppose you want a 512-bit key for deriving two 256-bit sub-keys.
Sha512.digest(&concat(password, salt))
}

Algorithm details

SHA-256 (FIPS 180-4 §6.2)

  • Processes 512-bit (64-byte) blocks.
  • 64 rounds per block.
  • Eight 32-bit working registers (H[0..8]).
  • Big-endian serialisation for the final digest.
  • Initial H values — first 32 bits of fractional parts of square roots of the first 8 primes (2..19).
  • Round constants K[0..64] — first 32 bits of fractional parts of cube roots of the first 64 primes.

SHA-512 (FIPS 180-4 §6.4)

  • Processes 1024-bit (128-byte) blocks.
  • 80 rounds per block.
  • Eight 64-bit working registers.
  • 128-bit length field in the padding (carry-propagation across length_hi + length_lo in the stream state).

SHA-384 (FIPS 180-4 §5.3.4)

Identical compression to SHA-512, differing only in:

  • The initial H values (FIPS §5.3.4, taken from fractional parts of square roots of primes 23..53).
  • The final output is truncated to H[0..6] (384 bits = 48 bytes).

The sha384.vr implementation therefore consists of ~180 lines — most of it stream-boilerplate identical in shape to SHA-512 — and reuses sha512::compress_block directly.

Test vectors

All three variants pass the FIPS 180-4 Appendix example vectors and the NIST CAVP short-message KAT.

InputSHA-256 output (hex)
"" (empty)e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
"abc"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1
InputSHA-384 output (hex)
""38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b
"abc"cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7
InputSHA-512 output (hex)
""cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
"abc"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f

VCS discharge: vcs/specs/L1-core/security/sha2_kat.vr (shape) and CAVP-driven runs under L1-core/security/run/ (byte-exact).

Performance

Reference throughput (pure Verum, no acceleration):

PlatformSHA-256SHA-384/512
x86_64 (Zen 4, 4.5 GHz)~300 MB/s~200 MB/s
ARM Neoverse N1~200 MB/s~150 MB/s

Accelerated (@cfg(feature = "crypto-accel")):

PlatformSHA-256SHA-384/512
x86_64 with SHA-NI~3 GB/s— (no HW accel in SHA-NI)
x86_64 with AVX-512~2.5 GB/s~2 GB/s
ARMv8 FEAT_SHA2~4 GB/s~2 GB/s

Use streaming update for inputs that don't fit comfortably in memory; the one-shot digest convenience allocates zero extra buffers beyond the input slice.

Side-channel notes

  • Timing safety. The reference implementation is branch-free over input bytes: the round loop has a fixed iteration count, and no table lookups are indexed by secret data (all bit-ops and fixed-index H[] / K[] accesses).

  • Length-extension. SHA-256 and SHA-512 are Merkle-Damgård hashes and thus vulnerable to length-extension attacks if used directly as a keyed MAC (hash(key || message)). This is NOT a defect of the hash — it's a protocol error. For authentication, always use core.security.mac.hmac which is designed to resist length-extension.

  • Collision resistance. SHA-256 is still cryptographically collision-resistant; public pre-image and second-preimage attacks remain out of reach. SHA-1 is deprecated for signatures but remains available in core.security.hash.sha1 for protocols that require it (Git SHA-1, legacy HMAC-SHA1 in TOTP, etc.).

Relation to other modules

File layout

FileRole
core/security/hash/sha1.vrSHA-1 (200 LOC, legacy only)
core/security/hash/sha256.vrSHA-256 (340 LOC)
core/security/hash/sha384.vrSHA-384 (180 LOC, shares sha512 core)
core/security/hash/sha512.vrSHA-512 (330 LOC, exposes compress_block)
core/security/hash/crc32.vrCRC-32 / IEEE 802.3 — non-cryptographic checksum

crc32 — non-cryptographic checksum

Packaged alongside the SHA-2 family because it is a byte-oriented digest, but this is not a cryptographic hash — adversaries can trivially reproduce any target checksum. Use SHA-256 or HMAC when the integrity check must be tamper-resistant.

mount core.security.hash.crc32.{Crc32, crc32, crc32_continue};

// one-shot
let digest: UInt32 = crc32(b"hello world");

// streaming
let mut h = Crc32.new();
h.update(chunk1);
h.update(chunk2);
let digest: UInt32 = h.finalize();

Standard IEEE 802.3 / RFC 1952 (gzip) polynomial 0xEDB88320 with reflected input, reflected output, pre-XOR 0xFFFFFFFF, and final XOR 0xFFFFFFFF. Matches zlib's crc32, the crc32 CLI, Python's binascii.crc32, and Rust's crc32fast::hash. Used internally by core.database.sqlite.native for WAL frame checksums.

crc32c — Castagnoli polynomial

mount core.security.hash.crc32c.{Crc32c, crc32c};
let digest: UInt32 = crc32c(b"123456789"); // 0xE3069283 (RFC 3720)

Reflected polynomial 0x82F63B78 (Castagnoli). Better error- detection properties than CRC-32/IEEE over 27..127-bit payloads (Koopman & Chakravarty 2004). Used by SCTP (RFC 4960 §6.8), iSCSI data digest (RFC 7143 §10.4.3), ext4/btrfs/f2fs/XFS filesystems, Google Cloud Storage object checksums. SSE4.2 / ARMv8 CRC32C hardware instructions can accelerate ~10× behind a follow-up intrinsic.

xxhash — XXH64 fast non-crypto hash

mount core.security.hash.xxhash.{XxHash64, xxh64};

let h: UInt64 = xxh64(data, seed);

let mut hasher = XxHash64.new(seed);
hasher.update(chunk1);
hasher.update(chunk2);
let h: UInt64 = hasher.finalize();

Yann Collet's XXH64. The non-crypto speed champion: ~3 GB/s in pure Verum (4-lane parallel accumulator for inputs ≥ 32 bytes; seed + PRIME64_5 for smaller inputs; final 3-round avalanche). Streaming output always equals one-shot — the update / finalize path buffers tails identically.

Used by Facebook RocksDB blob-file integrity, LZ4 frame-format content checksums, ClickHouse analytics column hashing, Redis rax, Ceph. Not cryptographic — adversarial inputs trivially collide.

murmur3 — MurmurHash3 (32-bit + 128-bit)

mount core.security.hash.murmur3.{murmur3_32, murmur3_128, Murmur3Hash128};

let h32: UInt32 = murmur3_32(key_bytes, seed);

let h128: Murmur3Hash128 = murmur3_128(key_bytes, seed);
// { high: UInt64, low: UInt64 }

Austin Appleby 2011. Wire-compatible with Cassandra Murmur3Partitioner, Guava Hashing.murmur3_*, Apache Spark partitioning, Apache Commons Codec, Rust fastmurmur3. Two variants — 32-bit for hash tables, 128-bit (x64 variant) when birthday collisions at scale matter.

Same "not cryptographic" caveat as XXH64 — partition keys, BloomFilter fingerprints, dedup fingerprints only. Use SHA-256 or HMAC against untrusted peers.

References