G
YouTube TikTok Games
Skip to content
KronuzPublic

About

SHA-256 for C++20: one call or streaming, hex or raw digest, on the CPU's SHA instructions when it has them

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

sha256

Part of Kronuz C++ Libraries, a collection of small, reusable C++ libraries.

CI License: MIT C++20

SHA-256 for C++20: one call or streaming, hex or raw digest, on the CPU's SHA instructions when it has them. Blocks run on SHA-NI on x86_64 and the SHA2 extension on arm64, on AVX2 on x86_64 cores without SHA-NI, picked once at run time, and on portable C++ otherwise. Reach for it for content addressing, deduplication and integrity checks without linking OpenSSL.

✨ Highlights

  • One call or streaming. kronuz::sha256::digest("abc"), or update() in chunks and final(); the SHA256 class (add(), getHash()) keeps Xapiand's interface.
  • Hardware speed, one binary. The SHA-NI, AVX2 and ARMv8 kernels are detected at first use. On the CI runners below they run within about 1% of OpenSSL EVP on bulk data (6% behind with GCC on Neoverse-N2) and ahead of it on 64-byte messages.
  • No clash with OpenSSL. The namespaced API compiles next to <openssl/sha.h>, whose global SHA256() hides the class of the same name.
  • Hex or raw. 64 lowercase hex characters, or 32 bytes into your buffer.
  • Any input. Size-based, so embedded NULs count; buffers at any alignment are read safely.
  • Repeatable reads. getHash() finalizes a copy, so you can keep adding.
  • Checked for every kernel the host has: NIST vectors, every split point, random inputs against the portable kernel, unaligned offsets under ASan and UBSan, and a libFuzzer target.

πŸš€ Quick start

#include "sha256.h"

#include <cstdio>

int main() {
	namespace sha = kronuz::sha256;
	std::puts(sha::to_hex(sha::digest("abc")).c_str());
	// ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

	sha::hasher h;
	h.update("hello, ");
	h.update("world");
	std::puts(sha::to_hex(h.final()).c_str());  // the same as hashing "hello, world" at once
	// 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b

	std::puts(sha::kernel_name(sha::active_kernel()));  // "sha-ni", "avx2", "armv8" or "portable"

	SHA256 sha256;  // the Xapiand-compatible class, same digests
	std::puts(sha256(std::string_view("abc")).c_str());
	// ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
}

examples/demo.cc shows the avalanche from a one-byte change.

πŸ“¦ Installation

Requires a C++20 compiler and CMake 3.14 or newer. CMake fetches endian at a pinned commit; the library compiles three source files, and -DSHA256_HARDWARE=OFF builds the portable kernel alone. CI builds and tests it on Linux (x86_64 with GCC and Clang, arm64), macOS (arm64, x86_64) and FreeBSD, plus ASan + UBSan and TSan builds.

include(FetchContent)
FetchContent_Declare(sha256
  GIT_REPOSITORY https://github.com/Kronuz/sha256.git
  GIT_TAG        eb46cb6240e63bfae515bab4ee91784d6176528b)  # or main to track the tip
FetchContent_MakeAvailable(sha256)

target_link_libraries(my_app PRIVATE sha256::sha256)
#include "sha256.h"

🎯 When to use it

Fingerprints of content (cache keys, deduplication, checksums of stored data), and bulk hashing of segments or files without linking OpenSSL on CPUs with SHA instructions.

Not for:

  • Passwords, signatures, or MACs: use a reviewed cryptography library, with HMAC or a password hash. This implementation is not hardened against side channels.
  • Bulk hashing on x86_64 without SHA-NI (Intel cores before Ice Lake; every AMD Zen has it): the AVX2 kernel is 1.6x the portable one there, but OpenSSL's assembly is still about 1.3x faster.

⚑ Performance

GB/s (1e9 bytes/s) to hash one message on one thread, from sha256_bench: the best of five passes in a Release build. sha256 is this library on the kernel its CPU selects: armv8 (the arm64 SHA2 instructions), sha-ni (the x86-64 SHA extensions) or avx2 (x86-64 without SHA-NI). Portable is this library's plain C++ rounds, forced with force_kernel, and the only kernel on a CPU with none of them. OpenSSL EVP is EVP_DigestInit_ex, EVP_DigestUpdate and EVP_DigestFinal_ex on a reused context from the OpenSSL named in the first column. The bench checks that all three return the same digests. A 64 B message is one cache-hot digest, padding included; 64 MiB is one digest of a large buffer. The last column is sha256's throughput over EVP's, so above 1 is faster than OpenSSL. The M4 Pro and i9-9980HK rows are laptop runs on main; the other rows are GitHub's shared runners from the bench jobs of CI run 41331989169, whose kernels are the ones in this tree, so a few percent is noise.

Machine, compiler, OpenSSL Kernel Message sha256 Portable OpenSSL EVP sha256 Γ· EVP
Apple M4 Pro, Apple clang 17, 3.5.9 armv8 64 B 1.783 0.246 1.519 1.17
64 MiB 2.980 0.506 3.012 0.99
Intel Core i9-9980HK (no SHA-NI), Apple clang 17, 3.6.5 avx2 64 B 0.132 0.098 0.158 0.84
64 MiB 0.352 0.216 0.463 0.76
AMD EPYC 7763, GCC 13.3, 3.0.13 sha-ni 64 B 0.531 0.114 0.417 1.27
64 MiB 1.586 0.258 1.587 1.00
Neoverse-N2, Clang 18.1, 3.0.13 armv8 64 B 1.082 0.194 0.464 2.33
64 MiB 2.167 0.408 2.166 1.00
Intel Core i7-8700B (no SHA-NI), Apple clang 17, 3.6.3 portable 64 B 0.082 0.082 0.131 0.63
64 MiB 0.175 0.175 0.360 0.49

On SHA-NI and ARMv8 the hardware kernel is 5.3x to 9.5x faster than the portable one on 64 MiB, within about 1% of EVP on bulk data (4.5% ahead on the M1 runner, 6% behind with GCC on Neoverse-N2) and 1.2x to 2.3x faster on 64-byte messages, where EVP's per-call overhead dominates. A CPU without SHA instructions runs AVX2 on x86_64 (1.3x to 1.6x the portable kernel, still 1.2x to 1.4x behind EVP) or the portable kernel, which EVP's vectorized assembly beats by 1.6x to 2.2x. The i7-8700B runner row predates the AVX2 kernel. docs/perf/ has 4 KiB messages and the other runners.

cmake -B build -DCMAKE_BUILD_TYPE=Release -DSHA256_BUILD_BENCHMARKS=ON
cmake --build build && ./build/sha256_bench

With several OpenSSLs installed (Homebrew's openssl@3 and openssl@4), set OPENSSL_ROOT_DIR so the headers and the library come from the same one; the bench prints the version of the library that runs. SHA256 on a 16-byte input, hex string included, takes 62 ns on the M4 Pro (sha256_hex_bench, built by the same option).

πŸ“š Documentation

πŸ› οΈ Building and testing

cmake -B build && cmake --build build && ctest --test-dir build
./build/sha256_demo
cmake -B build -DSHA256_FUZZ=ON -DCMAKE_CXX_COMPILER=clang++ && cmake --build build && ./build/sha256_fuzz -max_total_time=60
cmake -B build -DCMAKE_BUILD_TYPE=Release -DSHA256_BUILD_BENCHMARKS=ON && cmake --build build && ./build/sha256_bench

CTest runs sha256 (the class), sha256_kernels (every kernel the host has, plus the forced portable fallback) and, when CMake finds OpenSSL, sha256_openssl (both headers in one file, digests against OpenSSL's SHA256()). A host without SHA instructions tests the portable kernel, plus AVX2 where it has it; CI covers SHA-NI and ARMv8.

🀝 Contributing

Issues and pull requests are welcome. Read AGENTS.md and the catalog's rules of engagement first; CI must pass on every platform.

πŸ“„ License

MIT. See LICENSE. Extracted from Xapiand (Copyright Β© 2015-2020 Dubalu LLC); the portable kernel derives from Stephan Brumme's SHA-256 (Copyright Β© 2014, 2015 Stephan Brumme).

About

SHA-256 for C++20: one call or streaming, hex or raw digest, on the CPU's SHA instructions when it has them

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages