Szyfrowanie od podpisywania różni się tym, że szyfruje się kluczem publicznym, a podpisuje prywatnym. Tak? To jest jedyna różnica? No to tak mam zrobione.
Nie. Tzn wyjątkowo zupełnie przypadkiem dla RSA tak jest, bo to akurat taki algorytm który jednocześnie pozwala na szyfrowanie i na podpisywanie. Ale nie jest to żadna reguła. Takie (EC)(Ed)DSA nie ma odpowiednika "szyfrowania", a jedynie możliwość podpisywania. W kontekście JWT w ogóle możesz zapomnieć o tym, że RSA wspiera coś takiego jak szyfrowanie.
Najpierw payload muszę zahashować jakimś HMACiem, aby utworzyć skrót, aby nie podpisywać całego payloadu. Tu też jest ok tak?
Nie. HMAC a Hash (SHA, MD5) to są zupełnie dwie różne rzeczy. Aż boję się pytać czy aby czasem ten "HMAC" którego teraz używacie też nie jest pisany "ręcznie" i np. to nie jest po prostu SHA z wiadomośći... :D
Jeszcze raz: JWT ma RFC z opisanymi standardami i powinieneś się ich trzymać. RFC opisuje konkretnie jakich algorytmów użyć!
Ale w ogólności tak, musisz hashować wiadomość zanim ją podpiszesz, bo RSA nie pozwala podpisać czegoś dłuższego niż modulus, czyli w zalezności od twojego klucza np. 1024 czy 2048 bitów.
W jaki sposób bezpiecznie to podpisać? Zakładam, że inaczej utworzyć instancje Ciphera?
Ja bym sugerował użyć jakiegoś gotowca, bo Java w standardzie ma bardzo niewiele. Np. taka libka:
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
Ona służy dokładnie do tego. Coś w tym stylu:
RSAKey rsaJWK = new RSAKeyGenerator(2048)
.keyID(someId)
.keyUse(KeyUse.SIGNATURE)
.generate();
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject(subject)
.jwtID(someid)
.audience(someAudience)
.issuer(someIssuer)
.expirationTime(someTimestamp)
.claim("claim_name", claim)
.claim("claim_name2", claim2)
.build();
SignedJWT signedJWT = new SignedJWT(
new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaJWK.getKeyID()).build(),
claimsSet);
signedJWT.sign(signer);
String finalJWT = signedJWT.serialize() + "=";
wygeneruje poprawnie podpisane JWT które jak wystawisz klucz (rsaJWK.toPublicJWK().toJSONObject()
) to dowolny inny klient JWT (np. jakaś angularowa apka) będzie umiał ten podpis zweryfikować. Ale NIE RÓB TAK chyba że piszesz coś do testów. Absolutnie nie używaj takiego kodu na produkcji!
Jesli już z jakiegoś powodu musisz bez żadnych zewnętrznych zależności, to java ma klasy do podpisów, ale bardzo biedne:
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/Signature.html
Niemniej nadal apeluje: NIE PISZ własnego identity providera, jeszcze robiąc jakieś krypto na kolanie. Albo pisz, ale daj nam potem link do tej aplikacji, żebyśmy mogli zostać milionerami za bug bounty ( ͡° ͜ʖ ͡°) Serio, są otwarte darmowe rozwiązania i należy ich używać.