JOSE encryption, understanding JWE/JWS

·

3 min read

Cover Image for JOSE encryption, understanding JWE/JWS

In fintech, asymmetric key encryption has been a cornerstone of security mechanism. Historically PGP has been the solution of choice.

However with the advent of REST based APIs, JOSE (pronounced as ho-ze) is getting popular.

Asymmetric key encryption has two components. Encryption and Signing. Encryption takes the sensitive data and turns it into a encrypted blob. This ensures confidentiality. Signing is a process of taking the plaintext and taking its hash and encrypting that hash. This proves that the sender indeed has access to the plaintext is really who they claim they are.

Of course a detailed discussion of these terms are outside the scope of our article but roughly the objective of both these steps is.

  1. Confidentiality => Only those who need to see the info should be able to see the info.

  2. Non-Repudiation => The party that sent the data should not be able to claim that they did not send it.

  3. Authentication => The party that is sending the data is indeed the one it is claiming it to be.

  4. Integrity => Proof that the data was not modified in flight.

All these things are achieved using a pair of asymmetric keys.

JWTs - JSON Web Tokens

JWTs are json objects that are signed. These are pretty old and widely used to store authentication information. For example, a lot of web applications store a JWT locally and then pass it to authenticate against APIs. A JWT is a simple JSON object that has some information and a signature of that information. It is the signature that allows the server to verify that the values in the JWT are not modified.

You can see example JWTs on https://jwt.io/

JWKs - JSON Web Keys

JWKs are a modified form of JWT, but instead of containing data, JWKs represent actual encryption keys. This is similar to how PEM files are used in PGP.

A JWK can be either a public key (which can be shared with everyone) or a private key (which must never be shared).

JWTs are unrelated to JWKs, but they are similar in how they are formatted.

JWE - JSON Web Encryption

JWE stands for JSON Web Encryption. It's a standard (RFC 7516) for encrypting data using a JSON-based format. Think of it as a secure way to package and send sensitive information.

Visa’s APIS use JWE and have a helpful article on the topic.

JWE is also a JSON object that has two main properties. A header and a ciphertext.

Example header:

{ "alg": "RSA-OAEP", "enc": "A256GCM" }

Which is followed by a ciphertext and an authentication tag. The eventual base64 encoded blob would look like this:

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrLxFhOxjzKbPjMYO8CSDmBIkar_.n-jWmBI1qkDpWX76CJbggw.cikH7aYKZ-BxsVA.zHsHHy2JJzFsRz7xgZEyRlW_yfV_KeGjzHm7I6cPl0NJjCcTJhk72rK9yf5fB1nXFA5bhSjDpdpqZ_yEI2F2MaZ28w0arijmP7H5QtvAaKuby6qAcIJsL0Q.KVajRvfQ2rCiMpvAJB4mAQ

The three components are base64 enconded and appended using . as a separator.

JWS - JSON Web Signature

JWS is very similar to JWE but it is used to sign some content rather than to encrypt.

A typical JWS payload looks like this

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

When decoded from base64, we get the individual fields.

Header:

{ "alg": "HS256", "typ": "JWT" }

Payload:

{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }

Entire JWS

{ "payload": "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", "signature": "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "header": { "alg": "HS256", "typ": "JWT" }, "protected": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" }

Combination of JWE/JWS

Most of the times we will end up using both JWE and JWS.

The payload is typically signed first and then encrypted.

JWE(JWS(payload))

This proves that the one who signed the content had access to the plaintext payload.

Implementation

All major programming languages have well established an mature libraries to support JOSE encryption mechanism and generally it is pretty trivial to implement.

from jose import jwt, jws

# Sample data
payload = {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
secret_key = "your-secret-key"  # Replace with your actual secret key

# JWS Signing
jws_token = jws.sign(payload, secret_key, algorithm="HS256")
print("JWS Token:", jws_token)

# JWS Verification
try:
    decoded_payload = jws.verify(jws_token, secret_key, algorithms=["HS256"])
    print("Verified Payload:", decoded_payload)
except jws.JWTError as e:
    print("Verification failed:", e)