Przelewy24, jeden z najpopularniejszych operatorów płatności w Polsce, podpisuje powiadomienia webhook przy użyciu skrótu wykorzystującego wspólny klucz: sklep otrzymuje sekret przy konfiguracji integracji, a każde przychodzące powiadomienie zawiera podpis, który trzeba przeliczyć lokalnie i porównać z otrzymanym. Ten sam wzorzec — podpisz kluczem współdzielonym, zweryfikuj przez ponowne obliczenie — stosuje HMAC-SHA256 u większości bramek płatniczych na świecie.
Dlaczego potrzebny jest klucz tajny, a nie tylko skrót
Zwykły skrót jak SHA-256 dowodzi tylko, że dane nie zostały uszkodzone w transporcie — nie kto je wysłał. Gdyby serwer po prostu haszował treść webhooka i porównywał z hashem dołączonym do żądania, atakujący mógłby sfałszować własny ładunek i dołączyć pasujący hash. HMAC rozwiązuje to, domieszając klucz tajny znany wyłącznie nadawcy i odbiorcy — bez niego wygenerowanie poprawnego podpisu jest praktycznie niemożliwe, nawet jeśli atakujący dokładnie wie, jakiej funkcji skrótu się używa.
Dlaczego to nie po prostu "skrót klucza plus wiadomość"
Naiwne połączenie klucza i wiadomości przed haszowaniem (np. hash(klucz + wiadomość)) jest podatne na ataki rozszerzenia długości w niektórych funkcjach skrótu — atakujący czasem może dopisać dane i obliczyć nowy poprawny skrót bez znajomości pełnego klucza. HMAC wykorzystuje specyficzną konstrukcję z podwójnym haszowaniem i wewnętrznym/zewnętrznym paddingiem, która całkowicie zamyka tę klasę ataków.
Gdzie jeszcze pojawia się HMAC
Poza podpisywaniem webhooków, HMAC uwierzytelnia żądania API i stanowi rdzeń algorytmu TOTP — tego samego, który generuje sześciocyfrowe kody w aplikacjach uwierzytelniających.
Do czego to się przydaje
- Weryfikacja autentyczności webhooków z bramek płatniczych lub innych zewnętrznych serwisów.
- Podpisywanie żądań API bez przesyłania samego klucza tajnego przez sieć.
- Zrozumienie wewnętrznej mechaniki kodów TOTP i tokenów sesji.
Porównanie w czasie stałym
Porównywanie otrzymanego podpisu z oczekiwanym za pomocą zwykłego operatora === jest niebezpieczne: taki operator zwykle zatrzymuje się na pierwszym niezgodnym bajcie, a czas wykonania porównania zdradza atakującemu, ile pierwszych bajtów odgadł poprawnie. Solidnie zaimplementowana obsługa webhooków zawsze korzysta z funkcji porównania w czasie stałym — na przykład hash_equals w PHP czy crypto.timingSafeEqual w Node.js — która sprawdza wszystkie bajty niezależnie od tego, gdzie występuje pierwsza niezgodność.