JWT (Json Web Token)

JSON Web Token (JWT) is a compact claims representation format intended for space constrained environments such as HTTP Authorization headers and URI query parameters. JWTs encode claims to be transmitted as a JavaScript Object Notation (JSON) object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or MACed and/or encrypted.

Supported algorithms

Here is a table of supported algorithms for signing JWT claims using JWS (Json Web Signature):

Algorithm name Hash algorithms Keywords Priv/Pub Key?
Elliptic Curve DSA sha256, sha512 :es256, :es512 Yes
Edwards Curve DSA sha512 :eddsa Yes
RSASSA PSS sha256, sha512 :ps256, :ps512 Yes
RSASSA PKCS1 v1_5 sha256, sha512 :rs256, :rs512 Yes
HMAC sha256*, sha512 :hs256, :hs512 No

The JWE (Json Web Encryption) in difference to JWS uses two types of algoritms: key encryption algorithms and content encryption algorithms.

The key encryption algorithms are responsible to encrypt the key that will be used for encrypt the content. This is a table that exposes the currently supported Key Encryption Algorithms (specified in JWA RFC):

Algorithm name Decription Keyword Shared Key Size
DIR Direct use of a shared symmetric key :dir (depends on content encryption algorithm)
A128KW AES128 Key Wrap :a128kw 16 bytes
A192KW AES192 Key Wrap :a192kw 24 bytes
A256KW AES256 Key Wrap :a256kw 32 bytes
RSA1_5 RSA PKCS1 V1_5 :rsa1_5 Asymetric key pair
RSA-OAEP RSA OAEP with SHA1 :rsa-oaep Asymetric key pair
RSA-OAEP-256 RSA OAEP with SHA256 :rsa-oaep-256 Asymetric key pair

The content encryption algoritms are responsible to encrypt the content. This is a table that exposes the currently supported Content Encryption Algorithms (all specified in the JWA RFC):

Algorithm name Description Keyword Shared Key Size
A128CBC-HS256 AES128 with CBC mode and HMAC-SHA256 :a128cbc-hs256 32 bytes
A192CBC-HS384 AES192 with CBC mode and HMAC-SHA384 :a192cbc-hs384 48 bytes
A256CBC-HS512 AES256 with CBC mode and HMAC-SHA512 :a256cbc-hs512 64 bytes
A128GCM AES128 with GCM mode :a128gcm 16 bytes
A192GCM AES192 with GCM mode :a192gcm 24 bytes
A256GCM AES256 with GCM mode :a256gcm 32 bytes

Signing data

Let’s start with signing data. For it we will use the sign function from buddy.sign.jws namespace, and the hs256 algorithm for signing:

(require '[buddy.sign.jwt :as jwt])

(jwt/sign {:userid 1} "secret")
;; "eyJ0eXAiOiJKV1MiLCJhbGciOiJIU..."

The sign function return a encoded and signed token as plain String instance or an exception in case of something goes wrong. As you can observe, no algorithm is passed as parameter. In this situation the default one will be used, and in this case is :hs256.

NOTE: Due to the nature of the storage format, the input is restricted mainly to json objects in the current version.

Unsigning data

It’s time to unsign data. That process consists on verify the signature of incoming data and return the plain data (without signature). For it we will use the unsign function from buddy.sign.jwt namespace:

(jwt/unsign data "secret")
;; => {:userid 1}

Claims validation

buddy-sign JWT implements validation of a concrete subset of claims: iat (issue time), exp (expiration time), nbf (not before), iss (issuer) and aud (audience).

The validation is performed on decoding the token. If :exp claim is found and is posterior to the current date time (UTC) an validation exception will be raised. Alternatively, the time to validate token against can be specified as :now option to unsign.

Additionally, if you want to provide some leeway for the claims validation, you can pass the :leeway option to the unsign function.

Let’s see an example using direct api:

(require '[clj-time.core :as time])

;; Define claims with `:exp` key
(def claims
  {:user 1 :exp (time/plus (time/now) (time/seconds 5))})

;; Serialize and sign a token with previously defined claims
(def token (jwt/sign claims "key"))

;; wait 5 seconds and try unsign it

(jwt/unsign token "key")
;; => ExceptionInfo "Token is older than :exp (1427836475)"

;; use timestamp in the past
(jwt/unsign token "key" {:now (time/minus (time/now) (time/seconds 5))})
;; => {:user 1}

Encrypting data

Let’s start with encrypting data. For it we will use the encrypt function from the buddy.sign.jwt namespace:

(require '[buddy.sign.jwt :as jwt])
(require '[buddy.core.hash :as hash])

;; Hash your secret key with sha256 for
;; create a byte array of 32 bytes because
;; is a requirement for default content
;; encryption algorithm

(def secret (hash/sha256 "mysecret"))

;; Encrypt it using the previously
;; hashed key

(jwt/encrypt {:userid 1} secret {:alg :dir :enc :a128cbc-hs256})
;; "eyJ0eXAiOiJKV1MiLCJhbGciOiJIU..."

The encrypt function, like sign from JWT, returns a plain string with encrypted and encoded content using a provided algorithm and shared secret key.

Decrypting Data

The decrypt is a inverse process, that takes encrypted data and the shared key, and returns the plain data. For it, buddy-sign exposes the decrypt function. Let see how you can use it:

(jwt/decrypt incoming-data secret)
;; => {:userid 1}

Digital signature algorithms

In order to use any of digital signature algorithms you must have a private/public key. If you don’t have one, don’t worry, it is very easy to generate it using openssl (look on FAQ).

Having generated a key pair, you can sign your messages using one of supported digital signature algorithms.

Example of signing a string using es256 (eliptic curve dsa) algorithm:

(require '[buddy.core.keys :as keys])

;; Create keys instances
(def ec-privkey (keys/private-key "ecprivkey.pem"))
(def ec-pubkey (keys/public-key "ecpubkey.pem"))

;; Use them like plain secret password with hmac algorithms for sign
(def signed-data (jwt/sign {:foo "bar"} ec-privkey {:alg :es256}))

;; And unsign
(def unsigned-data (jwt/unsign signed-data ec-pubkey {:alg :es256}))

Asymetric encryption

In order to use any asymetric encryption algorithm, you should have private/public key pair. If you don’t have one, don’t worry, it is very easy to generate it using openssl (look on FAQ).

Then, having ready the key pair, you can start using one of the supported key encryption algorithms in the JWE specification such as :rsa1_5, :rsa-oaep or :rsa-oaep-256.

Let see an demonstration example:

(require '[buddy.core.keys :as keys])

;; Create keys instances
(def privkey (keys/private-key "privkey.pem"))
(def pubkey (keys/public-key "pubkey.pem"))

;; Encrypt data
(def encrypted-data (jwt/encrypt {:foo "bar"} pubkey
                                 {:alg :rsa-oaep
                                  :enc :a128cbc-hs256})

;; Decrypted
(def decrypted-data (jwt/decrypt encrypted-data privkey
                                 {:alg :rsa-oaep
                                  :enc :a128cbc-hs256}))