This repository is StormByte Crypto: hash, compress, encrypt, sign and key agreement for the StormByte C++ suite.
It depends on StormByte Base ≥ 2.0.0, StormByte Buffer ≥ 2.0.0 and StormByte System ≥ 2.0.0. Public headers live under StormByte/crypto/. Crypto++ stays in the private tree: installed headers never mention CryptoPP::. With a static Crypto++ link, consumers do not install it.
The suite is split on purpose. Base, Buffer, Config, Database, Logger, Multimedia, Network and System are other repositories. This one does not implement them.
- Hasher — SHA-256, SHA-512, SHA3-256, SHA3-512, BLAKE2b, BLAKE2s. One-shot hex digest or a
Buffer::Consumerthat yields the digest when the source closes. - Compressor — Zlib, Gzip, BZip2. Same block / stream contract as the rest of the module.
- Symmetric crypter — AES CBC, AES-GCM, ChaCha20-Poly1305, Camellia, Serpent, Twofish. Keys from
Secure::Passwordvia PBKDF2-HMAC-SHA256 (600 000 iterations). Authenticated modes fail closed on a bad tag or a wrong password. - Asymmetric crypter — RSA OAEP-SHA and ECC ECIES.
Strategy::Nativeis one PK transform per blob.Strategy::Hybridwraps a random AES-256-GCM session key. Decrypt auto-detects the envelope. - Signer — DSA, RSA PKCS#1 v1.5 + SHA-256, ECDSA, Ed25519. Block and streaming sign / verify.
- Secret — ECDH on secp256r1 / secp384r1 / secp521r1, and X25519. The shared secret is a
Secure::Password, not astd::string. - KeyPair — Generate, persist PEM or DER, optional PKCS#8 (PBES2 + PBKDF2 + AES-256-CBC, OpenSSL-compatible). Public key travels as
StormByte::Safe::String(Base64 SPKI); private key stays in aSecure::Password. Handles areStormByte::Safe::ClonablewithSafe::Shared(KeyPair::Generic::PointerType). - Secure::Password / Secure::Vault — wiped secret buffer and named store under
StormByte::Crypto::Secure. Last owner zeros the bytes. Vault is movable, not copyable.Password::Size()isStormByte::ByteSize. A missingVault::GetisStormByte.Crypto.Secure.Vault: …. - Buffer-first I/O —
std::span<const std::byte>→Buffer::WriteOnlyfor blocks;Buffer::Consumerin / out for pipelines (Network, Multimedia). Octet payloads areStormByte::BinaryData. Abstract counts useStormByte::Size; octet lengths useStormByte::ByteSize. Non-secret public text is ingested asstd::string_view.
| Module | Role | API |
|---|---|---|
| Base | Exceptions, Expected, serialization, UUID, concepts, CString / WCString / Size / ByteSize |
/StormByte |
| Buffer | FIFO, SharedFIFO, Ring, Producer/Consumer and multi-stage pipelines | /StormByte-Buffer |
| Config | Human-readable text and versioned binary documents (groups, lists, raw bytes) | /StormByte-Config |
| Crypto | This repository | /StormByte-Crypto |
| Database | One API over SQLite, PostgreSQL and MariaDB | /StormByte-Database |
| Logger | Stream logger with levels, headers, hierarchical components and Scope |
/StormByte-Logger |
| Multimedia | Decode, encode and containers without raw FFmpeg types; codecs enabled only if present | /StormByte-Multimedia |
| Network | Framed packets, Client/Server, IPv4/IPv6 TCP and Buffer pipelines (compress/encrypt) | /StormByte-Network |
| System | Processes, pipes and environment variables across Linux, Windows and macOS | /StormByte-System |
- What this module does
- The rest of the suite
- Installation
- Usage
- Security notes
- Contributing
- License
- Support
Needs a C++26 compiler, CMake 3.28 or newer, StormByte Base ≥ 2.0.0, StormByte Buffer ≥ 2.0.0 and StormByte System ≥ 2.0.0. Crypto++ and libbzip2 are build dependencies. Prefer a static Crypto++ link when you redistribute.
git clone --recursive https://github.com/StormByte-Suite/StormByte-Crypto.git
cd StormByte-Crypto
cmake -S . -B build
cmake --build buildShared vs static follows CMake BUILD_SHARED_LIBS (declared in lib/, default ON). A plain configure builds the shared library. -DBUILD_SHARED_LIBS=OFF builds a static archive; on Windows the headers then do not use dllimport. Vendored StormByte dependencies follow the same mode. Prefer a static Crypto++ link when you redistribute; that is independent of whether StormByte-Crypto itself is shared or static.
A shared build keeps this library as its own .so / .dll. Under the LGPL that is usually the simpler way to ship: the user can replace that file. A static archive is folded into your binary. The LGPL still applies to this code; you must give the recipient a way to relink your product with a different build of this library. If that does not fit how you distribute the final product, a commercial license is available from the copyright holder (see License).
Link StormByte-Crypto (and Buffer / String / System / Base). Include path: the public install prefix, headers as #include <StormByte/crypto/….hxx>.
Headers are #include <StormByte/crypto/….hxx>. Namespace root is StormByte::Crypto. Wiped secrets live in StormByte::Crypto::Secure.
Nothing in the public tree includes Crypto++. Private headers are not installed.
Public handles are Clonable + MakePointer / Shared. KeyPair, Signer, Crypter and Secret take KeyPair::Generic::PointerType, not std::shared_ptr. Exceptions use Path{"Crypto"}; child offices add their own segment. what() is StormByte.Crypto or StormByte.Crypto.<Child>: message. Secure uses StormByte.Crypto.Secure / StormByte.Crypto.Secure.Vault.
auto hasher = Hasher::Create(Hasher::Type::SHA256);
auto zip = Compressor::Create(Compressor::Type::Zlib, 6);
auto aes = Crypter::Create(Crypter::Type::AES_GCM, password);
auto rsaKp = KeyPair::RSA::Generate(2048);
auto rsa = Crypter::Create(Crypter::Type::RSA, rsaKp);
auto signer = Signer::Create(Signer::Type::ECDSA, ecdsaKp);
auto ecdh = Secret::Create(Secret::Type::ECDH, ecdhKp);Concrete types (Crypter::AES_GCM, KeyPair::X25519, Signer::ED25519, …) construct the same way without going through Create.
Secure::Password is the only public container for secret bytes (passphrases, private key DER, shared secrets). Copies share the buffer; the last owner wipes it. Size() is StormByte::ByteSize.
Ingest is deliberately not std::string_view and not std::string by value.
- A view cannot wipe the caller's buffer, so the secret would stay in the program after construction.
- Passing
std::stringby value or by move across a DLL is unsafe: the buffer was allocated on the caller's heap. Destroying it inside this library can free the wrong CRT. - Therefore the caller cedes a non-const
std::string&. The constructor copies into wiped storage owned by this module and then overwrites and clears the argument. After return the only remaining copy is the onePasswordholds. - Literals use
explicit Password(const char*). They are copied; the source is not wiped (it lives in read-only storage). Use that form for tests and placeholders, not for production secrets kept in source. - Raw bytes (
const void*+ByteSize) are copied and not wiped; the caller owns the source.
#include <StormByte/crypto/secure/password.hxx>
#include <StormByte/crypto/secure/vault.hxx>
#include <StormByte/crypto/crypter/generic.hxx>
using namespace StormByte::Crypto;
using StormByte::Crypto::Secure::Password;
using StormByte::Crypto::Secure::Vault;
std::string fromEnv = std::getenv("DB_SECRET");
Password db(fromEnv); // fromEnv is emptied and wiped
Password placeholder("token-xyz"); // literal: not wiped
Vault vault;
vault.Store("database", db);
vault.Store("api", placeholder);
if (auto p = vault.Get("database")) {
auto aes = Crypter::Create(Crypter::Type::AES_GCM, *p);
}
if (*vault.Get("database") == db)
; // constant-time compare of the bytes
vault.Remove("api");
vault.Clear();Vault is movable, not copyable. A move leaves the source empty. Names are std::string_view. A missing name is Secure::VaultException.
#include <StormByte/crypto/hasher/generic.hxx>
#include <StormByte/crypto/compressor/generic.hxx>
#include <StormByte/buffer/fifo.hxx>
#include <StormByte/buffer/producer.hxx>
using namespace StormByte::Crypto;
auto sha = Hasher::Create(Hasher::Type::SHA256);
auto zip = Compressor::Create(Compressor::Type::Zlib, 6);
StormByte::Buffer::FIFO digest, packed;
const char msg[] = "payload";
const auto span = std::span<const std::byte>(
reinterpret_cast<const std::byte*>(msg), sizeof(msg) - 1);
sha->Hash(span, digest);
zip->Compress(span, packed);
StormByte::Buffer::Producer prod;
prod.Write(msg);
prod.Close();
auto hashed = sha->Hash(prod.Consumer());Password → random salt + PBKDF2-HMAC-SHA256 → key. AES-GCM and ChaCha20-Poly1305 authenticate; a wrong password or a flipped bit returns false.
#include <StormByte/crypto/crypter/symmetric/aes_gcm.hxx>
#include <StormByte/crypto/secure/password.hxx>
#include <StormByte/buffer/fifo.hxx>
#include <StormByte/buffer/producer.hxx>
using namespace StormByte::Crypto;
using StormByte::Crypto::Secure::Password;
Password password("SecurePassword123!");
Crypter::AES_GCM gcm(password);
StormByte::Buffer::FIFO encrypted, decrypted;
const char msg[] = "authenticated payload";
const auto span = std::span<const std::byte>(
reinterpret_cast<const std::byte*>(msg), sizeof(msg) - 1);
gcm.Encrypt(span, encrypted);
gcm.Decrypt(
std::span<const std::byte>(encrypted.Data().data(), encrypted.Data().size()),
decrypted);
StormByte::Buffer::Producer prod;
prod.Write(msg);
prod.Close();
auto cipher = gcm.Encrypt(prod.Consumer());
auto plain = gcm.Decrypt(cipher);CBC siblings (AES, Camellia, Serpent, Twofish) use the same Encrypt / Decrypt names.
Native is one PK operation per blob (small messages). Hybrid is a random AES-256-GCM key wrapped with the recipient public key. Decrypt reads the header and picks the path.
#include <StormByte/crypto/keypair/rsa.hxx>
#include <StormByte/crypto/crypter/asymmetric/rsa.hxx>
#include <StormByte/buffer/fifo.hxx>
using namespace StormByte::Crypto;
auto kp = KeyPair::RSA::Generate(2048);
Crypter::RSA hybrid(kp);
Crypter::RSA native(kp, Crypter::Asymmetric::Strategy::Native);
StormByte::Buffer::FIFO out;
hybrid.Encrypt(span, out);
hybrid.Decrypt(
std::span<const std::byte>(out.Data().data(), out.Data().size()),
out);ECC (Crypter::ECC + KeyPair::ECC) is the same API.
| Format | Meaning |
|---|---|
PEM |
OpenSSL text (BEGIN / Base64). Default. |
DER |
Raw ASN.1. Same family as many .cer / .crt blobs. |
#include <StormByte/crypto/keypair/rsa.hxx>
#include <StormByte/crypto/secure/password.hxx>
using namespace StormByte::Crypto;
using StormByte::Crypto::Secure::Password;
auto kp = KeyPair::RSA::Generate(2048);
kp->Save("/tmp/keys", "app", KeyPair::StorageFormat::PEM);
Password wrap("disk-secret");
kp->Save("/tmp/keys", "app-enc", wrap);
auto loaded = KeyPair::Load("/tmp/keys/app.pub.pem", "/tmp/keys/app.pem");
auto enc = KeyPair::Load("/tmp/keys/app-enc.pub.pem", "/tmp/keys/app-enc.pem", wrap);Wrong or missing wrap password fails closed. Type comes from the OID (RSA, DSA, EC, Ed25519, X25519). Generate → Save → Load stays usable for encrypt, sign and share. X25519 also understands raw 32-byte library form. PublicKey() is const StormByte::Safe::String&; convert with std::string{std::string_view{kp->PublicKey()}} if you need a std::string.
#include <StormByte/crypto/keypair/ed25519.hxx>
#include <StormByte/crypto/signer/generic.hxx>
#include <StormByte/buffer/fifo.hxx>
using namespace StormByte::Crypto;
auto kp = KeyPair::ED25519::Generate();
auto signer = Signer::Create(Signer::Type::ED25519, kp);
StormByte::Buffer::FIFO sig;
signer->Sign(span, sig);
bool ok = signer->Verify(span, std::string_view(
reinterpret_cast<const char*>(sig.Data().data()), sig.Data().size()));Streaming: signer->Sign(consumer) / signer->Verify(consumer, signature).
#include <StormByte/crypto/keypair/x25519.hxx>
#include <StormByte/crypto/secret/x25519.hxx>
using namespace StormByte::Crypto;
auto alice = KeyPair::X25519::Generate();
auto bob = KeyPair::X25519::Generate();
auto secret = Secret::Create(Secret::Type::X25519, alice);
auto shared = secret->Share(bob->PublicKey());Share takes std::string_view (a String converts). The result is std::optional<Secure::Password>. ECDH is the same with KeyPair::ECDH::Generate(256|384|521) and Secret::Type::ECDH.
- Decompression of untrusted input is not size-bounded. Like the underlying zlib/libbzip2,
Compressordecompresses as much as the stream decodes to; a small malicious input can expand to a very large output ("decompression bomb"). If you decompress data from an untrusted source, bound it yourself: check the expected/maximum size before decompressing, or stop draining the streamingConsumeronce your own limit is hit. - Private key files written by
KeyPair::Save/SavePrivateare created owner-only (0600on POSIX) and refuse to write through a pre-existing symlink at the destination path. Public key files are unaffected by either restriction. - Do not keep a live
std::stringof a production password afterPasswordconstruction. Cede the buffer so it can be wiped. Do not pass secrets asstring_viewintoPassword.
Issues only on this repository. Fork and open a pull request against master.
From 2.0.0, original StormByte-Crypto source is dual-licensed:
- GNU Lesser General Public License version 3 or later. See LICENSE and https://www.gnu.org/licenses/lgpl-3.0.html.
- A commercial license from the copyright holder (David C. Manuelda, StormBytePP).
Neither license covers other StormByte modules or third-party material shipped under thirdparty/ (including Crypto++, bundled libbzip2, and vendored StormByte trees). Those keep their own licenses. Neither license grants patent rights.
StormByte is developed in spare time. Sponsorship is optional and does not buy features, priority or support.