JWT to niewielka rzecz o dużym zasięgu. Jest dowodem tożsamości między Twoimi usługami, a algorytm, który go podpisuje, zwykle jest najwolniejszym elementem systemu do zmiany, ponieważ każdy wystawca i każdy weryfikator musi przejść na niego jednocześnie. Dlatego warto wiedzieć, że taka opcja istnieje, zanim będzie potrzebna.
sgcWebSockets 2026.10 dodaje postkwantowe algorytmy JWT z RFC 9964 do klienta i serwera JWT: ML-DSA-44, ML-DSA-65 i ML-DSA-87. Stają obok HS, RS i ES jako trzy kolejne wartości tej samej właściwości, a wszystko inne w Twoim tokenie pozostaje bez zmian.
Wystawianie tokenu
Wybierz algorytm w nagłówku, przekaż komponentowi klucz prywatny i podpisz:
oJWT := TsgcHTTP_JWT_Client.Create(nil);
oJWT.JWTOptions.Header.alg := jwtMLDSA65;
oJWT.JWTOptions.Algorithms.MLDSA.PrivateKey.Text := vPrivatePEM;
oJWT.JWTOptions.Payload.iss := 'my-service';
oJWT.JWTOptions.Payload.sub := 'user-1';
vToken := oJWT.Sign;
Klucz prywatny to zwykły PEM PKCS#8. Wszystkie trzy formy zdefiniowane w RFC 9881 są odczytywane, więc klucz przechowywany jako 32-bajtowe ziarno, jako klucz rozwinięty, lub jako oba, działa poprawnie.
Weryfikacja tokenu
Strona serwerowa ma tę samą postać, z włączonym kluczem publicznym i rodziną algorytmów:
oServer := TsgcHTTP_JWT_Server.Create(nil);
oServer.JWTOptions.Algorithms.MLDSA.Enabled := True;
oServer.JWTOptions.Algorithms.MLDSA.PublicKey.Text := vPublicPEM;
if oServer.Validate(vToken, vHeader, vPayload, vError) then
ShowMessage(vPayload);
Każda rodzina algorytmów jest przełączana osobno, więc wdrożenie, które wystawia wyłącznie tokeny ML-DSA, może wyłączyć pozostałe, a token przychodzący z polem alg ustawionym na coś innego jest odrzucany, zamiast być sprawdzany.
Zestaw parametrów musi pasować
W RFC 9964 jest jedna zasada, którą warto wypowiedzieć na głos, ponieważ to błąd, który inaczej pozostałby niezauważony: klucz musi należeć do zestawu parametrów wskazanego przez alg. Klucz, który nie pasuje, jest odrzucany. Podpisywanie niewłaściwym kluczem zgłasza wyjątek, a weryfikacja niewłaściwym kluczem zwraca fałsz zamiast zgłaszać wyjątek, więc atakujący nie może wykorzystać tej różnicy, aby czegokolwiek się dowiedzieć.
Klucze jako JSON Web Keys
RFC 9964 rejestruje dla tych algorytmów typ klucza, AKP, a biblioteka go odczytuje i zapisuje, co przydaje się, gdy klucze są publikowane pod punktem końcowym JWKS, a nie umieszczane w pliku:
uses
sgcHTTP_JWT_MLDSA;
var
vJWK, vPublicPEM, vPrivatePEM: string;
begin
// {"kty":"AKP","alg":"ML-DSA-65","pub":"...","priv":"..."}
vJWK := sgcMLDSA_ExportPrivateJWK(mldsa65, oSeed);
// and back again, as the PEM the components read
sgcMLDSA_ImportJWKAsPEM(vJWK, vPublicPEM, vPrivatePEM);
end;
Element prywatny to 32-bajtowe ziarno, tak jak definiuje to RFC, a czytnik jest wobec niego rygorystyczny: elementy muszą być kanonicznym base64url o dokładnie właściwej długości, alg jest wymagane, a gdy obecny jest klucz prywatny, musi być zgodny z kluczem publicznym w tym samym JWK. JWK, który nie spełnia któregoś z tych warunków, jest odrzucany, zamiast zostać wczytany w połowie.
Interoperacyjność, udowodniona
Format podpisu jest użyteczny tylko wtedy, gdy ktoś inny generuje te same bajty. Dwa sprawdzenia są dostarczane jako testy, a nie jako deklaracje:
- Opublikowane przykłady z RFC 9964 są odtwarzane bajt po bajcie. Podpisywanie odbywa się w wariancie deterministycznym, więc ten sam klucz i te same dane wejściowe zawsze dają ten sam podpis, a załącznik RFC staje się czymś, co można zweryfikować w teście.
- Tokeny wygenerowane przez niezależną implementację, w tym przypadku bibliotekę Python dilithium-py, są tu poprawnie weryfikowane, a kontenery kluczy zapisane ręcznie według RFC 9881 przechodzą przez odczyt i zapis bez zmian.
Przy okazji: binarny sekret HMAC
Przy okazji zniknęło niepowiązane ograniczenie. Algorytmy HS przyjmowały klucz jako tekst, a prawdziwy klucz HMAC to losowe bajty, z których większość w ogóle nie jest poprawnym tekstem. Istnieje teraz druga właściwość, która przyjmuje klucz jako base64url, czyli to samo kodowanie, którego używa element k w oct JWK:
oJWT.JWTOptions.Algorithms.HS.SecretBase64URL := 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ...';
Gdy jest ustawiona, ma pierwszeństwo przed sekretem tekstowym, zarówno na kliencie, jak i na serwerze.
Roszczenia napisane przez kogoś innego
Jeszcze jedna poprawka warta poznania, jeśli akceptujesz tokeny od innych wystawców. Ciąg JSON może być zapisany jako surowe UTF-8 lub z każdym znakiem spoza ASCII zapisanym w formie escape, a różne biblioteki wybierają różnie. Obie formy są teraz sprowadzane do tego samego tekstu, więc nazwa z akcentem jest odczytywana poprawnie niezależnie od tego, który wystawca ją utworzył, a nagłówek alg zapisany z sekwencjami escape jest rozpoznawany jako algorytm, który reprezentuje.
Aktualizacja
Trzy algorytmy są dodatkami na końcu istniejącej listy, więc zapisana wartość porządkowa zachowuje swoje znaczenie, a nic z tego, co już robisz, się nie zmienia. Podpisywanie za pomocą ML-DSA wymaga pakietu sgcCrypto, a kompilacja bez niego odrzuca takie tokeny z jasnym komunikatem zamiast zawodzić w niejasny sposób.
Czytaj dalej
- Kryptografia postkwantowa w Delphi
- Delphi TLS 1.3 bez bibliotek DLL OpenSSL
- Klient JWT w Delphi i Serwer JWT w Delphi, podstawy
Zobacz
Jest o tym krótki film na kanale eSeGeCe.
Masz pytania, uwagi lub potrzebujesz pomocy z migracją? Skontaktuj się z nami — odpowiedź otrzymasz od osób, które napisały ten kod.
