Protect Your REST APIs and PII with JWT
Simple way to Protect your REST APIs and PII (Personally Identifiable Information) using JWT Tokens.
How it works
To protect your customer data (PII like card number) that is exchanged between two parties/channels JWT Token are helpful.
HTTPS only provides network level security but data still visible to the uses who gains authorization to the system.
$/> curl https://yourdomain.com/customers
[
{
"id": 1,
"name": "James",
"social": "999-99-9999",
"phonoe": "111-111-1111",
}
]
Mask Sensitive information of API response for data security.
$/> curl https://yourdomain.com/customers
[
{
"id": "Q-VQ7HafDZ8aD6J_UJD62G3iUMyZBSGguf3pSBnLCgQh7FhA==",
"name": "James",
"social": "####-##-9999",
"phonoe": "XXX-XXXX-1111",
}
]
Steps to implement JWT Library
Create simple POJO With Sensitive Information Attributes
Create simple POJO class which contains sensitive information you want to protect.
This helps you develop REST API which contains sensitive information which are accessible by different customers and also protects your API by enabling token based authentication.
PersonalInfo
contains
-
Social (sensitive)
-
Phone Number (sensitive information)
-
Card Number (sensitive)
package com.tvajjala.security.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.tvajjala.security.annotation.Obfuscate;
import com.tvajjala.security.constants.Strategy;
import java.time.LocalDate;
/**
* object that contains sensitive information
*
* @author ThirupathiReddy Vajjala
*/
public class CustomerInfo extends WebToken {
@Obfuscate(strategy = Strategy.CARD)
@JsonProperty("card")
private String card;
@JsonProperty("ssn")
@Obfuscate(strategy = Strategy.SSN)
private String social;
@JsonProperty("phn")
@Obfuscate(strategy = Strategy.PHONE)
private String phone;
public String getCard() {
return card;
}
public void setCard(final String card) {
this.card = card;
}
public String getSocial() {
return social;
}
public void setSocial(final String social) {
this.social = social;
}
public String getPhone() {
return phone;
}
public void setPhone(final String phone) {
this.phone = phone;
}
}
Create Base Class
This based class should be used by JWT library to place sensitive information in encrypted format. and also contains attributes which verifies token expiration time , generation time and issuer.
Tip
|
read more about JWT specification supported claims here and https://jwt.io/introduction/ |
package com.tvajjala.security.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class WebToken {
@JsonProperty("sub")
private String subject;
@JsonProperty("iat")
private final long issuedAt = Instant.now().getEpochSecond();
@JsonProperty("exp")
private final long expiredAt = Instant.now().minus(15, ChronoUnit.MINUTES).getEpochSecond();
public String getSubject() {
return subject;
}
public void setSubject(final String subject) {
this.subject = subject;
}
}
Create Enum to Support Different Types
Each sensitive information has separate format and length, to support different masking patterns , this ENUM will helpful.
/**
* @author ThirupathiReddy Vajjala
*/
public enum MaskingStrategy {
PHONE, SSN, ACCOUNT, DEFAULT
}
Create Annotation which you add it your POJO attributes.
This annotate will helps seamless encryption and decryption your sensitive information .
package com.tvajjala.security.annotation;
import com.tvajjala.security.constants.Strategy;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author ThirupathiReddy Vajjala
*/
@Retention(RetentionPolicy.RUNTIME)//available source, compile and runtime
@Target(ElementType.FIELD)//add this annotation on field
public @interface Obfuscate {
/**
* masking strategy
*
* @return
*/
Strategy strategy() default Strategy.DEFAULT;
/**
* encrypt the content
*
* @return true or false , default it is true
*/
boolean enc() default true;
}
add @Obfuscate
annotation to attributes which has sensitive information.
Tip
|
annotation processor inside this utility will read all attributes and encrypt and add it to JWT subject .
|
perform signing on this JSON payload with secretKey
Note
|
nimbusds library used to generate token and signing and verifying token
|
Generate Symmetric Encryption Keys
package com.tvajjala.security.util;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.SecureRandom;
import java.util.Base64;
public class KeyGeneration {
public static void main(String[] args) throws Exception{
SecureRandom random = new SecureRandom();
byte[] sharedSecret = new byte[32];
random.nextBytes(sharedSecret);
System.out.println("signingKey "+ Base64.getUrlEncoder().encodeToString(sharedSecret));
byte[] iv = new byte[16];
random.nextBytes(iv);
System.out.println("IV key: "+Base64.getUrlEncoder().encodeToString(iv));
KeyGenerator keyGenerator=KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey= keyGenerator.generateKey();
System.out.println("dataKey : "+Base64.getUrlEncoder().encodeToString(secretKey.getEncoded()));
}
}
Note
|
These symmetric keys shared between two parties. |
# Signing and verifying
siginingKey= VDaYcuLDYcX1x88tyBAFSAKvqfZTATAC2pn1umqoRjE=
# Encrypting Data
dataEncryptionKey= aflyv0OvtWOlstbS1Udc0IYS+fD3Vq+FXyai2cBE2+k=
#Initialization Vector of data encryption
iv= GYym8ew1diYNl6Fv86ECg==
Client side token generating and signing
byte[] sharedSecret=Base64
.getDecoder()
.decode("VDaYcuLDYcX1x88tyBAFSAKvqfZTATAC2pn1umqoRjE=");
JWSSigner signer = new MACSigner(sharedSecret);
JWSObject jwsObject = new JWSObject(new JWSHeader(JWSAlgorithm.HS256), new Payload(jsonPayload));
// Apply the HMAC siginig
jwsObject.sign(signer); (1)
-
Client side Signing
Server side token verification
JWSVerifier verifier = new MACVerifier(sharedSecret);
jwsObject.verify(verifier);// if TRUE - trusted source (1)
-
Verify the token at server side, it could be Servlet Filter.
Masking Strategies
public enum Strategy {
PHONE("xxx-xxx-1234"),
SSN("xxx-xxx-9999"),
ACCOUNT_NUMBER("xxxxxx1234"),
CREDIT_CARD("xxxxxxxxxxxx1234"),
DEFAULT
}
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJRLVZRN0hhZkRaOGFENkpfZUlDa1hIWG5UckNqZVExeEtlOEIyQ3p6c2VjRjkyeE9kUDdLQV9VSkQ2MkczaVVNeVpCU0dndWYzcFNCbkxDZ1FoN0ZoQT09IiwiaWF0IjoxNTkzNjE4ODk3LCJleHAiOjE1OTM2MTc5OTcsInRybiI6IlhYWFhYWFhYWFg4Mzg4MzgiLCJzc24iOiIjIyMtIyMtOTk5OSJ9.0DGMFJ2KKKmsCHcd5H32JXbh_1DyNRatDv0TgfNjy3Q
Tip
|
Point your browser to https://jwt.io/ and paste above token and see how it looks like.
|
{
"sub": "Q-VQ7HafDZ8aD6J_eICkXHXnTrCjeQ1xKe8B2CzzsecF92xOdP7KA_UJD62G3iUMyZBSGguf3pSBnLCgQh7FhA==", (1)
"iat": 1593618897, (2)
"exp": 1593617997, (3)
"trn": "XXXXXXXXXX838838", (4)
"ssn": "###-##-9999" (5)
}
-
Subject contains sensitive information in encrypted format
-
Issued Date of the token
-
Expiry Date of the token
-
Sensitive information in masked format Ex: card number
-
Sensitive information in masked format Ex: Social
Authorization Header
Pass this token as Authorization header for downstream calls.
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJRLVZRN0hhZkRaOGFENkpfZUlDa1hIWG5UckNqZVExeEtlOEIyQ3p6c2VjRjkyeE9kUDdLQV9VSkQ2MkczaVVNeVpCU0dndWYzcFNCbkxDZ1FoN0ZoQT09IiwiaWF0IjoxNTkzNjE4ODk3LCJleHAiOjE1OTM2MTc5OTcsInRybiI6IlhYWFhYWFhYWFg4Mzg4MzgiLCJzc24iOiIjIyMtIyMtOTk5OSJ9.0DGMFJ2KKKmsCHcd5H32JXbh_1DyNRatDv0TgfNjy3Q
Comments
Post a Comment