Encryption & Decryption of Api Request/Response in Android Development.

Jayant Kumar🇮🇳
4 min readDec 20, 2023
Photo by FLY:D on Unsplash

Hello everyone , In this article we will learn how to encrypt/decrypt your Api request & response in android development.

For this purpose we will use AES algorithm for encryption & decryption and Retrofit , Okhttp for network request.

Encryptions

// These constants are helpful to encrypt and decrypt the request and response
private const val ALGO = "AES"
private const val TOKEN_KEY = "8949300284030"
private const val PADDING = "AES/CBC/PKCS5Padding"
private const val IV = "9f0034nf094h49"
private const val IV_SIZE = 0


// Encrypt the json request

fun String.encryptAES(): String {
// step1
val tokenBytes = TOKEN_KEY.toByteArray(Charsets.UTF_8)
// step 2
val secretKey = SecretKeySpec(tokenBytes, ALGO)

// step 3
val iv = IvParameterSpec(IV.toByteArray())
// step 4
val cipher = Cipher.getInstance(PADDING)
//step 5
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv)

//step 6
val cipherText = cipher.doFinal(this.toByteArray(Charsets.UTF_8))
// step 7
return cipherText.toHex()
}

// convert the byteArray to hexadecimal
fun ByteArray.toHex(): String {
return joinToString("") { "%02x".format(it) }
}

To encrypt any request , it required 7 steps , Let’s understand each one by one.

We have created an extension function of String , whatever json request we want to send to the server , first we will attach that json string to this function and it will automatically encrypt that request.

Step 1

Conversion of TOKEN_KEY to Byte Array:

  • The TOKEN_KEY is converted to a byte array (tokenBytes) using the UTF-8 character encoding. This byte array will be used as the secret key for the encryption process.

Step 2

Creation of SecretKeySpec:

  • A SecretKeySpec is created using the byte array obtained from the TOKEN_KEY and the specified algorithm (ALGO), which is set to "AES."

Step 3

Initialisation Vector (IV):

  • An initialisation vector (IV) is created using a predefined value (IV). The IV is used to add an extra layer of security to the encryption process.

Step 4

Cipher Instance Creation:

  • A Cipher instance is created using the specified algorithm (PADDING), which is set to "AES/CBC/PKCS5Padding."

Step 5

Cipher Initialisation:

  • The cipher is initialised for encryption (Cipher.ENCRYPT_MODE) using the secret key and IV.

Step 6

Encryption:

  • The input string (this) is converted to bytes using UTF-8 encoding (this.toByteArray(Charsets.UTF_8)), and then the Cipher instance is used to encrypt these bytes (cipher.doFinal(...)).

Step 7

Conversion to Hexadecimal String:

  • The resulting encrypted bytes (cipherText) are converted to a hexadecimal string using a custom extension function (toHex()).

Decryption

fun String.decryptAES(): String {
val tokenBytes = TOKEN_KEY.toByteArray(Charsets.UTF_8)
val secretKey = SecretKeySpec(tokenBytes, ALGO)

val ivAndCipherText = this.hexToByteArray()
val cipherText = ivAndCipherText.copyOfRange(IV_SIZE, ivAndCipherText.size)
val iv = IvParameterSpec(IV.toByteArray())

val cipher = Cipher.getInstance(PADDING)
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv)

return cipher.doFinal(cipherText).toString(Charsets.UTF_8)
}

fun String.hexToByteArray(): ByteArray {
try {
val result = ByteArray(length / 2)
for (i in indices step 2) {
val byte = substring(i, i + 2).toInt(16)
result[i / 2] = byte.toByte()
}
return result
} catch (e: NumberFormatException) {
e.printStackTrace()
}
return ByteArray(0)
}

Decryption process also similar to encryption , nothing fancy , you can understand the code.

Now comes into the main part , where we will handle the encryption & decryption of request and response? . Well the answer is ….

In your OAuthInterceptor class ..

class OAuthInterceptor @Inject constructor(
private val sessionManger: SessionManger
) : Interceptor {
private val scope = CoroutineScope(SupervisorJob())
override fun intercept(chain: Interceptor.Chain): Response {
val accessToken = runBlocking { sessionManger.getToken().first() }
val request = chain.request()
val encryptedRequestBody = sendEncryptedRequestBody(request.body)
val response =
chain.proceed(
newRequestWithAccessToken(
accessToken, request,
encryptedRequestBody
)
)
val responseBody = getDecryptedResponse(response)
return response.newBuilder().body(responseBody).build()
}

// decrypt the response coming from the server
private fun getDecryptedResponse(response: Response): ResponseBody {
val source = response.body?.source()
source?.request(Long.MAX_VALUE)
val buffer = source?.buffer
val responseBodyString = buffer?.clone()?.readString(Charsets.UTF_8)
val decryptString = responseBodyString?.decryptAES() ?: EMPTY
return decryptString.toResponseBody(APPLICATION_JSON.toMediaTypeOrNull())
}

// encrypt the request body and send to the server
private fun sendEncryptedRequestBody(request: RequestBody?): RequestBody? {
if (request != null) {
val buffer = Buffer()
request.writeTo(buffer)
val plainRequestBodyString = buffer.readString(Charsets.UTF_8)
val encryptedText = plainRequestBodyString.encryptAES()
return encryptedText.toRequestBody(PLAIN_TEXT.toMediaTypeOrNull())
}
return null
}


private fun newRequestWithAccessToken(
accessToken: String?,
request: Request,
newRequestBody: RequestBody?
): Request {
val builder = request.newBuilder().header(
AUTHORIZATION, "$BEARER $accessToken"
).header(X_ENCRYPTED, TRUE)
if (newRequestBody != null) {
builder.method(request.method, newRequestBody)
}
return builder.build()
}
}

As you can see in the above code , we have a create a OAuthInterceptor class , where we will handle all the coming request & response.

Basically before sending any request to the server , we have to encrypt the request. So for that we have created a function sendEncryptedRequestBody(…) , where we are encrypting the original json data and return the plain text result.

After that we have created an another function newRequestWithAccessToken(…) , where we are sending the encrypted request with the Access Token to the server.

And the last process is to decrypt the response , so for that we have create a function getDecryptedResponse(…) , where we are decrypting the response and return the response in the form of Json.

Now use your OAuthInterceptor class with your Retrofit.

@Provides
@Singleton
fun providesOkHttp(oAuthInterceptor: OAuthInterceptor, childModule: DataModule): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(oAuthInterceptor)
.also { client ->
client.addInterceptor(HttpLoggingInterceptor().apply {
level = if (childModule.isDebugBuildConfig()) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
})
}
.build()
}

@Provides
@Singleton
fun providesApiService(
moshi: Moshi,
client: OkHttpClient,
childModule: DataModule
): ApiService = retrofit {
baseUrl(childModule.getBaseUrl())
addConverterFactory(MoshiConverterFactory.create(moshi))
client(client)
build()
}.create(ApiService::class.java)

private inline fun retrofit(init: Retrofit.Builder.() -> Unit): Retrofit {
val retrofit = Retrofit.Builder()
retrofit.init()
return retrofit.build()
}

That’s all for today’s my friends , hope you enjoyed and learnt something new from this article.

--

--

Jayant Kumar🇮🇳

Hello , My name is Jayant, I am a tech youtuber and software Engineer from India 🇮🇳