JWT Signatures and EdDSA
On this page
This article describes the use of the EdDSA signature algorithm in JOSE, in particular in conjunction with JWTs. JSON Object Signing and Encryption (JOSE) is a framework of technologies and standards for signing and encrypting JSON objects. The list includes the following specifications:
- JSON Web Tokens (JWT)
- JSON Web Signatures (JWS)
- JSON Web Encryption (JWE)
- JSON Web Algorithms (JWA)
- JSON Web Key (JWK)
The framework is extensible. RFC8037, for example, describes how to apply EdDSA in JOSE and as part of that specification defines a new key type. Values, members and parameters are registered and maintained by IANA in the registries for JOSE.
JWT and JWS
A very popular format for tokens is the JSON Web Token (JWT) format. The standard defines a JSON structure for tokens as well as common claims. It also provides instructions on how to apply other JOSE standards, such as JSON Web Signatures (JWS) for signing tokens.
JSON Web Signatures can be serialized using either the JWS Compact Serialization or the JWS JSON Serialization. Signed JWTs are always serialized in a URL-safe manner using the JWS Compact Serialization. The result is a sequence of URL-safe parts separated by period ('.
') characters.
In technical terms, a signed JWT is constructed the following way:
BASE64URL(UTF8(JWS Protected Header)) + '.' +BASE64URL(UTF8(JWT Claims Set) + '.' +BASE64URL(JWS Signature)
The first part represents the JOSE header that describes the signature algorithm used to generate the signature. Consequently, the header must at least contain the alg
parameter.
In a self-contained JWT the header contains additional information required by a receiver to validate the JWT. In this case the header may for example also include the kid
, x5t
or jwk
parameter.
Since the header is part of the signature, it is also referred to as the JWS Protected Header.
The second part of a JWT is called the JWS Payload. It contains the message, that is the JSON object called the JWT Claims Set. Examples for claims that the payload may contain are the iss
, sub
and aud
claims. The Curity Identity Server allows you to add any other claim to a token.
The third and final part of the JWT is the signature value. The signature covers both the header and payload of the JWT. It is calculated using the algorithm specified in the alg
parameter in the header.
JWA
The original list of algorithms used with JOSE is defined in JSON Web Algorithms (JWA), which also specifies the registration process for new algorithms. In other words, the value in the alg
parameter must be registered in the IANA "JSON Web Signature and Encryption Algorithms" registry. EdDSA
was registered as a value for the EdDSA algorithm as part of specifying EdDSA for JOSE.
It is best practice for an application only accepting secure algorithms. An application should specify and list the supported algorithms that are considered secure in the application's context. The following table shows an excerpt of registered values for signatures based on a public key algorithm like RSA, Elliptic Curve DSA (ECDSA) or EdDSA.
Algorithm name | Algorithm Description |
---|---|
RS256 | RSASSA-PKCS1-v1_5 using SHA-256 |
PS256 | RSASSA-PSS using SHA-256 and MGF1 with SHA-256 |
ES256 | ECDSA using P-256 and SHA-256 |
EdDSA | EdDSA signature algorithms |
Compliance
Standards and regulations typically include requirements regarding algorithms. For example, the Financial-Grade API Security Profile 1.0 Part 2, the FAPI 1.0 Advanced Profile, requires PS256 or ES256 for signatures. In particular, it explicitly discourages the use of RS256. The draft specification for FAPI 2.0 includes EdDSA in the list of approved algorithms.
JWK
Public key algorithms are based on a key pair. A key pair consists of a secret part, the private key, and a public part, the public key. A signature is created using the private key and verified with the public key. Thus, only the entity that possesses the private key can sign a JWT but anybody with the public key can verify the signature.
There are different formats to encode a key (or key pair). One example is PKCS #8, another one is the JSON Web Key (JWK) format.
The JWK format defines a JSON data structure for representing a key. The members of the JSON object are the parameters and properties of the key. Which parameters a key has and how they are encoded depends on the key type. The key type is stored in the kty
parameter. Its value depends on the algorithm that the key is supposed to be used with. For example, "kty": "EC"
defines a key for an elliptic curve, "kty": "RSA"
is a key type to be used with RSA and EdDSA keys have "kty": "OKP"
.
EdDSA JWK Encoding
EdDSA is a signature scheme that is instantiated with recommended parameters for the Edward's Curves Ed25519 and Ed448. The private key is an integer and the public key is a point (x,y) on the curve. Despite EdDSA operating on elliptic curves, it uses a different signature scheme and different encoding rules than ECDSA.
EdDSA only encodes the y-coordinate of a curve point. Since there are two points on a curve with the same y-coordinate, one bit in the encoding is reserved to store the x-coordinate of the point as well. Consequently, an EdDSA public key is represented by a single parameter though it represents a point with two coordinates. This becomes clearly visible in the JWK format of an EdDSA public key, where the parameter x
holds the encoded public key:
"kty": "OKP","kid": "-1909572257","alg": "EdDSA","crv": "Ed25519","x": "XWxGtApfcqmKI7p0OKnF5JSEWMVoLsytFXLEP7xZ_l8"
To be able to encode an EdDSA key as a JWK, a new key type was introduced: OKP
, the Octet Key Pair. The crv
parameter contains the name of the curve, that is either Ed25519
or Ed448
. The parameter x
contains the encoded public key, an octet stream that is the little-endian encoding of the public key point on the curve. Optionally, the JWK may also clarify via the alg
parameter that this key is supposed to be used with the signing algorithm EdDSA
though this information is redundant in this case.
Knowing the details of EdDSA encoding allows a developer to better understand cryptographic interfaces. Study the following code for retrieving an EdDSA public key in Java as an example:
boolean xBit = ... // Bit for the x-coordinateBigInteger y = ... // y-coordinate// Load parameters from Ed25519NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");EdECPublicKeySpec pubSpec = new EdECPublicKeySpec(paramSpec, new EdPoint(xBit, y));// Generate an EdDSA Public Key from the point on Ed25519KeyFactory kf = KeyFactory.getInstance("EdDSA");PublicKey pubKey = kf.generatePublic(pubSpec);
The abstract above shows how the encoding of the public key (boolean xBit
and BigInteger y
), the recommended parameters (predefined as Ed25519
) and the algorithm name (EdDSA
) fit together.
To get the values for xBit
and y
in the Java example, decode the public key x
from the JWK according to the specification:
// Byte array is in little endian encoding// The most significant bit in final octet indicates if X is negative or not:boolean xBit = (publicKeyBytes[b-1] & 0x80) != 0;// Recover y value by clearing x-bit.publicKeyBytes[b-1] = (byte)(publicKeyBytes[b-1] & 0x7f);// Switch to big endian encodingbyte[] publicKeyBytesBE = new byte[b];for(int i = 0; i < b; i++) {publicKeyBytesBE[i] = publicKeyBytes[b-1-i];}BigInteger y = new BigInteger(1, publicKeyBytesBE);
Now use the variables xBit
and y
to generate a Public Key
instance for the EdDSA key.
Keysize
EdDSA keys are much smaller than RSA keys. FAPI 1.0 requires 2048-bits or more for RSA keys and at least 160-bits for elliptic curve keys. The key length, that is the length of the private key, for Ed25519 is 256-bits and 456-bits for Ed448. Thus, EdDSA is suitable for Financial-Grade security. Consequently, EdDSA is listed as one of the approved algorithms in the draft specification of FAPI 2.0.
EdDSA JWS and JWT
Signing
A signed JWT has three parts:
- JWS Protected Header
- JWS Payload (JWT Claims Set)
- JWS Signature
Both the JWS Protected Header and the JWT Claims Set are signed.
First, prepare the input for the signature. Start with the JWS Protected Header. Set alg
to EdDSA
as this is the algorithm that will be used to create the signature. Specify the key used for signing as well because it is useful later when verifying the signature.
{"alg": "EdDSA","kid": "-1909572257"}
Encode the header using the Base64URL encoding. The result for this example is:
eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0
Then, prepare the message to sign. In the case of a signed JWT, the payload is the JWT Claims Set. The following example shows the claims of an access token created by the Curity Identity Server:
{"jti": "22916f3c-9093-4813-8397-f10e6b704b68","delegationId": "b4ae47a7-625a-4630-9727-45764a712cce","exp": 1655279109,"nbf": 1655278809,"scope": "read openid","iss": "https://idsvr.example.com","sub": "username","aud": "api.example.com","iat": 1655278809,"purpose": "access_token"}
The payload is also Base64URL encoded. The following string is the encoded version of the claims from the access token:
eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9
Combine the JWS Protected Header and JWT Claims Set by concatenating the Base64URL encoded strings. The result is the JWS signing input (line break for readability only):
eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0.eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9
Finally, calculate the EdDSA signature over the JWS signing input using the private key. In Java, the code would look similar to the following:
PrivateKey privateKey = ...;String jwsSigningInput = "eyJra...lbiJ9";Signature sigAlg = Signature.getInstance("Ed25519");sigAlg.initSign(privateKey);sigAlg.update(jwsSigningInput.getBytes(StandardCharsets.US_ASCII));byte[] signature = sigAlg.sign();
The signature is also Base64URL encoded. In this example the encoded signature results in the following string:
rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg
Append the signature to the JWS signing input. The final JWT is a signed JWT. The result of the example is shown as follows (line breaks for readability only):
eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0.eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9.rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg
Tokensize
EdDSA signatures have a length of 512-bits for Ed25519 or 912-bits for Ed448. In comparison, RSA signatures are as long as the key, that is at least 2048-bits (i.e. about 2-4 times bigger). As a result, an EdDSA token is significantly smaller than one signed with PS256, for example.
Verifying
When verifying the signature of a JWT, first determine the algorithm and, if provided, the key from the JWS Protected Header of the JWT. Split the JWT at the period ('.
') characters. The first part is the header. Decode the header with Base64URL. The header may look similar to the following:
{"alg": "EdDSA","kid": "-1909572257"}
Check the algorithm against a list of secure and supported algorithms for the application and context. In this case, alg
must match EdDSA
for an EdDSA signature.
Then look up the public key that matches the algorithm. In the example above, the header provides a key identifier for easy lookup:
"kty": "OKP","kid": "-1909572257","alg": "EdDSA","crv": "Ed25519","x": "XWxGtApfcqmKI7p0OKnF5JSEWMVoLsytFXLEP7xZ_l8"
The key is an EdDSA key, easily identified by the alg
parameter in this case (kty
and crv
in other cases). The crv
parameter contains the EdDSA variant Ed25519
that the key matches. This information is required for the signature verification.
Remember, that both the header and payload were signed. Thus, the input for signature verification is simply the JWT without the signature part. In this example, the input is the following string (line break for readability only):
eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0.eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9
Now verify the signature using Ed25519
as this is the EdDSA variant of the key. In Java, the code would look similar to the following:
PublicKey publicKey = ...;String jwtWithoutSignature = "eyJra...lbiJ9";String signature = "rjeE8...";// "Ed25519" defines the parameters for EdDSA and must match public keySignature sigAlg = Signature.getInstance("Ed25519");// Use the public key for verificationsigAlg.initVerify(publicKey)// Provide the message that was signedsigAlg.update(jwtWithoutSignature.getBytes(StandardCharsets.US_ASCII));// Verify the signature against the message and public keysigAlg.verify(Base64.getUrlDecoder().decode(signature));
Note, that there is a lot more to think about when validating a token than just verifying the signature. Refer to JWT Best Practices for guidance.
EdDSA vs RSA and ECDSA
EdDSA is known as a high performance signature algorithm with small key sizes and signatures. Using EdDSA signatures over RSA saves time, money and resources. EdDSA is also more secure than RSA.
EdDSA is deterministic and does not depend on a random number generator for security. The requirement of a nonce and random number generator to create one has lead to known vulnerabilities in ECDSA implementations in the past. In addition, EdDSA does not require expensive point validation for corner cases. Consequently, EdDSA is more secure than ECDSA because it is simply easier to implement.
As long as there are no other requirements that dictate the use of certain signature algorithms, switch to EdDSA if you can. It's fast, it's secure, and it's green.
Conclusion
EdDSA uses elliptic curves but is not compatible with ECDSA. This fact may be confusing but with some basic understanding about the parameters of an EdDSA key, the signature algorithm is easy to work with. Thanks to the EdDSA for JOSE specification it can be used in a standardized way to produce lightweight and secure signatures for JWTs.
Judith Kahrer
Product Marketing Engineer at Curity
Join our Newsletter
Get the latest on identity management, API Security and authentication straight to your inbox.
Start Free Trial
Try the Curity Identity Server for Free. Get up and running in 10 minutes.
Start Free Trial