Encrypt and Decrypt sensitive data with JSON Web Encryption(JWE)
In this blog post, I am going to explain to you about JWE authentication and how we encrypt and decrypt sensitive data. Before point out to the JWE let’s have a quick introduction of his parent object called JWT.
Introduction
JSON web token (JWT) use as a container to transport sensitive data between two different or multiple places(Ex: Client/ Server). This informations can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Mainly a JWT can be used to:
Authorization: This is the main responsibility of the JWT.Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
Information Exchange: JWT is a better way to exchange secure data between multiple parties.Because JWT has 2 options. One is a signed payload using private/public keys and the other one is an encrypted payload.
JWT does not exist itself either it has to be a JWS or a JWE (JSON Web Encryption). It is like an abstract class. The JWS and JWE are the concrete implementations. Simply, JWT is a parent of JWS and JWE.
Previously I have compiled a blog post about JWS. Please visit this JWT(JWS) authentication for Springboot REST API application to get a full idea about JWS behavior and the implementation.
Let’s move to the JWE !.
What is JWE (JSON Web Encryption)?
JSON Web Encryption (JWE) is a means of representing encrypted content using JSON data structures.
A JWE token is built with five key components, each separated by a period (.): JOSE header, JWE Encrypted Key, JWE initialization vector, JWE Additional Authentication Data (AAD), JWE Ciphertext, and JWE Authentication Tag.
Let's have a look one by one.
JOSE header
The JOSE header is the very first element of the JWE token and it stands for JSON Object Signing and Encryption. The JWE specification introduces a number of header parameters. Here I have mentioned some outstanding header elements.
alg: (Required)
Defines the algorithm used to encrypt the Content Encryption Key (CEK). This MUST be set to “RSA-OAEP”.
enc: (Required)
Defines the algorithm used to perform authenticated encryption on the payload to produce the Ciphertext and the Authentication Tag. This MUST be set to “A128CBC-HS256”.
cty: (Required)
Defines the “content type” of the payload. In this case, the value MUST be “JWT”, to indicate that a nested JWT (= our JWS) is carried inside this JWT.
kid: (Optional)
It is a hint indicating which key was used to secure the JWE. The structure of the “kid” value is unspecified. The key hint references the public key with which the JWE was encrypted; this can be used to determine the private key needed to decrypt the JWE.
zip:(Optional)
If token compression is needed, the JSON payload in plaintext must be compressed following the compression algorithm defined under the zip header element.
JWE Encrypted Key
The Content Encryption Key (CEK) is encrypted with the intended recipient’s key and the resulting encrypted content is recorded as a byte array, which is referred to as the JWE Encrypted Key.
The process is JOSE header defines an enc element(content-encryption algorithm) and the alg (encryption algorithm) to encrypt the Content Encryption Key (CEK).
EX : {“alg”:”RSA-OAEP”,”enc”:”A256GCM”}
JWE Initialization Vector
The Initialization Vector is used to introduce randomness in the encryption process. Some encryption algorithms are used for content encryption require an initialization vector, during the encryption process.
An initialization vector is a randomly generated number, which is used along with a secret key to encrypt data.
Ciphertext
The Ciphertext is the encrypted payload itself. Which means plain JSON payload. The JWE ciphertext is computed by encrypting the plaintext JSON payload using the Content Encryption Key (CEK), the JWE initialization vector, and the Additional Authentication Data (AAD) value, with the encryption algorithm defined by the header element enc.
JWE Authenticated Tag
The Authentication Tag is used for integrity checks.
Now we have an overall idea about the components of the JWE token. Let’s have a look at a code example.
Also please note that in this blog post I am going to show you how to generate a JWE token and decrypt the values. Please refer to my previous post about JWS to see the full implementation(with controllers /service layer changes/ validations etc). You can use the same approach to authenticate your application using the JWE token.
Please visit this bitbucket URL to see the full JWE implementation example.
Step 1: Create KeyPair Object
First, we need to initialize the KeyPairGenerator object and defined a key size(2048 bytes). Then we can generate a KeyPair object to call the ‘genKeyPair()’ method.
Step 2: Create KeyFactory and RSA Keys Specs
Here we need to create the ‘KeyFactory’ object first calling the ‘getInstance()’ method with the ‘RSA’ algorithm as a parameter. The next step will be creating RSAPublicKeySpec and RSAPrivateKeySpec.For this, we have to use the ‘getKeySpec()’ method inside of KeyFactory class. The parameters will be,
key: The key.
keySpec: The specification class in which the key material should be returned.
Step 3: Generate (and retrieve) RSA Keys from the KeyFactory using Keys Specs
To generate RSAPublicKey / RSAPrivateKey we have to call the ‘generatePublic’ method and ‘generatePrivate’ method inside of KeyFactory class with parameters called‘publicKeySpec’, ‘privateKeySpec’ respectively.
Please note that when we come to the real project works we are not going to generate public keys and private keys each time. We can keep them in the secured S3 bucket or AWS secret manager(Recommended).
NOTE: Also if you are using ‘PublicKey’ and ‘PrivateKey’ objects you have to cast them from RSAPublicKey and RSAPrivateKey respectively.
Step 4: Set claim values.
We need to create an instance using builder() method where it is defined in JWTClaimsSet class.The JWTClaimsSet class has provided default claims values like,issuer, subject, expirationTime, notBeforeTime,jwtID. Also, it provided to add custom values as well. You can use ‘claim(key,value)’ method to set custom values. It is just like Java java.utill.Map.
Step 5: Create the JWE header and specify RSA-OAEP as the encryption algorithm and 128-bit AES/GCM as the encryption method
Step 6: Create an RSA encrypted with the specified public RSA key and generate a JWE token.
Now let’s see how to decrypt and extract the token claim values.
Step 1: Convert JWE string value into EncryptedJWT type.
Step 2: Create a decrypter with the specified private RSA key.
Step 3: Doing the decryption.
Step 4: Extract sensitive data.
As I mentioned in the above example you can extract the values using the key(It is very similar to accessing java.utill.Map key/values).
Please visit a completed version from here and I have explained every possible step in the comment section. Please leave a comment if you have concerns or questions.