Secure App — Implementasi Enkripsi dengan OkHttp Interceptors

Yusuf Safrudin
TLabCircle
Published in
4 min readMar 16, 2023

Dalam sebuah requirement atau standarisasi terkait pengembangan aplikasi, ada yang mengharuskan sebuah aplikasi untuk dilakukan enkripsi ketika melakukan request ke server atau menerima response dari server, lalu bagaimana kita melakukan hal tersebut?

https://unsplash.com/photos/y5N2HDwagVw

Interceptors

Interceptors adalah sebuah mekanisme yang dapat digunakan untuk me monitor, rewrite dan retry sebuah request.

square

kali ini kita akan mencoba membuat enkripsi request dan dekripsi dari response server dengan menggunakan OkHttpInterceptor, pada sample ini kita menggunakan mock server dari postman.

Encryption Interceptor

import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.mobile.encrypthttpinterceptor.Encryptor
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okio.Buffer

object EncryptInterceptor : Interceptor {
private const val key = "SampleSecretKeys" // 16 character
private const val initVector = "SampleInitVector" // 16 character

@RequiresApi(Build.VERSION_CODES.O)
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val rawBody = request.body

val mediaType: MediaType? = "text/plain; charset=utf-8".toMediaTypeOrNull()
var encryptedBody = ""

try {
val rawBodyStr: String = rawBody?.let { requestBodyToString(it) }.toString()
encryptedBody = Encryptor.encrypt(key, initVector, rawBodyStr).toString()
Log.i("ApiCall", "Raw body=> $rawBodyStr")
Log.i("ApiCall", "Encrypted BODY=> $encryptedBody")
} catch (e: Exception) {
e.printStackTrace()
}

val body = encryptedBody.toRequestBody(mediaType)
request = request.newBuilder().header("Content-Type", body.contentType().toString())
.method("GET", null)
.method(request.method, body)
.build()

return chain.proceed(request)
}

private fun requestBodyToString(requestBody: RequestBody): String {
val buffer = Buffer()
requestBody.writeTo(buffer)
return buffer.readUtf8()
}
}

Decryption Interceptor

import android.os.Build
import android.text.TextUtils
import android.util.Log
import androidx.annotation.RequiresApi
import com.mobile.encrypthttpinterceptor.Encryptor
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import java.util.*

object DecryptInterceptor : Interceptor {
private const val key = "SampleSecretKeys"
private const val initVector = "SampleInitVector"

@RequiresApi(Build.VERSION_CODES.O)
override fun intercept(chain: Interceptor.Chain): Response {
val response: Response = chain.proceed(chain.request())
if (response.isSuccessful) {

val newResponse = response.newBuilder()
var contentType = response.header("Content-Type")
if (TextUtils.isEmpty(contentType)) contentType = "application/json"

val responseStr = response.body!!.string()
var decryptedString: String? = null
try {
decryptedString = Encryptor.decrypt(key, initVector, responseStr)
} catch (e: Exception) {
e.printStackTrace()
}
Log.i("ApiCall", "Response string => $responseStr")
Log.i("ApiCall", "Decrypted BODY=> $decryptedString")
newResponse.body(decryptedString!!.toResponseBody(contentType?.toMediaTypeOrNull()))
return newResponse.build()
}
return response
}
}

Api Service

interface ApiService {
@POST("/user-info")
suspend fun apiDemo(
@Body body: HashMap<String, String>,
): Response<InterceptResponse>
}
data class InterceptResponse(
val status: String,
val data: String
)

Retrofit dan OkHttp Client

kita menambahkan EncryptInterceptor dan DecryptInterceptor ke OkHttpClientkemudian kita tambahkan ke Retrofit Builder.

import android.annotation.SuppressLint
import com.mobile.encrypthttpinterceptor.BuildConfig
import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

object ApiCall {

private val encryptInterceptor by lazy {
EncryptInterceptor
}

private val decryptInterceptor by lazy {
DecryptInterceptor
}

private val loggingInterceptor = HttpLoggingInterceptor()

private val okHttpClient = OkHttpClient().newBuilder()
.connectTimeout(300L, TimeUnit.SECONDS)
.readTimeout(300L, TimeUnit.SECONDS)
.writeTimeout(300L, TimeUnit.SECONDS)
.pingInterval(1, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.connectionPool(ConnectionPool(0,1, TimeUnit.NANOSECONDS))
.protocols(listOf(Protocol.HTTP_1_1))
.addInterceptor(encryptInterceptor)
.addInterceptor(decryptInterceptor)

@SuppressLint("NewApi")
private fun retrofit(): Retrofit {
if (BuildConfig.DEBUG) {
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
okHttpClient.addInterceptor(loggingInterceptor)
}

return Retrofit.Builder()
.client(okHttpClient.build())
.baseUrl("https://f94121c4-ba12-4e22-bf82-0a9145166a7c.mock.pstmn.io")
.addConverterFactory(GsonConverterFactory.create())
.build()
}

fun apiService(): ApiService = retrofit().create(ApiService::class.java)

}

Setelah kita setup Retrofit dan Okhttp client, kita coba panggil langsung di MainActivity

class MainActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initApi()
}

private fun initApi() {
val bodyParams = HashMap<String, String>()
bodyParams["name"] = "robert"
lifecycleScope.launch {
try {
val response = apiService().apiDemo(bodyParams)
if (response.isSuccessful) {
val data = response.body()?.data
Log.d("MainActivity", "response result: $data")
}
} catch (e: java.lang.Exception) {
Log.e("MainActivity", "response error: ${e.message}")
}
}
}
}

Log

I/ApiCall: Raw body=> {"name":"robert"}
I/ApiCall: Encrypted BODY=> WvRBjGvRveuobji5YLd+KmsqgHSvyQvb83zRWQNLryY=
I/okhttp.OkHttpClient: --> POST https://f94121c4-ba12-4e22-bf82-0a9145166a7c.mock.pstmn.io/user-info
I/okhttp.OkHttpClient: Content-Length: 44
I/okhttp.OkHttpClient: Content-Type: text/plain; charset=utf-8
I/okhttp.OkHttpClient: WvRBjGvRveuobji5YLd+KmsqgHSvyQvb83zRWQNLryY=
I/okhttp.OkHttpClient: --> END POST (44-byte body)
I/okhttp.OkHttpClient: <-- 200 OK https://f94121c4-ba12-4e22-bf82-0a9145166a7c.mock.pstmn.io/user-info (964ms)
I/okhttp.OkHttpClient: Date: Thu, 09 Mar 2023 04:26:31 GMT
I/okhttp.OkHttpClient: Content-Type: application/json; charset=utf-8
I/okhttp.OkHttpClient: Content-Length: 108
I/okhttp.OkHttpClient: Connection: keep-alive
I/okhttp.OkHttpClient: x-srv-trace: v=1;t=cf4d5f4924ba0f23
I/okhttp.OkHttpClient: x-srv-span: v=1;s=109a743261f0fe8c
I/okhttp.OkHttpClient: Access-Control-Allow-Origin: *
I/okhttp.OkHttpClient: X-RateLimit-Limit: 120
I/okhttp.OkHttpClient: X-RateLimit-Remaining: 119
I/okhttp.OkHttpClient: X-RateLimit-Reset: 1678244556
I/okhttp.OkHttpClient: ETag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"
I/okhttp.OkHttpClient: Vary: Accept-Encoding
I/okhttp.OkHttpClient: GdJYKpTjYnZ48xur51hTKtIL2BpIdmWN4kcObTusg6E6nDISTcS6+WISv6njODh6KTOVj6Dna/wxfu1wnkYT1rf3GqB08reFnJHsbY1syoo=
I/okhttp.OkHttpClient: <-- END HTTP (108-byte body)
I/ApiCall: Response string => GdJYKpTjYnZ48xur51hTKtIL2BpIdmWN4kcObTusg6E6nDISTcS6+WISv6njODh6KTOVj6Dna/wxfu1wnkYT1rf3GqB08reFnJHsbY1syoo=
I/ApiCall: Decrypted BODY=> {
"status" : "success",
"data": "hallo robert downey jr"
}
D/MainActivity: response result: hallo robert downey jr

Kesimpulan

Dengan menggunakan OkHttpInterceptor kita dapat memanipulasi content yang akan dikirim maupun yang diterima dari server. tentu selalu ada kelebihan dan kekurangan dari metode ini, untuk itu kita bisa kombinasikan beberapa metode untuk membuat aplikasi kita lebih secure salah satunya dengan menggunakan native C/C++, untuk artikel terkait itu bisa di lihat di sini

--

--