Un JWT es algo pequeño con un alcance largo. Es la prueba de identidad entre sus servicios, y el algoritmo que lo firma suele ser la parte de un sistema que más tarda en cambiar, porque todos los emisores y todos los verificadores tienen que moverse a la vez. Por eso merece la pena saber que la opción existe antes de necesitarla.
sgcWebSockets 2026.10 añade al cliente y al servidor JWT los algoritmos JWT poscuánticos de RFC 9964: ML-DSA-44, ML-DSA-65 y ML-DSA-87. Se colocan junto a HS, RS y ES como tres valores más de la misma propiedad, y todo lo demás en su token sigue igual.
Emitir un token
Elija el algoritmo en la cabecera, dé al componente una clave privada y firme:
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;
La clave privada es un PEM PKCS#8 corriente. Se leen las tres formas que define RFC 9881, así que una clave almacenada como la semilla de 32 bytes, como la clave expandida, o como ambas, funciona en cualquier caso.
Validar uno
El lado del servidor tiene la misma forma, con la clave pública y la familia activadas:
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);
Cada familia se activa por separado, así que un despliegue que solo emite tokens ML-DSA puede desactivar las demás, y un token que llega con alg establecido a otra cosa se rechaza en lugar de comprobarse.
El conjunto de parámetros debe coincidir
Hay una regla en RFC 9964 que merece decirse en voz alta, porque es el error que de otro modo pasaría inadvertido: la clave tiene que pertenecer al conjunto de parámetros que nombra alg. Una clave que no coincide se rechaza. Firmar con la que no corresponde genera una excepción, y validar con la que no corresponde devuelve falso en lugar de generar una excepción, de modo que un atacante no puede usar esa diferencia para aprender nada.
Claves como JSON Web Keys
RFC 9964 registra un tipo de clave para estos algoritmos, AKP, y la biblioteca lo lee y lo escribe, que es lo que interesa cuando las claves se publican en un endpoint JWKS en lugar de dejarse en un archivo:
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;
El miembro privado es la semilla de 32 bytes, que es lo que define el RFC, y el lector es estricto al respecto: los miembros deben ser base64url canónico de exactamente la longitud correcta, alg es obligatorio, y cuando hay una clave privada presente debe coincidir con la pública en la misma JWK. Una JWK que falla en cualquiera de esos puntos se rechaza en lugar de cargarse a medias.
Interoperabilidad, demostrada
Un formato de firma solo sirve si otra implementación produce los mismos bytes. Dos comprobaciones se distribuyen como pruebas, no como afirmaciones:
- Los ejemplos publicados de RFC 9964 se reproducen byte a byte. El firmante es la variante determinista, así que la misma clave y la misma entrada dan siempre la misma firma, y el apéndice del RFC se convierte en algo sobre lo que se puede hacer una aserción.
- Los tokens producidos por una implementación independiente, en este caso la biblioteca Python dilithium-py, se validan aquí, y los contenedores de claves escritos a mano a partir de RFC 9881 hacen el viaje de ida y vuelta sin cambios.
Ya que estábamos: un secreto HMAC binario
Una limitación sin relación desapareció en la misma pasada. Los algoritmos HS recibían su clave como texto, y una clave HMAC real son bytes aleatorios, la mayoría de los cuales no son texto válido en absoluto. Ahora hay una segunda propiedad que recibe la clave como base64url, la misma codificación que usa el miembro k de una JWK oct:
oJWT.JWTOptions.Algorithms.HS.SecretBase64URL := 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ...';
Cuando se establece, tiene prioridad sobre el secreto de texto, tanto en el cliente como en el servidor.
Claims escritos por otra persona
Una corrección más que conviene conocer si acepta tokens de otros emisores. Una cadena JSON puede escribirse como UTF-8 sin escapar o con todos los caracteres no ASCII escapados, y distintas bibliotecas eligen de forma distinta. Ahora ambas formas se resuelven en el mismo texto, así que un nombre con acento se lee correctamente sea cual sea el emisor que lo produjo, y una cabecera alg escrita con escapes se reconoce como el algoritmo que representa.
Actualizar
Los tres algoritmos se añaden al final de la lista existente, así que un ordinal almacenado conserva su significado y nada de lo que ya hace cambia. Firmar con ML-DSA necesita el paquete sgcCrypto, y una compilación sin él rechaza esos tokens con un mensaje claro en lugar de fallar de forma oscura.
Siga leyendo
- Criptografía poscuántica en Delphi
- Delphi TLS 1.3 sin DLLs de OpenSSL
- Cliente JWT en Delphi y Servidor JWT en Delphi, lo básico
Míralo
Hay un vídeo corto sobre esto en el canal de eSeGeCe.
¿Preguntas, comentarios o ayuda con la migración? Póngase en contacto — recibirá respuesta de las personas que escribieron el código.
