spomky-labs/defuse-generator
A small PHP utility from Spomky-Labs for generating secure cryptographic keys compatible with defuse/php-encryption. It offers a simple API for creating encryption keys to protect application data in Laravel and other PHP projects.
composer require spomky-labs/defuse-generator
Before writing code, open vendor/spomky-labs/defuse-generator/src and read the two or three classes there. The public surface is intentionally tiny — typically a generator class plus an interface — and the exact namespace/method signature is the only thing you need to confirm. It wraps defuse/php-encryption's CSPRNG, so the important guarantee is "cryptographically secure bytes, mapped onto a charset," not a framework feature.
There is no Laravel service provider or config file. Register a binding yourself in AppServiceProvider:
use Defuse\Generator\StringGenerator; // confirm namespace in vendor/
public function register(): void
{
$this->app->singleton(StringGenerator::class, fn () => new StringGenerator());
}
Your first real use case should be issuing an opaque API or password-reset token, because that is where CSPRNG quality actually matters:
final class TokenIssuer
{
public function __construct(private StringGenerator $generator) {}
/** @return array{0:string,1:string} plaintext, hash */
public function issue(int $length = 48): array
{
$plain = $this->generator->generate(
$length,
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
);
return [$plain, hash('sha256', $plain)];
}
}
Return the plaintext to the user once, store only the hash, and compare with hash_equals on lookup. That pattern is the reason to reach for this package at all; everything else (random filenames, slugs) is better served by Laravel's Str::random().
Bind once, inject everywhere. Never instantiate the generator at call sites. A singleton binding means one place to swap in a fake during tests and one place to audit entropy usage.
// AppServiceProvider::register()
$this->app->singleton(StringGenerator::class, fn () => new StringGenerator());
Drive alphabets from config, not literals. Different features need different character sets, and hard-coded strings drift.
// config/random.php
return [
'token_length' => (int) env('RANDOM_TOKEN_LENGTH', 48),
'alphabets' => [
'alnum' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
'url' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
'numeric' => '0123456789',
'human' => 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789', // no I/O/0/1
],
];
$this->app->singleton(StringGenerator::class, fn () => new StringGenerator());
$this->app->bind('random.alphabet', fn () => config('random.alphabets.url'));
Read alphabets through config(), never env(), so php artisan config:cache behaves.
Wrap it in a domain abstraction. The package type is a low-level utility; your application should depend on an interface like TokenGenerator, implemented by a class that delegates to it. This keeps vendor types out of jobs, controllers, and tests, and makes it trivial to replace the package later with random_bytes().
interface TokenGenerator { public function generate(int $length): string; }
final class DefuseTokenGenerator implements TokenGenerator
{
public function __construct(private StringGenerator $generator) {}
public function generate(int $length): string
{
return $this->generator->generate($length, config('random.alphabets.url'));
}
}
OTP / short codes need a length and expiry policy, not just randomness. Use the numeric alphabet, expire in minutes, and rate-limit verification. Six digits is ~20 bits of entropy — fine only because attempts are throttled.
$code = $this->generator->generate(6, config('random.alphabets.numeric'));
Cache::put("otp:{$user->id}", hash('sha256', $code), now()->addMinutes(10));
Idempotency keys for queued jobs are a clean second use case: generate once when dispatching, persist on the job payload, and use a unique index to reject duplicate work.
dispatch(new SendInvoice($invoice, $this->generator->generate(32, config('random.alphabets.url'))));
Uniqueness needs a database constraint and a retry loop, not just a long string. For slugs, short links, or referral codes, catch the unique-constraint violation and regenerate a bounded number of times.
foreach (range(1, 5) as $_) {
$code = $this->generator->generate(8, config('random.alphabets.human'));
try { return ReferralCode::create(['code' => $code, 'user_id' => $user->id]); }
catch (UniqueConstraintViolationException) { continue; }
}
throw new RuntimeException('Could not allocate a referral code.');
Testing: bind a deterministic fake to your domain interface in TestCase::setUp(). Do not attempt to seed the CSPRNG, and do not assert on generated values in feature tests — assert on length, charset membership, and that the stored hash matches.
$this->app->bind(TokenGenerator::class, fn () => new class implements TokenGenerator {
public function generate(int $length): string { return str_repeat('a', $length); }
});
Commands and one-offs: an artisan command that mints a token for a service account is the most common operational use. Inject the generator via the command constructor as usual.
random_bytes() has existed since PHP 7.0 and Str::random() since Laravel 5.4. This package's historical value was PHP 5.x compatibility via Defuse. If you are on Laravel 9/10/11, justify the dependency explicitly or use the framework.length × log2(alphabetSize). A 32-character hex string is 128 bits; a 6-digit numeric code is ~20 bits. Short human codes must be paired with expiry and rate limiting, never used as API keys.$alphabet[random_int(0, len-1)] is fine, but ord(random_byte) % strlen($alphabet) skews the distribution. Read the source once and confirm.utf8mb4_unicode_ci treats A and a as equal in a unique index. Use _bin collation or a binary/varbinary column for token columns, or restrict the alphabet to a case-insensitive-safe subset.I, l, 1, O, 0). Use the human alphabet above and strtoupper before comparison if the channel is case-insensitive.Hash facade (bcrypt/argon2); random strings are for secrets you hand out, not secrets you verify.Authorization headers or POST bodies, and hash before persistence.mt_rand()/uniqid(). Let it surface as a 500; a weak token is worse than a failed request.env() calls outside config files return null after config:cache. Route every length and alphabet through config('random.*').defuse/php-encryption, which pulls in OpenSSL/Sodium expectations. Confirm the extension exists in your Docker image and that the dependency is not abandoned before shipping it into a long-lived app.TokenGenerator interface from day one. Swapping the Defuse-backed implementation for bin2hex(random_bytes(32)) or Str::random(48) later then touches exactly one class.How can I help you explore Laravel packages today?