nicobleiler / php-passphrase
Passphrase generator with Laravel integration, inspired by Bitwarden. Uses the EFF long word list by default with support for custom wordlists.
Requires
- php: ^8.2
- ext-mbstring: *
Requires (Dev)
- laravel/pint: ^1.27
- orchestra/testbench: ^9.0 || ^10.0
- phpbench/phpbench: ^1.4.3
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.4 || ^12.0 || ^13.0
- rector/rector: ^2.3
- dev-master
- v2.1.0
- v2.1.0-beta.2
- v2.1.0-beta.1
- v2.1.0-alpha.2
- v2.1.0-alpha.1
- v2.0.0
- v2.0.0-beta.1
- v2.0.0-alpha.1
- v1.2.0
- v1.1.0
- v1.0.0
- v1.0.0-beta.4
- v1.0.0-beta.3
- v1.0.0-beta.2
- v1.0.0-beta.1
- v1.0.0-alpha.8
- v1.0.0-alpha.7
- v1.0.0-alpha.6
- v1.0.0-alpha.5
- v1.0.0-alpha.4
- v1.0.0-alpha.3
- v1.0.0-alpha.2
- v1.0.0-alpha.1
- dev-beta
- dev-alpha
- dev-dev
- dev-renovate/ubuntu-24.x
- dev-feat/26-entropy-bits-target
- dev-feat/25-blacklist
This package is auto-updated.
Last update: 2026-03-01 17:00:42 UTC
README
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.
Installation
composer require nicobleiler/php-passphrase
Laravel will auto-discover the service provider. For other frameworks, see Standalone Usage.
Quick Start
Laravel (Facade)
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"
Laravel (Dependency Injection)
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, ); } }
Standalone Usage
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: '-');
Custom Randomizer
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 output
Security note: Only use non-secure engines for testing or demos. For real passphrase generation, always use the default
Secureengine.
Options
| 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.
Configuration
Publish the config file:
php artisan vendor:publish --tag=passphrase-config
This 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.
Word Lists
Note: All word lists (bundled or custom) must contain at least 2 words. This validation also applies after exclusions.
Via Config (Laravel)
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', ];
Programmatically
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-wordlists
This 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.
How It Works
The generation algorithm mirrors Bitwarden's Rust implementation:
Sequence diagram
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
Loading
Testing
composer test
Or directly:
vendor/bin/phpunit
The 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)
Performance (2026-02-16)
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.
Benchmarking
Run the benchmark suite with:
composer bench
The 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=5
Compared 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-main
When installed, the benchmark also includes:
eurosat7/randomviaEurosat7\Random\Generator::password(10)(~64+ bits)
Baseline and comparison runs:
composer bench:baseline composer bench:compare
Requirements
- PHP 8.2+
ext-mbstring(for multibyte/Unicode capitalization support)- Laravel 11+ (optional, for Laravel integration)
License
MIT — see LICENSE.
Credits
- Passphrase generation logic inspired by Bitwarden
- Word list from the Electronic Frontier Foundation, licensed under CC-BY 4.0