What a body encryption layer looks like
Body encryption is applied above the transport. The app serialises a payload, encrypts it, and often encodes the result before placing it in the request. From the outside the request looks like a POST with a short text body, which is exactly why it is used. It hides field names, values and validation rules at the same time.
Common shapes:
- AES in CBC mode with PKCS5 or PKCS7 padding, keyed from a constant or a derived value.
- AES-GCM, which adds an authentication tag that must be transmitted and verified.
- A session key generated per install or per login, wrapped with RSA or an ECDH-derived key, and sent alongside the payload.
- Compression followed by encryption, in either order, which changes the plaintext you have to reproduce.
- Base64 over the ciphertext, sometimes URL-safe and sometimes with padding removed.
- Per-message encryption inside a WebSocket or gRPC stream, where the framing carries the pieces.
Recovering the key
The key is usually stored in a way that reflects what the developer was trying to protect against. A constant string in the dex is the simplest case and the easiest to find, though R8 may inline it or split it across an array rebuild. A key assembled by XORing a byte array with a value from another constant is common in lightly protected builds.
A key wrapped with a public key, where the ciphertext of the session key travels with the request, needs the private key from the device side. A key stored in the Android keystore exists only on that device and cannot be extracted, so the app has to be asked for plaintext or for an encryption service. White-box implementations embed the cipher and the key in native code with the algorithm restructured, in which case recovering a table is not enough and the routine has to be understood or extracted.
Key derivation needs the same care as the key itself. A value that looks wrong often turns out to be a password passed through PBKDF2 with a specific iteration count and salt, or a SHA-256 of something the app considered a secret.
Recovering the IV
An initialisation vector must be unique per encryption under the same key, so most apps generate one and transmit it. In CBC implementations the vector is often placed before the ciphertext as raw bytes or in the first block, and some schemes derive it from a request identifier or a counter instead of sending it. A vector reused across messages weakens the cipher and gives you a second path to the plaintext, since identical first blocks produce identical ciphertext blocks.
Log the bytes handed to the cipher's init method. That single value, together with the key, makes captured traffic readable.
Recovering the algorithm
The transformation string passed to the cipher factory names the algorithm, the mode and the padding. Read it rather than guessing from the ciphertext length, because several modes produce similar output sizes and only some require an authentication tag. GCM expects a tag appended to the ciphertext, and a reproduction that ignores it will encrypt correctly and still be rejected.
Brute force on the ciphertext is a poor use of time when the key algorithm is reachable in the process. A hook on the cipher class gives key, vector, algorithm and both directions of the transform in one place, which is the fastest route from traffic to plaintext.
Verifying that a decrypted body is real
Plaintext that decrypts without padding errors can still be wrong. Check that it parses as the type the Content-Type header claims, that repeated calls to the same screen produce structurally similar output, and that changing one field in the UI changes one place in the plaintext. A padding oracle error and a plausible-looking string are different outcomes and only the first one tells you the key is wrong.
Keep the two directions separate in your implementation. Request and response bodies often use different keys, and a client that encrypts with the request key and decrypts with it too will read every response as garbage. Record which key and which vector belong to which direction as soon as you have them, because a key that works in one direction is easy to reuse by accident in the other.
Long-lived sessions add second-order work. If the key is derived from a session credential, a reconnect or a token refresh can change the key partway through a conversation, and the server will stop accepting bodies encrypted with the first one. Check whether the derived value changes after a re-authentication before concluding that the algorithm is wrong.
Once you can decrypt and encrypt the body, the endpoint schema follows. Then the work returns to the problems that survive encryption: signing over the ciphertext, session handling, and request order.
Related work
Reviewed 28 September 2026 · SReverse research desk