Skip to content

Repository files navigation

StormByte-Crypto

Platform C++26 CMake License: LGPL v3 or commercial CI Sponsor

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.

What this module does

  • Hasher — SHA-256, SHA-512, SHA3-256, SHA3-512, BLAKE2b, BLAKE2s. One-shot hex digest or a Buffer::Consumer that 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::Password via 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::Native is one PK transform per blob. Strategy::Hybrid wraps 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 a std::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 a Secure::Password. Handles are StormByte::Safe::Clonable with Safe::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() is StormByte::ByteSize. A missing Vault::Get is StormByte.Crypto.Secure.Vault: ….
  • Buffer-first I/O — std::span<const std::byte> → Buffer::WriteOnly for blocks; Buffer::Consumer in / out for pipelines (Network, Multimedia). Octet payloads are StormByte::BinaryData. Abstract counts use StormByte::Size; octet lengths use StormByte::ByteSize. Non-secret public text is ingested as std::string_view.

The rest of the suite

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

Table of Contents

Installation

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 build

Shared 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>.

Usage

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.

Factories

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.

Password and Vault

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::string by 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 one Password holds.
  • 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.

Hash and compress

#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());

Symmetric encrypt

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.

Asymmetric encrypt

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.

KeyPair on disk

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.

Sign and verify

#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).

Key agreement

#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.

Security notes

  • Decompression of untrusted input is not size-bounded. Like the underlying zlib/libbzip2, Compressor decompresses 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 streaming Consumer once your own limit is hit.
  • Private key files written by KeyPair::Save/SavePrivate are created owner-only (0600 on 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::string of a production password after Password construction. Cede the buffer so it can be wiped. Do not pass secrets as string_view into Password.

Contributing

Issues only on this repository. Fork and open a pull request against master.

License

From 2.0.0, original StormByte-Crypto source is dual-licensed:

  1. GNU Lesser General Public License version 3 or later. See LICENSE and https://www.gnu.org/licenses/lgpl-3.0.html.
  2. 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.

Support

StormByte is developed in spare time. Sponsorship is optional and does not buy features, priority or support.

About

Hash, compress, encrypt, sign and key agreement for the StormByte C++ suite. Crypto++ stays private.

Topics

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages