Combining RSA + AES Encryption to secure REST Endpoint With Sensitive Data

--

There are so many links out there who explains about encryption, particularly symmetric encryption using AES, and asymmetric encryption using RSA. For the sake of coding implementation, I will re-write them here, as simple (and short) as possible.

There are basically some important terms:

# Encryption

you want to provide data to your partner, but don’t want anybody else know what the data is -only you and your partner can know the data-. So you encrypt the data. Instead of sending plain data (e.g. I Love You), you and your partner have a secret key to mask the plain data. Suppose the secret key is XXX (only you and your partner know it). You encrypt the plain data using the secret key, so it masked and became MFf$2jr0akb3302Amr02+53mZa9rYsA2j9f. You send this masked string (encrypted data) to your partner.

# Decryption

The other side of coin. Your partner receive string MFf$2jr0akb3302Amr02+53mZa9rYsA2j9f (encrypted data). Using secret key (XXX), your partner decrypt the encrypted data, so it became plain string I Love You. Encrypted data can only decrypted back using matching key. A wrong key will not show the plain data.

# Symmetric Encryption

You and your partner has same one key (XXX) for encryption / decryption.

# Asymmetric Encryption

You are not not really good at keeping secret, and the key (XXX) might be leaked. Your partner worry that someone will have the key (XXX), steal encrypted message you sent, then use the leaked key and decrypt the message. So your partner decide to keep the key (she is really good at keeping secret!).

Problem is, you still need to encrypt the data then send encrypted data. But you cannot has the key (XXX). Your partner then provide asymmetric key. Your partner has pair of key :

  • public key PP1 (which she said: Just share it to everyone, it is public anyway, anybody can know).
  • she also has private key QQ1 (which she keeps by herself, thus called as private key)
  • This PP1 and QQ1 key pair is irreplacable each other. So if “someone” has PP1 (it is public anyway), but does not has QQ1, this “someone” cannot decrypt the message. The way asymmetric encryption works, the pair must be exact (PP1-QQ1, but not PP2-QQ1 or PP1-QQ2)

You use public key PP1 to encrypt the plain message (I Love You), resulting 590–2nnZas+21v0j5LAia90AmKf\jil2b4sG90.sa9F95z which you send yo your partner.

Your partner use the matching private key (QQ1) to decrypt 590–2nnZas+21v0j5LAia90AmKf\jil2b4sG90.sa9F95z and she gets I Love You

“Someone” intercept the message, gets 590–2nnZas+21v0j5LAia90AmKf\jil2b4sG90.sa9F95z. This “someone” has PP1 key (it is public, remember), but he does not has QQ1 private key. This “someone” cannot use PP1 to decrypt it. The only person can decrypt the message is your partner (she is the only one who has QQ1 private key).

Problem solved!

# AES Encryption

Popular symmetric encryption. Can encrypt large data.

# RSA Encryption

Popular asymmetric encryption. Can only encrypt limited amount of data.

# Use Case on Data Interchange using REST API

You are a company, and you have partner company. Your job : provide customer data (email, phone, and 50 more fields) in form of json. You have partner company. The customer data is considered sensitive, and only partner company can know. So when your partner access https://you.com/api/{customerId} you need to provide encrypted customer data. Somebody intercept the traffic and get the data, they will get only encrypted data.

You and partner company cannot trust anyone for encrypting such sensitive data. Only your partner can decrypt the data. So symmetric encryption cannot be used. To resolve this, in a way, asymmetric encryption must take place. Problem : asymmetric RSA encryption an only encrypt limited amount of data, not the whole 50+ fields. So you need to encrypt the full data using symmetric encryption (AES), AND put a way to utilise asymmetric encryption (RSA). Your company partner will owns the RSA public-private key, and send only the public key to you to encrypt data.

This is how it should works.

  1. Your partner owns public key PP9 and private key QQ9
  2. Your partner hit GET https://you.com/api/{customerId} providing RSA-public key on request header. Kind of
curl --location --request GET 'https://you.com/cust099' --header 'rsa-public-key: PP9'

2. For each request, you generate random AES key (XX9), then encrypt the customer data using AES (symmetric). The customer data became NgqUCBFoLq7qn1RhOKNy/yDuy1pFsLYd1k7mR6913+oXfLMtDK#5MPtFBpcNkyb

This AES key XX9 is always randomly generated for each request.

3. You encrypt the AES key (XX9) using provided RSA public key. So XX9 became klf0Ak4Nz9

4 You give the encrypted customer data (NgqUCBFoLq7qn1RhOKNy/yDuy1pFsLYd1k7mR6913+oXfLMtDK#5MPtFBpcNkyb) as API response, along with encrypted AES key klf0Ak4Nz9 (from step 3). So the response is basically:

Response header > encrypted-AES-key : klf0Ak4Nz9

Response body > NgqUCBFoLq7qn1RhOKNy/yDuy1pFsLYd1k7mR6913+oXfLMtDK#5MPtFBpcNkyb

So there are two encrypted data here:

  • Response header provides encrypted AES (symmetric) key. This AES key is randomly generated for each response, and encrypted using RSA (asymmetric). Means only your partner can decrypt the AES key.
  • Response body provided customer data, encrypted using AES.

Note that the AES key is sent as part of response (to be precise : at response header). But instead of sending plain AES key, the key itself is asymmetrically encrypted using RSA. Since for each request-response, an AES key is randomly generated, a response body can only decrypted using key that is sent along, on it’s own response header.

5. Your partner company decrypt the AES key klf0Ak4Nz9 using their matching private key (QQ9, which only they know the key). So they get plain AES key XX9

6. Your partner company uses decrypted AES key XX9 to decrypt NgqUCBFoLq7qn1RhOKNy/yDuy1pFsLYd1k7mR6913+oXfLMtDK#5MPtFBpcNkyb so they get the plain customer data (email, phone, and 50 other fields).

# Java Implementation

Create a new spring boot project with dependency on spring web and lombok (optional). Then, add bouncycastle dependency

implementation ‘org.bouncycastle:bcprov-jdk15on:1.70’
  • Let’s just say we are going to send customer data (simplified): here
  • This is the cryptography service for encrypt / decrypt RSA or AES : here
  • This is the API implementation for sending data : here

Generating public / private key pair can be done by several means. There is also online generator, hence your company partner can generate by themselves.

For the sake of sample, let’s say that RSA public key is 1024 bit length : MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCF+7HXO77T1pO2tn1cFGC98ZsiYZCOXV4Psq3eRJ02wd5F4f5sdq+jdetBnvNJJ1ubO0dvCeC6zu89esgsEwz/6a+nZaRD0TUoXPsDnA/gsOi1IKMd9warVirjhfogfcj5yzAZMv9Vgbl+Zj5gXj7z6nvs6otyFtGOAu47RnvrSwIDAQAB

And the matching RSA private key is MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIX7sdc7vtPWk7a2fVwUYL3xmyJhkI5dXg+yrd5EnTbB3kXh/mx2r6N160Ge80knW5s7R28J4LrO7z16yCwTDP/pr6dlpEPRNShc+wOcD+Cw6LUgox33BqtWKuOF+iB9yPnLMBky/1WBuX5mPmBePvPqe+zqi3IW0Y4C7jtGe+tLAgMBAAECgYB7R1yaKsnpxr0BWCY+bC2bd8wDNXw50eTzUreyFLYHJRisekWMbSQsphpBtcwqWkFqsn+GxrL1j+QHUIsQKrNBDGW0qwNa6X2DeOyf7OOTsOqLxvCMhBri1vesfK/xiB//UZLLXMOCnyapnCcXQRRRIPltN5xtsWKANuxrOMvF+QJBALm7w+/8NA3tFUfpED0qiG9cjte3oQL5ccqEdQ9op3JTgT/24j9jLgBJejV7z4quszhMI0s+KwvH7wDvHPa3u1UCQQC4q+3iF3OutxUl33T2b7qEa2TB9Dby22STwsahI5SdKF5shT/eZpFQe6ZSZ+VJjXeE/gs1dTQz9TcgRZdGI0wfAkATeEFj6NY0F4RBk4bmQo0mz1cR+efg/fEVv6jdwqS68VH9OI4jAuyRjyJGdQIekY2r+x9ABfZRN9YwDODGZCA5AkEAoRKE2DDwW7GgpnnzAIePPbSWG32w975YSXEX4rCwDSuKwEiw3oDDulk/Rp0MJk1XtA4JJMVnlPIK9QD94tUirQJBAJO49L8KLaC4bdPcqhikZN1rqNPViU0UKwsVDvPxTf2eG5PRi9z3LNMOtrzdMJIqLKxfy8vk5aPzP7nwLQF0O8Q=

When running the app above and access endpoint with public RSA key using this curl

curl -X 'GET' \   'http://localhost:8080/api/crypto/encrypted_string' \   -H 'accept: text/plain' \   -H 'public-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCF+7HXO77T1pO2tn1cFGC98ZsiYZCOXV4Psq3eRJ02wd5F4f5sdq+jdetBnvNJJ1ubO0dvCeC6zu89esgsEwz/6a+nZaRD0TUoXPsDnA/gsOi1IKMd9warVirjhfogfcj5yzAZMv9Vgbl+Zj5gXj7z6nvs6otyFtGOAu47RnvrSwIDAQAB'

Then it will produce response body like this (will be different each hit, even if the data is same)

dCuZsm96q2LuzkYtRoiZJo9jDohMLj8VFPmI3hO6IjlKaL4esLfnAbMe55dO1ttbvNUWJ3XqJe75jD8ns1F4KP3e4/Kgf7gWs8Fgh3q2lvLA6uauJfeo72DQNg6mMc4OpfuP10kB0I2/qdkn3vjESA==

Also response header that contains AES key (encrypted using RSA, will be different for each hit)

encrypted-aes-key: dNQluEjN+srTx2WrVwQvMGETfMBBc/7BoXYtqr/1DKOktrSWBdJJhFEJjayngU9CS9GzGFHzQlQt5WhszDcppwf29dDwt6PfcI1n+8KT8ntQIVj8vCLLgTAu4wW3HHKXYe1Tf93x7GZWqrk+XUlhmspVio7Jsh7MMmO54ryXyP0=

Nice. You (the API provider) securely provides customer data.

# The Partner Side

Partner, which has private key, then can decrypt the data they receive. A simplifed application, using data from above,will be like this.

public class CustomerDecryptor {  public static void main(String[] args) throws Exception {
var cs = new CryptographyService();
var rsaPrivateKey = cs.getRSAPrivateFromBase64(
"MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIX7sdc7vtPWk7a2fVwUYL3xmyJhkI5dXg+yrd5EnTbB3kXh/mx2r6N160Ge80knW5s7R28J4LrO7z16yCwTDP/pr6dlpEPRNShc+wOcD+Cw6LUgox33BqtWKuOF+iB9yPnLMBky/1WBuX5mPmBePvPqe+zqi3IW0Y4C7jtGe+tLAgMBAAECgYB7R1yaKsnpxr0BWCY+bC2bd8wDNXw50eTzUreyFLYHJRisekWMbSQsphpBtcwqWkFqsn+GxrL1j+QHUIsQKrNBDGW0qwNa6X2DeOyf7OOTsOqLxvCMhBri1vesfK/xiB//UZLLXMOCnyapnCcXQRRRIPltN5xtsWKANuxrOMvF+QJBALm7w+/8NA3tFUfpED0qiG9cjte3oQL5ccqEdQ9op3JTgT/24j9jLgBJejV7z4quszhMI0s+KwvH7wDvHPa3u1UCQQC4q+3iF3OutxUl33T2b7qEa2TB9Dby22STwsahI5SdKF5shT/eZpFQe6ZSZ+VJjXeE/gs1dTQz9TcgRZdGI0wfAkATeEFj6NY0F4RBk4bmQo0mz1cR+efg/fEVv6jdwqS68VH9OI4jAuyRjyJGdQIekY2r+x9ABfZRN9YwDODGZCA5AkEAoRKE2DDwW7GgpnnzAIePPbSWG32w975YSXEX4rCwDSuKwEiw3oDDulk/Rp0MJk1XtA4JJMVnlPIK9QD94tUirQJBAJO49L8KLaC4bdPcqhikZN1rqNPViU0UKwsVDvPxTf2eG5PRi9z3LNMOtrzdMJIqLKxfy8vk5aPzP7nwLQF0O8Q=");
var encryptedAESCipherKey = "dNQluEjN+srTx2WrVwQvMGETfMBBc/7BoXYtqr/1DKOktrSWBdJJhFEJjayngU9CS9GzGFHzQlQt5WhszDcppwf29dDwt6PfcI1n+8KT8ntQIVj8vCLLgTAu4wW3HHKXYe1Tf93x7GZWqrk+XUlhmspVio7Jsh7MMmO54ryXyP0=";
var plainCipherKey = cs.decryptRSA(encryptedAESCipherKey, rsaPrivateKey);
var encryptedCustomerResponse = "dCuZsm96q2LuzkYtRoiZJo9jDohMLj8VFPmI3hO6IjlKaL4esLfnAbMe55dO1ttbvNUWJ3XqJe75jD8ns1F4KP3e4/Kgf7gWs8Fgh3q2lvLA6uauJfeo72DQNg6mMc4OpfuP10kB0I2/qdkn3vjESA==";

var plainMsg = cs.decryptAES(encryptedCustomerResponse, plainCipherKey);
System.out.println("Plain message : " + plainMsg);
}
}

Which will produce output below.

Plain message : {"customerName":"Customer name -2046994620","customerId":"cust47174675","dateOfBirth":"1993-08-26T00:00:00"}

Of course, since partner owns the private key, only partner company can obtain this data. If there is man-in-the middle attack, which intercepts traffic and get the public key and encrypted response, he cannot decrypt the customer data.

# More Way to Secure REST API

Securing REST API is more than encryption. There are many ways and techniques that can be used for various cases. For more technical implementation of REST API security, see here.

Just my 2 cents

--

--