• Время чтения ~3 мин
  • 04.12.2022

Генерация паролей — это то, о чем мы все думаем в какой-то момент, но как мы можем это сделать — или сделать эти пароли легко запоминающимися и безопасными?

В этом уроке я расскажу вам, как я недавно создал генератор паролей для развлечения, и что я думал об этом. Я создал его не только для Laravel, но и как независимый от фреймворка подход, который я мог бы легко использовать в Laravel.

При использовании инструмента для создания пароля вы часто получаете набор слов, соединенных тире. Я хотел сделать что-то, что я мог бы использовать и запоминать с относительной легкостью, но с возможностью сделать пароли более «безопасными», меняя местами буквы вместо цифр.

Давайте включим наш код!

Сначала мы хотим определить стандартный интерфейс, который наш генератор захочет реализовать — это позволит нам использовать его внутри Laravel, вне Laravel и везде, где мы хотим его использовать.

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

Мы хотим, чтобы в нашей реализации было два возможных подхода: генерировать случайный пароль с использованием запоминающихся слов и еще один вариант генерировать то же самое, что может показаться более безопасным. Прекрасным примером этого будет:

летучая рыба-плавающая ящерица - памятный
fly1ng-f1sh-sw1mm1ng-l1z4rd - "безопасный"

Чтобы добиться этого, нам нужно будет создать способ генерации слов. Нам понадобится Существительные and прилагательные, что означает, что нам нужно создать еще один контракт. Этот контракт будет способом получения случайного слова из зарегистрированного списка либо стандартным подходом, либо в соответствии с нашими требованиями. secure подход.

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

Оба метода в этом контракте будут достигать примерно одного и того же, единственная разница в том, что с одним из них мы сделаем немного больше обработки перед возвратом. Давайте погрузимся в код, чтобы увидеть, как это может работать.

Мы знаем, что нам требуется список существительных и список прилагательных, и оба будут делать одно и то же с точки зрения поведения. В будущем мы можем немного изменить это, чтобы включить и глаголы, но сейчас мы сосредоточимся на существительных и прилагательных. Для этого я буду использовать черту PHP, чтобы добавить общее поведение к каждой реализации.

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 строить 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;
}

Наши две реализации позволят нам генерировать половину того, что нам нужно. Далее мы должны подумать о том, как мы хотим создавать наши «безопасные» пароли. Давайте вернемся к созданной нами черте.

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,
        };
    }
}

Итак, как это работает? В качестве «безопасного» пароля мы по-прежнему хотим получить случайное слово из нашего списка, но нам нужно выполнить дополнительную обработку. Мы разбили слово на его основные буквы, чтобы получился массив. Затем мы можем сопоставить каждую букву и вызвать наш метод convert для обновления ввода. Наш метод convert представляет собой простой оператор сопоставления, который мы можем использовать для сопоставления ввода с выводом, поэтому мы контролируем, как они записываются. Я сопоставил их здесь с типичным подходом, который вы можете найти, когда числовое представление выглядит как буквенное представление, что позволяет нам по-прежнему иметь запоминающийся пароль.

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);
    }
}

Наш генератор паролей принимает две реализации списка в свой конструктор, чтобы мы могли получить к ним доступ. Затем мы реализуем два метода генерации паролей: запоминающийся и безопасный.

Для каждого подхода мы вызываем внутренний метод, который позволит нам построить пароль, взорвав массив с помощью строки соединения. Если мы хотим распространить это на большее количество слов, мы должны передать больше случайных слов.

Наш контракт не должен знать о build метод, так как это деталь реализации. Мы могли бы расширить этот метод, чтобы настроить символ присоединения, но я оставлю это на ваше усмотрение, если вы захотите.

В качестве дополнительного дополнения мы теперь создадим поставщика услуг и фасад, чтобы вы могли быстро вызвать или внедрить его в код своего приложения. Однако эта часть только Laravel. До этого момента мы фокусировались на независимом от фреймворка коде.

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',
            );
        }
    }
}

Здесь мы загружаем нашу конфигурацию, в которой будут храниться существительные и прилагательные, которые мы хотим использовать в нашем генераторе паролей. Мы также предоставляем Laravel набор инструкций о том, как мы хотим загрузить нашу реализацию из контейнера.

Теперь фасад будет работать таким же образом, но будет разрешать реализацию из контейнера, позволяя вам статически вызывать методы, которые вы хотите вызвать.

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

Наш фасад возвращает контракт, который мы использовали для привязки реализации, и имеет блоки документов, в которых указывается, какие методы доступны и какие типы возвращаемых данных он ожидает.

Все, что нам теперь нужно сделать, это вызвать генератор паролей, и мы можем предоставлять случайно сгенерированные пароли для наших пользователей.

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

Я создал пакет, который предоставляет приведенный выше код, в котором есть немного больше кода, если вы хотите немного больше погрузиться в пример. Вы можете найти репозиторий для этого здесь: https://github.com/JustSteveKing/password-generator

Отказ от ответственности. Это не предназначено для использования в производственной среде для создания ваших паролей. Мой вариант использования для этого состоит в том, чтобы фактически генерировать одноразовые коды использования, такие как одноразовые коды доступа. Это не самый безопасный вариант, так как список слов довольно мал и сделает вас уязвимым для потенциальной атаки по словарю.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297