I'm working on an Android application where I need to encrypt the request body before sending it to the server and then decrypt the response received from the server. I'm using Ktor's HTTP client for network requests and I have a CryptoUtils
class that handles encryption and decryption using AES. Here's an outline of what I have done:
private fun HttpClientConfig<OkHttpConfig>.encryptionInstall() {
install("EncryptRequest") {
requestPipeline.intercept(HttpRequestPipeline.Transform) { request ->
if (request !is EmptyContent) {
val request = request as LoginDTO
val originalBody = Json.encodeToString(x)
val encryptionKey = "MY_ENCRYPTION_KEY"
val encryptedBody = CryptoUtils.encryptData(
encryptionKey, originalBody
)
val encryptedContent = TextContent(encryptedBody, ContentType.Application.Json)
proceedWith(encryptedContent)
} else {
proceedWith(request)
}
}
}
}
2. encryption: Now, I'm trying to decrypt the response received from the server using the DecryptResponse
plugin. Here's the code for the decryption install:
private fun HttpClientConfig<OkHttpConfig>.decryptInstall() {
install("DecryptResponse") {
receivePipeline.intercept(HttpReceivePipeline.After) { response ->
val originalResponseReceived = response.body<String>()
runCatching {
val encryptionKey = "MY_DECRYPTION_KEY"
val decryptData = CryptoUtils.decryptData(
encryptionKey, originalResponseReceived.toString().replace("\n", "")
)
val castedFromStringToObject =
Json.decodeFromString<LoginResponseDTO>(decryptData.orEmpty())
// Now I have the decrypted data, how do I proceed with it?
// I need to create an HttpResponse object with this decrypted data.
// But I can't create an instance of an abstract class HttpResponse.
// How can I create a new HttpResponse to proceed with the decrypted data?
}
}
}
}
I tried decrypting the response using the DecryptResponse plugin and I successfully decrypted the content. However, I encountered an issue when trying to proceed with the decrypted data. Since HttpResponse is an abstract class, I couldn't directly create an instance of it to proceed with the decrypted data.
I was expecting to be able to create a new HttpResponse object with the decrypted data so that I can continue processing it. However, since HttpResponse is abstract, I'm not sure how to proceed. I need guidance or examples on how to create a new HttpResponse to proceed with the decrypted data after decryption.
Any insights or examples on how to achieve this would be greatly appreciated.
Thank you in advance for your help!
Instead of asking for examples, please just have a look at the actual source code.
This is phase HttpResponsePipeline.Receive
, but still the HttpResponsePipeline
.
scope.responsePipeline.intercept(HttpResponsePipeline.Receive) { (type, content) ->
val method = context.request.method
val contentLength = context.response.contentLength()
if (contentLength == 0L) return@intercept
if (contentLength == null && method == HttpMethod.Head) return@intercept
if (content !is ByteReadChannel) return@intercept
val response = with(plugin) {
HttpResponseContainer(type, context.decode(context.response, content))
}
proceedWith(response)
}
File HttpResponsePipeline
should have been read, because understanding the phases is crucial.
The situation might be easier to understand and debug, when also adding a logging interceptor.
// https://mvnrepository.com/artifact/io.ktor/ktor-client-logging-native
implementation("io.ktor:ktor-client-logging-native:1.3.1")
When intercepting at phase HttpReceivePipeline.After
, this should be a plain-text response already (!!). With multiple interceptors it becomes obvious why selecting the proper phase is important. Phase After
appears too late, see HttpResponsePipeline
; Receive
, Transform
or Parse
appear more meaningful than After
. When using Transform
on the request side already, I'd suggest to also use it on the response side.
You may want to return an instance of DefaultHttpResponse
or maybe HttpResponseContainer
. See what proceedWith()
at phase HttpResponsePipeline.Transform
would accept to know which return-type is required. Even when the original response
is immutable, you still can/should copy the headers from the original response
(except content-lenght) + the decrypted response.body
.
As the statement response = with(plugin)
suggests, one could plug-in the code, while overriding hooks transformRequestBody
and transformResponseBody
. This might be the proper approach.