Creating a Password Generator

Published on by

Creating a Password Generator image

Password generation is something we all think about doing at some point, but how can we go about doing it - and making these passwords easy to remember and secure?

In this tutorial, I will walk you through how I recently built a password generator for fun and what my thoughts were. I created it for more than just Laravel but as a framework agnostic approach that I could use within Laravel easily.

When using a create password tool, you often get a selection of words joined together with a dash. I wanted to make something I could use and remember with relative ease, but with the option to make the passwords more "secure" by switching letters for numbers in places.

Let's get our code on!

We first want to define a standard interface that our generator will want to implement - this will allow us to use it within Laravel, outside of Laravel, and wherever we want to use it.

interface GeneratorContract
{
/**
* @return string
*/
public function generate(): string;
 
/**
* @return string
*/
public function generateSecure(): string;
}

We want our implementation to have two possible approaches, generate a random password using memorable words - and another option to generate the same thing that may be seen as more secure. A perfect example of this would be:

flying-fish-swimming-lizard - memorable fly1ng-f1sh-sw1mm1ng-l1z4rd - "secure"

To achieve this, we will need to create a way for words to be generated. We will need Nouns and Adjectives, meaning we need to create another contract to follow. This contract will be a way to fetch a random word from a registered list either in a standard approach or in our required secure approach.

interface ListContract
{
public function random(): string;
 
public function secure(): string;
}

Both methods on this contract will achieve roughly the same thing, the only difference is that with one of them, we will do a little more processing before returning. Let's dive into the code to see how this can work.

We know that we require a list of nouns and a list of adjectives, and both are going to do the same thing in terms of behavior. In the future, we may slightly change this so that we get verbs included as well, but for now, we will focus on nouns and adjectives. To achieve this, I will use a PHP Trait to add shared behavior to each implementation.

trait HasWords
{
/**
* @param array $words
*/
public function __construct(
private readonly array $words,
) {
}
 
public function random(): string
{
return $this->words[array_rand($this->words)];
}
}

To begin with, we create the trait, where we know that our implementation will want to be created with an array of words. Then we add the method for selecting a random item from this array. This will allow us to build our standard memorable password by calling this a few times and concatenating the results. Let's add this to the two implementations that we need to create.

final class AdjectiveList implements ListContract
{
use HasWords;
}
 
 
final class NounList implements ListContract
{
use HasWords;
}

Our two implementations will allow us to generate half of what we need. We must consider how we want to create our "secure" passwords next. Let us revisit the trait we created.

trait HasWords
{
/**
* @param array $words
*/
public function __construct(
private readonly array $words,
) {
}
 
public function random(): string
{
return $this->words[array_rand($this->words)];
}
 
public function secure(): string
{
$word = $this->random();
 
$asArray = str_split($word);
 
$secureArray = array_map(
callback: fn (string $item): string => $this->convertToNumerical($item),
array: $asArray,
);
 
return implode('', $secureArray);
}
 
public function convertToNumerical(string $item): string
{
return match ($item) {
'a' => '4',
'e' => '3',
'i' => '1',
'o' => '0',
default => $item,
};
}
}

So how do this work? As a "secure" password, we still want to fetch a random word from our list, but we need to do some additional processing. We split the word into its core letters to have an array. We can then map over each letter - and call our convert method to update the input. Our convert method is a simple match statement that we can use to map the input to the output, so we are controlling how these are written. I have mapped these here to a typical approach you may find where the number representation looks like the letter representation - allowing us to have a memorable password still.

Our list control now works, and we can create a list of nouns and adjectives - that are injected into the constructors. We can fetch the word directly or pass it through a converter to make it more "secure". Our next step is to look at the implementation of our generator. So far, we only have implementations for the lists themselves. Let's look at some more code.

final class PasswordGenerator implements GeneratorContract
{
public function __construct(
private readonly ListContract $nouns,
private readonly ListContract $adjectives,
) {
}
 
public function generate(): string
{
return $this->build(
$this->nouns->random(),
$this->adjectives->random(),
$this->nouns->random(),
$this->adjectives->random(),
);
}
 
public function generateSecure(): string
{
return $this->build(
$this->nouns->secure(),
$this->adjectives->secure(),
$this->nouns->secure(),
$this->adjectives->secure(),
);
}
 
private function build(string ...$parts): string
{
return implode('-', $parts);
}
}

Our password generator takes the two list implementations into its constructor so that we can access them. We then implement the two methods for generating passwords: memorable and secure.

For each approach, we call a method internally that will allow us to build the password by imploding the array with a joining string. If we want to extend this to more words, we must pass in more random words.

Our contract does not need to know about the build method as that is an implementation detail. We could extend this method to customize the joining character - but I will leave that to your implementation should you want to.

As an additionally added extra, we will now create a service provider and facade so that you can quickly call or inject this into your application code. This part is Laravel only, though. Up until this point, we have focused on framework-independent code.

final class PackageServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(
abstract: GeneratorContract::class,
concrete: fn (): GeneratorContract => new PasswordGenerator(
nouns: new NounList(
words: (array) config('password-generator.nouns'),
),
adjectives: new AdjectiveList(
words: (array) config('password-generator.adjectives'),
),
),
);
}
 
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->publishes(
paths: [
__DIR__ . '/../../config/password-generator.php',
config_path('password-generator.php'),
],
groups: 'password-generator-config',
);
}
}
}

Here we are loading our configuration, which will house the nouns and adjectives we want to use in our password generator. We also provide a set of instructions to Laravel about how we wish to load our implementation from the container.

Now the facade will work in the same way but will resolve the implementation from the container allowing you to statically call the methods you wish to call.

/**
* @method static string generate()
* @method static string generateSecure()
*
* @see GeneratorContract
*/
final class Generator extends Facade
{
protected static function getFacadeAccessor(): string
{
return GeneratorContract::class;
}
}

Our facade returns the contract that we have used to bind the implementation and has docblocks stating what methods are available and the return types it expects.

All we now need to do is call the password generator, and we can provide randomly generated passwords for our users.

Generator::generate(); // flying-fish-swimming-lizard
Generator::generateSecure(); // fly1ng-f1sh-sw1mm1ng-l1z4rd

I have created a package that delivers this code above, which has a little more code should you wish to dive into the example a little more. You can find the repository for this here: https://github.com/JustSteveKing/password-generator

Disclaimer. This is not intended for use in a production environment to create your passwords. My use case for this is to actually generate one off use codes such as One Time Pass Codes. This is not the most secure as the list of words is quite small, and will leave you open to a potential dictionary attack.

Steve McDougall photo

Technical writer at Laravel News, Developer Advocate at Treblle. API specialist, veteran PHP/Laravel engineer. YouTube livestreamer.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Tinkerwell

Version 4 of Tinkerwell is available now. Get the most popular PHP scratchpad with all its new features and simplify your development workflow today.

Visit Tinkerwell
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
LoadForge logo

LoadForge

Easy, affordable load testing and stress tests for websites, APIs and databases.

LoadForge
Paragraph logo

Paragraph

Manage your Laravel app as if it was a CMS – edit any text on any page or in any email without touching Blade or language files.

Paragraph
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
DocuWriter.ai logo

DocuWriter.ai

Save hours of manually writing Code Documentation, Comments & DocBlocks, Test suites and Refactoring.

DocuWriter.ai
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Generate Code Coverage in Laravel With PCOV image

Generate Code Coverage in Laravel With PCOV

Read article
Non-backed Enums in Database Queries and a withSchedule() bootstrap method in Laravel 11.1 image

Non-backed Enums in Database Queries and a withSchedule() bootstrap method in Laravel 11.1

Read article
Laravel Pint --bail Flag image

Laravel Pint --bail Flag

Read article
Laravel Herd for Windows is now released! image

Laravel Herd for Windows is now released!

Read article
The Laravel Worldwide Meetup is Today image

The Laravel Worldwide Meetup is Today

Read article
Cache Routes with Cloudflare in Laravel image

Cache Routes with Cloudflare in Laravel

Read article