• Час читання ~2 хв
  • 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)];
    }
}

Для початку ми створюємо ознаку, де ми знаємо, що наша реалізація хоче бути створена за допомогою масиву слів. Потім додаємо метод вибору випадкового елемента з цього масиву. Це дозволить нам створити наш стандартний пароль, який запам’ятовується, викликавши його кілька разів і об’єднавши результати. Давайте додамо це до двох реалізацій, які нам потрібно створити.

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 для оновлення введення. Наш метод перетворення — це простий оператор збігу, який ми можемо використовувати для відображення вхідних даних і вихідних даних, тому ми контролюємо, як вони записуються. Я зіставив їх тут із типовим підходом, який ви можете зустріти, коли подання цифр виглядає як подання букв, що дозволяє нам мати пароль, який можна запам’ятати.

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

Наш генератор паролів бере дві реалізації списків у свій конструктор, щоб ми могли отримати до них доступ. Потім ми реалізуємо два методи генерації паролів: запам’ятовуваний і безпечний.

Для кожного підходу ми викликаємо внутрішній метод, який дозволить нам створити пароль, об’єднавши масив із об’єднувальним рядком. Якщо ми хочемо поширити це на більше слів, ми повинні передати більше випадкових слів.

Наш контракт не повинен знати про будувати метод, оскільки це деталь реалізації. Ми могли б розширити цей метод, щоб налаштувати символ приєднання, але я залишу це вашій реалізації, якщо ви забажаєте.

Як додаткову додаткову функцію ми створимо постачальника послуг і фасад, щоб ви могли швидко зателефонувати або вставити це в код програми. Однак ця частина стосується лише 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;
    }
}

Наш фасад повертає контракт, який ми використовували для зв’язування реалізації, і має docblocks із зазначенням доступних методів і типів повернення, які він очікує.

Все, що нам тепер потрібно зробити, це викликати генератор паролів, і ми зможемо надати довільно згенеровані паролі для наших користувачів.

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

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

Відмова від відповідальності. Це не призначено для використання у виробничому середовищі для створення ваших паролів. Я використовую це для фактичного створення одноразових кодів, таких як One Time Pass Codes. Це не є найбезпечнішим, оскільки список слів досить малий, і ви станете відкритими для потенційної атаки за словником.

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