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!
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ć
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.
Poza spacją brakuje też innych znaków, np. ł i Ł. Unicode w przypadku niektórych ma pewien problem z normalizacją, po prostu nie ma schematu dekompozycji.