Encryption & Decryption of Api Request/Response in Android Development.
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 theCipher
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.