A Bitwarden-inspired passphrase generator for PHP with first-class Laravel integration.
Generates secure, memorable passphrases using the EFF long word list (7,776 words) by default, with full support for custom word lists.
composer require nicobleiler/php-passphraseLaravel will auto-discover the service provider. For other frameworks, see Standalone Usage.
use NicoBleiler\Passphrase\Facades\Passphrase;
// Default: 3 words, hyphen separator, no capitalize, no number
Passphrase::generate();
// "candle-rubber-glimpse"
// Customize everything
Passphrase::generate(
numWords: 5,
wordSeparator: '.',
capitalize: true,
includeNumber: true,
);
// "Candle.Rubber3.Glimpse.Obtain.Willow"use NicoBleiler\Passphrase\PassphraseGenerator;
class AuthController
{
public function __construct(
private PassphraseGenerator $passphrase,
) {}
public function temporaryPassword(): string
{
return $this->passphrase->generate(
numWords: 4,
capitalize: true,
includeNumber: true,
);
}
}use NicoBleiler\Passphrase\PassphraseGenerator;
$generator = new PassphraseGenerator();
echo $generator->generate(); // "candle-rubber-glimpse"You can set instance-level defaults that apply whenever generate() is called without explicit parameters:
$generator = new PassphraseGenerator();
$generator->setDefaults(
numWords: 5,
wordSeparator: '.',
capitalize: true,
includeNumber: true,
);
echo $generator->generate(); // "Candle.Rubber3.Glimpse.Obtain.Willow"
// Explicit params still override defaults
echo $generator->generate(numWords: 3, wordSeparator: '-');By default, PassphraseGenerator uses PHP's cryptographically secure Random\Engine\Secure. You can inject a custom Random\Randomizer for deterministic output (e.g. testing, demos, reproducible benchmarks):
use NicoBleiler\Passphrase\PassphraseGenerator;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
$generator = new PassphraseGenerator(
randomizer: new Randomizer(new Xoshiro256StarStar(12345)),
);
// Same seed always produces the same passphrase
echo $generator->generate(); // deterministic outputSecurity note: Only use non-secure engines for testing or demos. For real passphrase generation, always use the default
Secureengine.
| Parameter | Type | Default | Description |
|---|---|---|---|
numWords |
?int |
3 |
Number of words (minimum of 3). null uses instance/config default. |
wordSeparator |
?string |
'-' |
Character(s) between words. null uses instance/config default. |
capitalize |
?bool |
false |
Capitalize the first letter of each word. null uses instance/config default. |
includeNumber |
?bool |
false |
Append a random digit (0–9) to one random word. null uses instance/config default. |
targetEntropyBits |
?int |
null |
Optional. Adjusts word count to meet or exceed this entropy target (in bits). Overrides numWords when set. |
All generate() parameters are nullable. For numWords, wordSeparator, capitalize, and includeNumber, passing null (or omitting them) uses defaults set via setDefaults() or config/passphrase.php in Laravel.
targetEntropyBits is evaluated per-call by generate() and is not loaded from setDefaults() (there is currently no target_entropy_bits config key).
When calling generate(), providing targetEntropyBits takes precedence over numWords.
$generator = new PassphraseGenerator();
// Entropy-based generation: numWords is calculated to reach at least 60 bits
echo $generator->generate(targetEntropyBits: 60);These match Bitwarden's passphrase generator options exactly.
Publish the config file:
php artisan vendor:publish --tag=passphrase-configThis creates config/passphrase.php:
return [
'num_words' => 3,
'word_separator' => '-',
'capitalize' => false,
'include_number' => false,
// No 'target_entropy_bits' key: targetEntropyBits is a per-call generate() override
// null = bundled EFF long word list (7,776 words)
// Or provide your own word list as a PHP array of strings
'word_list' => null,
// Optional words to remove from the active word list
// Works with both bundled EFF and custom word_list values
'excluded_words' => [],
];These config values are wired into PassphraseGenerator::setDefaults() and are automatically used as defaults when calling Passphrase::generate() without explicit parameters.
targetEntropyBits is not part of setDefaults() and therefore has no target_entropy_bits config key; pass it directly to generate() when you want entropy-targeted generation.
Note: All word lists (bundled or custom) must contain at least 2 words. This validation also applies after exclusions.
Provide word_list as a PHP array of strings:
// config/passphrase.php
'word_list' => ['correct', 'horse', 'battery', 'staple'],
// Optionally remove specific words from the active list
'excluded_words' => ['horse'],Or load it from a dedicated PHP file:
// config/passphrase.php
'word_list' => require base_path('config/custom_word_list.php'),// config/custom_word_list.php
<?php
return [
'correct',
'horse',
'battery',
'staple',
];use NicoBleiler\Passphrase\WordList;
use NicoBleiler\Passphrase\PassphraseGenerator;
// From an array
$wordList = WordList::fromArray(['correct', 'horse', 'battery', 'staple']);
$generator = new PassphraseGenerator($wordList);
echo $generator->generate(numWords: 4);You can also publish the bundled EFF word list to your resources folder:
php artisan vendor:publish --tag=passphrase-wordlistsThis is optional and mainly useful if you want a local copy to inspect or customize. By default, the package reads the bundled EFF list directly.
The generation algorithm mirrors Bitwarden's Rust implementation:
sequenceDiagram
participant Client
participant ServiceProvider
participant PassphraseGenerator
participant Randomizer
participant WordList
Client->>ServiceProvider: Boot (Laravel)
ServiceProvider->>WordList: Create (singleton)
ServiceProvider->>PassphraseGenerator: Create with Randomizer(Secure)
ServiceProvider->>PassphraseGenerator: setDefaults(config values)
activate PassphraseGenerator
PassphraseGenerator-->>ServiceProvider: self (fluent)
deactivate PassphraseGenerator
Client->>PassphraseGenerator: generate() [no params]
activate PassphraseGenerator
PassphraseGenerator->>PassphraseGenerator: Use instance defaults
PassphraseGenerator->>Randomizer: getInt() [select words]
activate Randomizer
Randomizer-->>PassphraseGenerator: random indices
deactivate Randomizer
PassphraseGenerator->>WordList: wordAt(index)
activate WordList
WordList-->>PassphraseGenerator: word string
deactivate WordList
PassphraseGenerator->>Randomizer: getInt() [digit if needed]
activate Randomizer
Randomizer-->>PassphraseGenerator: random digit
deactivate Randomizer
PassphraseGenerator-->>Client: passphrase string
deactivate PassphraseGenerator
composer testOr directly:
vendor/bin/phpunitThe test suite includes tests modeled after Bitwarden's own test cases:
- Validation (word count bounds)
- Deterministic generation with seeded
Xoshiro256StarStarengine - Capitalize behavior (including Unicode)
- Number inclusion
- Separator handling (including multi-byte emoji)
- EFF word list integrity
- Laravel integration (service provider, facade, config defaults)
These benchmarks were run on a local Ryzen 9 5950X machine running Windows 11 with PHP 8.5.0.
In this run the available benchmark providers were: php-passphrase, genphrase/genphrase, martbock/laravel-diceware, random_bytes, Illuminate\\Support\\Str::random, and Illuminate\\Support\\Str::password (providers are included automatically when their packages are installed).
Note on cold runs:
benchGenerateColdincludes setup and first-use initialization (autoloading, object construction, and initial word-list work). Cold-runrstdevis therefore expected to be higher and should be interpreted as startup-cost signal, not steady-state throughput.
benchGenerateCold
+----------------+--------------------------------------------------------------------+------+-----+-----------+-----------+----------+----------+
| benchmark | set | revs | its | mem_peak | mode | mean | rstdev |
+----------------+--------------------------------------------------------------------+------+-----+-----------+-----------+----------+----------+
| ProvidersBench | php-passphrase (EFF 5 words, ~64.6 bits) | 1 | 20 | 1.612mb | 331.431μs | 504μs | ±141.06% |
| ProvidersBench | genphrase/genphrase (65-bit target, diceware) | 1 | 20 | 1.366mb | 1.662ms | 3.788ms | ±240.86% |
| ProvidersBench | martbock/laravel-diceware (EFF 5 words, ~64.6 bits) | 1 | 20 | 958.76kb | 3.68ms | 4.745ms | ±82.04% |
| ProvidersBench | random_bytes(8) hex (~64 bits) | 1 | 20 | 494.856kb | 9.818μs | 11.6μs | ±35.59% |
| ProvidersBench | Illuminate\Support\Str::random(11) (~65.5 bits) | 1 | 20 | 494.872kb | 182.63μs | 245.95μs | ±77.25% |
| ProvidersBench | Illuminate\Support\Str::password(10) (default options, ~64.6 bits) | 1 | 20 | 1.143mb | 921.507μs | 1.371ms | ±132.20% |
+----------------+--------------------------------------------------------------------+------+-----+-----------+-----------+----------+----------+
benchGenerateWarm
+----------------+--------------------------------------------------------------------+------+-----+-----------+---------+----------+---------+
| benchmark | set | revs | its | mem_peak | mode | mean | rstdev |
+----------------+--------------------------------------------------------------------+------+-----+-----------+---------+----------+---------+
| ProvidersBench | php-passphrase (EFF 5 words, ~64.6 bits) | 100 | 20 | 495.12kb | 1.353μs | 1.406μs | ±14.18% |
| ProvidersBench | genphrase/genphrase (65-bit target, diceware) | 100 | 20 | 1.364mb | 6.715μs | 6.829μs | ±3.74% |
| ProvidersBench | martbock/laravel-diceware (EFF 5 words, ~64.6 bits) | 100 | 20 | 510.016kb | 2.099ms | 2.073ms | ±2.68% |
| ProvidersBench | random_bytes(8) hex (~64 bits) | 100 | 20 | 495.112kb | 0.125μs | 0.132μs | ±24.62% |
| ProvidersBench | Illuminate\Support\Str::random(11) (~65.5 bits) | 100 | 20 | 495.128kb | 0.532μs | 0.563μs | ±16.54% |
| ProvidersBench | Illuminate\Support\Str::password(10) (default options, ~64.6 bits) | 100 | 20 | 587.672kb | 11.86μs | 11.927μs | ±2.81% |
+----------------+--------------------------------------------------------------------+------+-----+-----------+---------+----------+---------+
Relative comparisons (mean times):
- Cold run:
php-passphrase(504 μs) is ~7.5× faster thangenphrase(3.788 ms) and ~9.4× faster thanmartbock/laravel-diceware(4.745 ms). - Warm run:
php-passphrase(1.406 μs) is ~4.9× faster thangenphrase(6.829 μs) and ~1,475× faster thanmartbock/laravel-diceware(2.073 ms).
These values are environment-dependent; run composer bench locally if you need numbers for a different machine or PHP version.
Run the benchmark suite with:
composer benchThe benchmark suite uses PHPBench and compares providers with near-matched entropy targets.
Each provider is measured in two scenarios:
benchGenerateCold— includes provider setup + first generation (cold start)benchGenerateWarm— measures steady-state generation after setup warmup
Default stability settings (configured in phpbench.json):
- iterations:
20 - revolutions:
1(cold subject),100(warm subject) - warmup:
2(warm subject only)
You can override scale from CLI when needed, for example:
vendor/bin/phpbench run --report=providers --iterations=40 --revs=5 --retry-threshold=5Compared providers:
php-passphrasewith EFF 5 words (~64.6 bits)random_bytes(8)(~64 bits)Illuminate\\Support\\Str::random(11)(~65.5 bits)Illuminate\\Support\\Str::password(10)with default options (~64.6 bits)
Optional providers can be added composer require --dev genphrase/genphrase martbock/laravel-diceware and will be included in the benchmark suite if present:
genphrase/genphrasewith a 65-bit target on diceware modemartbock/laravel-dicewarewith EFF 5 words (~64.6 bits)
The eurosat7/random package cannot currently be required directly via Composer VCS because its upstream composer.json has no valid package name.
Use a local package-repository override in your composer.json instead:
{
"repositories": [
{
"type": "package",
"package": {
"name": "eurosat7/random",
"version": "dev-main",
"source": {
"type": "git",
"url": "https://github.com/eurosat7/random.git",
"reference": "main"
},
"autoload": {
"psr-4": {
"Eurosat7\\Random\\": "src/"
}
},
"require": {
"php": ">=8.2"
}
}
}
]
}Then run:
composer require --dev eurosat7/random:dev-mainWhen installed, the benchmark also includes:
eurosat7/randomviaEurosat7\Random\Generator::password(10)(~64+ bits)
Baseline and comparison runs:
composer bench:baseline
composer bench:compare- PHP 8.2+
ext-mbstring(for multibyte/Unicode capitalization support)- Laravel 11+ (optional, for Laravel integration)
MIT — see LICENSE.
- Passphrase generation logic inspired by Bitwarden
- Word list from the Electronic Frontier Foundation, licensed under CC-BY 4.0