Kategorie
java

Passay, wygeneruj mi proszę dobre hasło!

Passay? Hasła, hasła, wszędzie hasła. Czasem są potrzebne i czasem trzeba je generować. I tu na scenę wkracza właśnie Passay. Biblioteka do obsługi polityk dotyczących haseł.

Lecimy!

Junior Java Developer Handbook

Co to znaczy dobre hasło?

Można byłoby długo dyskutować o tym, kiedy hasło jest dobre. Prawda jest jednak taka, że do zależy od potrzeb. Bardzo często, kiedy np. zakładamy skrzynkę pocztową, czy też konto w jakimś portalu, to gdzieś tam z boku pojawia się informacja w stylu:

  • Hasło powinno mieć co najmniej 8 znaków.
  • Powinno zawierać litery i cyfry.
  • Super, gdy zawiera znaki specjalne.

Im bardziej unikalne tym lepiej. W generowaniu takich haseł pomocne są narzędzia online, czy też programy z rodziny KeePass.

Coraz częściej hasło samo w sobie stanowi tylko jeden z elementów procedury uwierzytelnienia. Jednak cały czas mamy bardzo dużo systemów, które opierają się wyłącznie na nazwie użytkownika i haśle.

Polityki generowania haseł

Kiedy przychodzi nam do budowania nowego systemu opartego o hasła, warto zdefiniować sobie politykę ich generowania. Polityka taka to nic innego jak jasne określenie elementów składowych hasła.

Przykładowa polityka Hasła będą składały się z 10 znaków, będą składały się z co najmniej jednej małej litery, jednej dużej i znaku specjalnego. Litery nie mogą być występującymi po sobie w alfabecie.

Passay

No i właśnie dochodzimy do rozwiązań w Javie.

Czy chcemy pisać sami generatory tego typu? Sądzę, że nie. Oczywiście, o ile nie jest to jakiś projekt na zaliczenie na studiach.

Czy jest coś trudnego w implementacji, gdybyśmy chcieli napisać to jednak sami? W gruncie rzeczy niewiele. Do głowy przychodzi mi ewentualnie ocenianie złożoności hasła. Tutaj wchodzi temat entropii. Ale generalnie, nie jest to coś niezwykle trudnego do zakodowania.

I właśnie dlatego powstają biblioteki, które rozwiązują tego typu problemu. Dziś w artykule mowa o bibliotece Passay, która nie raz pomagała mi w różnych projektach.

Passay jest biblioteką, która pozwala nam na zbudowanie polityk dotyczących haseł. Patrzymy na nią przez dwa aspekty:

  • Generowanie haseł zgodnie z wybranymi regułami.
  • Weryfikacja haseł pod kątem zgodności z wybranym zbiorem reguł.

Czyli z jednej strony możemy poprosić o stworzenie hasła, które na pewno spełni oczekiwane kryteria. A z drugiej możemy sprawdzić, czy np. hasło zdefiniowane przez użytkownika jest zgodne z kryteriami przez nas przyjętymi.

Dzisiaj skupimy się głównie na tym pierwszym aspekcie. Drugi zostawimy na inną okazję.

Generowanie haseł z Passay

Na początek załóżmy sobie kilka rzeczy:

  • Nasze wygenerowane hasło w testach będzie miało 10 znaków.
  • Do generowania hasła korzystamy z metody generate(…), której ciało widzimy poniżej.
  • Do walidacji hasła wykorzystamy wyrażenia regularne lub metodę validate(…). Ciało metody również poniżej.
  private final int PASSWORD_LENGTH = 10;
  
  private String generate(int length, CharacterRule... rules) {
    return new PasswordGenerator()
        .generatePassword(length, rules);
  }

  private boolean validate(String password, Rule... rules) {
    return new PasswordValidator(rules)
        .validate(new PasswordData(password))
        .isValid();
  }

Metoda generate(…) ma proste zadanie. Tworzy generator haseł PasswordGenerator z Passay i wywołuje jego metodę generatePassword(…). Argumentami tej metody jest długość hasła do wygenerowania oraz reguły, jakie hasło ma spełniać.

Z kolei validate(…) korzysta z walidacji hasła przez PasswordValidator, który konstruowany jest z uwzględnieniem reguł Rule do weryfikowania. PasswordData to w naszym przypadku opakowanie na hasło (może zawierać również inne informacja związane z hasłem). Metoda validate(…) pomoże nam w walidacji hasła, nie będziemy zagłębiać się w inne możliwe scenariusze walidacji. Na to przyjdzie pora kiedy indziej.

A tak wyglądałoby generowanie haseł wg prostej polityki.

POLITYKA Wygenerowane hasło powinno mieć 10 znaków i zawierać wyłącznie małe litery.

  @Test
  public void characterRuleTest() {
    CharacterRule rule = 
        new CharacterRule(EnglishCharacterData.LowerCase);
    String password = generate(PASSWORD_LENGTH, rule);

    assertThat(password, matchesPattern("[a-z]{10}"));
  }

Korzystamy w tym przypadku z klasy CharacterRule, która wskazuje na reguły dotyczące znaków w naszym haśle. Konstruktor tej klasy przyjmuje obiekt interfejsu CharacterData, który określa znaki, które będą brane pod uwagę do generowania danych. EnglishCharacterData to zestaw znaków z alfabetu angielskiego, w tym konkretnym przypadku bierzemy tylko małe litery.

Wynik sprawdzamy prostym wyrażeniem regularnym. A tak przy okazji, w tym przykładzie korzystamy z matchera matchesPattern z Hamcrest.

Inny przykład.

Polityka Hasła będą składały się z 10 znaków, w tym z co najmniej jednej małej litery, dwóch dużych liter, cyfry i znaku specjalnego.

  @Test
  public void customeRule2Test() {
    CharacterRule[] rules = {
        new CharacterRule(EnglishCharacterData.LowerCase),
        new CharacterRule(EnglishCharacterData.UpperCase, 2),
        new CharacterRule(EnglishCharacterData.Digit),
        new CharacterRule(EnglishCharacterData.Special)
    };

    String password = generate(PASSWORD_LENGTH, rules);

    assertTrue(validate(password, rules));
  }

Prawda, że to proste? Dodajemy więcej reguł do zestawu, który ma zostać użyty przez generator. Przy okazji walidację hasła przeprowadzamy przez naszą metodę validate(…), która dla przypomnienia korzysta z PasswordValidator z Passay.

Trochę trudniejszy przypadek hasła z Passay

Zobaczmy przykład z bardziej zaawansowanym wykorzystaniem CharacterData.

POLITYKA Wygenerowane hasło powinno mieć 10 znaków i zawierać wyłącznie duże litery ze zbioru od A do G.

  @Test
  public void customRuleTest() {
    CharacterRule rule =
        new CharacterRule(new CharacterData() {
          @Override
          public String getErrorCode() {
            return "MY_CUSTOM_CHARACTERS_POLICY_ERROR";
          }

          @Override
          public String getCharacters() {
            return "ABCDEFG";
          }
        });
    String password = generate(PASSWORD_LENGTH, rule);

    assertThat(password, matchesPattern("[A-G]{10}"));
    assertThat(password, not(matchesPattern("[H-Z]+")));
  }

W tym przypadku utworzyliśmy klasę anonimową implementującą interfejs CharacterData. Musimy zaimplementować dwie metody. Pierwsza, getErrorCode(), to podanie kodu błędu, gdyby był problem z tym zestawem znaków. Druga, getCharacters(…), to znaki, które będą brane pod uwagę do generowania. Na końcu sprawdzamy, czy nasze hasło rzeczywiście zawiera znaki z zakresu A-G i jest ich 10 i czy przypadkiem nie wkradł się tam inny znak.

Czego Passay nam nie wygeneruje

Jest kilka przypadków, kiedy wykorzystanie Passay może okazać się niewystarczające do generowania haseł. O ile biblioteka sprawdzi się w przypadku określania liczby i rodzaju znaków w haśle, o tyle nie dostarcza algorytmów bardziej zaawansowanych, np:

  • generowanie haseł bez sekwencji znaków,
  • generowanie haseł bez duplikacji łańcuchów,
  • i inne, oparte o nasze specyficzne wymagania.

Warto w tym miejscu jednak zastanowić się, czy nakładanie specyficznych restrykcji sprawia, że nasze hasło z punktu widzenia bezpieczeństwa jest lepsze, czy też niekoniecznie. I to zwykle nie jest oczywiste na pierwszy rzut oka, a czasem … jest wręcz odwrotnie niż nam się wydaje.

Na co trzeba zwrócić uwagę?

Pamiętasz? 10 znaków, jedna mała litera, dwie duże, cyfra i znak specjalny. Niby proste? Tak, ale dla komputera. Poniżej przykład wygenerowanego hasła właśnie z tej polityki opisywanej troszkę wyżej:

Y”xi¿ªW–4₺

Konia z rzędem temu, kto będzie w stanie zapamiętać i wpisać hasło tego typu. Dlatego bardzo starannie dobieraj politykę generowania haseł. Żeby użytkownika, w tym również i Ciebie, nie przerosło ewentualne zapamiętywanie takich ciągów znakowych. A jak już się uprzesz, to … zainstaluj mimo wszystko narzędzie w rodzaju Keepass lub LastPass.

Zagadka

W przykładach do tej pory posługiwałem się zbiorem liter w alfabecie angielskim. Passay dostarcza je w enum EnglishCharacterData. Mamy też zbiór liter polskich, które występują w PolishCharacterData. Poniżej ten zestaw z kodu biblioteki:

aąbcćdeęfghijklmnoópqrsśtuvwxyzźż
AĄBCĆDEĘFGHIJKLMNOÓPQRSŚTUVWXYZŹŻ

ZAGADKA
Polski zbór w Passay wygląda całkiem ok, ale … czegoś w nim brakuje? Wiesz czego? A czy wiesz dlaczego tego tam nie ma? No właśnie, jeśli wiesz to daj znać w komentarzu lub via e-mail. A jeśli nie wiesz, to będzie o tym w newsletterze. Koniecznie się zapisz

Okiem praktyka

Podsumowując. Passay jest świetną biblioteką do tworzenia polityk haseł i ich weryfikacji. Ma bardzo duże możliwości. Korzystam z niej w różnych projektach od dłuższego czasu, gdy zachodzi taka potrzeba.

Ale jest też jej druga strona medalu. Taka bardziej estetyczna. Mi generalnie … nigdy jej API nie przypadło do gustu. Robi to, co ma robić. Biblioteka jest w tym po prostu świetna. Serio, zdecydowanie rozwiązuje ten problem. Ale nie leży ona blisko podejścia fluent API, przez co kod z jej użyciem nie wygląda zbyt ładnie. Nie jestem przekonany też, co do odpowiedzialności nakładanej na interfejs CharacterData. Z jednej strony dostarcza zestaw znaków do generatora. Z drugiej jednak mówi, że hey, tak przy okazji, to jakiś kod błędu to … Mało w tym jest z zasady pojedynczej odpowiedzialności (SRP, ang. Single Responsible Principle).

Nasz szczęście zwykle kod związany z wykorzystaniem Passay pisze się raz w projekcie, enkapsuluje i nie wycieka on z warstwy infrastruktury. Także od biedy ujdzie 😁

A i mam takie pytanie do Ciebie? Znasz może jakąś dobrą alternatywę np. z lepszym API? A może chcesz się po prostu podzielić swoim zdaniem na temat Passay? Pisz śmiało, czy to w komentarzach, czy na maila.

Interesują Cię różne biblioteki do Javy? Świetnie, sprawdź mój cykl o bibliotekach, które warto znać:
Biblioteki Java, które warto znać

5 2 votes
Article Rating
Subscribe
Powiadom o
guest
2 komentarzy
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Dawid Bielecki aka dawciobiel

Czego brakuje w zbiorze polskich znaków Passay? – Wydaje mi się, że SPACJI. A dlaczego tego takiego znaku tam nie ma? – a no dlatego, że w zbiorze UNICODE jest wiele kodów które interpretują ten znak.