Loading & generating keys
Build every key the signers take through Crypto::keys() — rsa(), ec(), ed25519() and hmac() — loaded, generated or bootstrapped:
use RoundlyConsulting\Crypto\Facades\Crypto;
$rsa = Crypto::keys()->rsa()->privateFromStorage('local', 'keys/rsa.pem');
$rsaPub = Crypto::keys()->rsa()->publicFromConfig('jwt.public_key');
$ec = Crypto::keys()->ec()->generate('P-384');
$ed = Crypto::keys()->ed25519()->public($raw32Bytes); // raw 32-byte public key
$secret = Crypto::keys()->hmac()->fromConfig('services.webhook.secret');
// Load, or generate-and-persist on first boot:
$hmac = Crypto::keys()->hmac()->fromStorageOrGenerate('local', 'keys/hmac.key');
// A fresh secret, returned and never cached (shortcut for keys()->hmac()->generate(48)):
$fresh = Crypto::generateHmacSecret(48);For Ed25519 the facade uses the same public/private words as RSA and EC: public is the raw 32-byte key, private the 64-byte libsodium secret key.
The key classes
Every key class has a core zero-config factory that needs no container, config or disk. Each one validates on the way in, so a key is either strong and usable or rejected with a typed reason:
use RoundlyConsulting\Crypto\Signature\Key\EcKey;
use RoundlyConsulting\Crypto\Signature\Key\HmacSecret;
use RoundlyConsulting\Crypto\Signature\Key\OkpKey;
use RoundlyConsulting\Crypto\Signature\Key\RsaKey;
$secret = HmacSecret::fromString($raw); // ≥ 32 bytes, never a PEM
$rsa = RsaKey::private($privatePem); // RSA 2048–8192 bits
$rsaPub = RsaKey::public($publicPem);
$ec = EcKey::private($pem); // curve auto-detected: P-256 / P-384 / P-521
$ecPub = EcKey::fromCoordinates($x, $y, 'P-256'); // raw COSE/JWK point
$rsaJwk = RsaKey::fromModulusExponent($n, $e); // raw COSE/JWK modulus + exponent
$okp = OkpKey::ed25519($rawPublic); // 32-byte Ed25519 public keyValidation guards
- HmacSecret — rejects empty values, anything carrying public-key material (a PEM, even behind whitespace or a UTF-8 BOM, and raw DER key bytes — blocking RS256→HS256 confusion), secrets under 32 bytes and single-repeated-byte secrets. Throws WeakKeyException. HS384 and HS512 need 48 and 64 bytes — the Hs signer checks that.
- RsaKey — must be RSA, 2048–8192 bits, with an odd public exponent of at least 3. An oversized raw modulus is rejected before it is parsed.
- EcKey — P-256, P-384 or P-521 only; the curve is detected from the key itself.
- OkpKey — a raw 32-byte Ed25519 public key or a 64-byte libsodium secret key; wrong-length material throws KeyLoadException.
From a disk or your config
Opt-in Laravel-native loaders read material from any filesystem disk or from a config key you own, and run exactly the same guards as the core factory. A missing file (whether the disk returns null or is configured with 'throw' => true), an unknown disk name, a failed write in fromStorageOrGenerate() or a missing, blank or non-string config value throws Signature\KeyLoadException — never a PHP warning and never a filesystem exception; the original stays on getPrevious():
use RoundlyConsulting\Crypto\Signature\Key\HmacSecret;
use RoundlyConsulting\Crypto\Signature\Key\RsaKey;
use RoundlyConsulting\Crypto\Signature\Key\EcKey;
use RoundlyConsulting\Crypto\Signature\Key\OkpKey;
// From a filesystem disk (any configured disk name):
$secret = HmacSecret::fromStorage('local', 'keys/hmac.key');
$rsa = RsaKey::privateFromStorage('local', 'keys/rsa.pem');
$rsaPub = RsaKey::publicFromStorage('local', 'keys/rsa.pub');
$ec = EcKey::privateFromStorage('local', 'keys/ec.pem');
$okp = OkpKey::ed25519FromStorage('local', 'keys/ed25519.pub'); // 32 raw bytes
// From YOUR config key (explicit — the package reads no config on its own):
$secret = HmacSecret::fromConfig('services.webhook.secret');
$rsa = RsaKey::privateFromConfig('jwt.private_key');
$ecPub = EcKey::publicFromConfig('tokens.public_key');The PEM factories — RsaKey and EcKey public() / private(), Certificate::fromPem() and Chain::fromPems() — take PEM text, never a path: a file://… string, which PHP’s OpenSSL functions would read from disk, is refused as unreadable. Load files through fromStorage().
Generate fresh material
$secret = HmacSecret::generate(); // 32 random bytes (≥256 bits); pass a larger byte count if you like
$rsa = RsaKey::generate(2048); // or 3072 / 4096
$ec = EcKey::generate('P-256'); // or P-384 / P-521
$okp = OkpKey::generate(); // Ed25519, needs ext-sodiumHmacSecret::generate() accepts 32 to 1024 bytes — pass 48 or 64 for an HS384 or HS512 signer. RsaKey::generate() and EcKey::generate() need a usable OpenSSL configuration (openssl.cnf); loading PEMs, signing and verifying do not. On a host with a missing or broken config, generate() throws KeyLoadException rather than leaking a warning.
Load, or generate-and-persist on first boot
fromStorageOrGenerate() loads the key from a disk path or — only when the file is missing — generates a fresh one, writes it with private visibility and returns it. An existing-but-invalid file is never overwritten; it still throws. A write that fails throws KeyLoadException — a generated key that was not persisted would be replaced by a different one on the next boot. For asymmetric keys the persisted artifact is the private PEM (Ed25519 persists the 64-byte secret), so persist the public half yourself:
use Illuminate\Support\Facades\Storage;
// Bootstraps a secret on first run, reuses it forever after:
$secret = HmacSecret::fromStorageOrGenerate('local', 'keys/hmac.key');
// Asymmetric: private PEM is written; persist the public half alongside it:
$key = RsaKey::fromStorageOrGenerate('local', 'keys/rsa.pem', bits: 3072);
Storage::disk('local')->put('keys/rsa.pub', $key->publicPem(), 'private');
$ec = EcKey::fromStorageOrGenerate('local', 'keys/ec.pem', curve: 'P-384');
$okp = OkpKey::fromStorageOrGenerate('local', 'keys/ed25519.key'); // 64-byte secret, ext-sodiumPrivate visibility is driver-dependent: owner-only file permissions on the local driver, but a coarser ACL or a no-op on some object stores. Persist secret key material only to a private disk you control — never a publicly served one.
Exporting PEMs
Storing keys somewhere other than a Laravel disk? RsaKey and EcKey export both halves — privatePem() (PKCS#8) and publicPem() (SPKI). privatePem() throws KeyLoadException::notPrivate() on a public key, so a verify-only key can never be mistaken for signing material:
$key = RsaKey::generate(2048);
file_put_contents('/etc/app/private.pem', $key->privatePem()); // secret — chmod 0600
file_put_contents('/etc/app/public.pem', $key->publicPem());Factory reference
| Facade accessor | Methods |
|---|---|
Crypto::keys()->rsa() | public · private · fromModulusExponent · generate · publicFromStorage · privateFromStorage · publicFromConfig · privateFromConfig · fromStorageOrGenerate |
Crypto::keys()->ec() | public · private · fromCoordinates · generate · publicFromStorage · privateFromStorage · publicFromConfig · privateFromConfig · fromStorageOrGenerate |
Crypto::keys()->ed25519() | public · private · generate · publicFromStorage · privateFromStorage · publicFromConfig · privateFromConfig · fromStorageOrGenerate |
Crypto::keys()->hmac() | fromString · generate · fromStorage · fromConfig · fromStorageOrGenerate |
| Key | Static factories |
|---|---|
HmacSecret | fromString · generate · fromStorage · fromConfig · fromStorageOrGenerate |
RsaKey | public · private · fromModulusExponent · generate · publicFromStorage · privateFromStorage · publicFromConfig · privateFromConfig · fromStorageOrGenerate |
EcKey | public · private · fromCoordinates · generate · publicFromStorage · privateFromStorage · publicFromConfig · privateFromConfig · fromStorageOrGenerate |
OkpKey | ed25519 · fromSecretKey · generate · ed25519FromStorage · ed25519FromConfig · secretKeyFromStorage · secretKeyFromConfig · fromStorageOrGenerate |
Where ext-sodium is present, HmacSecret and OkpKey secrets are best-effort zeroed from memory when the object is destroyed — defence in depth, not a guarantee.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.