SReverseby Simpa Labs

Traffic analysis

How to identify encryption before an Android API request

Effort spent reproducing a payload that turns out to be encrypted is wasted, and effort spent attacking a payload that was never encrypted is worse. Identify the layer first.

Transport encryption and payload encryption

Every request over HTTPS is encrypted on the wire. That tells you nothing about whether the body is encrypted before it reaches the socket. The distinction matters because TLS ends at your proxy while payload encryption survives into your client, and only the second one requires you to recover a key.

Signals in the captured traffic

Start with what is in front of you and avoid decoding anything.

  • The Content-Type header disagrees with the body. A type of application/json with a body that does not begin with a brace is a strong signal.
  • The body is a single long base64 string, especially when the request clearly carries several fields.
  • The body length does not track the change you made in the UI. Encrypted payloads pad to block boundaries, so a one-character edit can leave the length unchanged.
  • A custom header carries a second opaque value, which is often the initialisation vector or a wrapped key.
  • The body contains a short prefix before the encoded text, such as a version byte or a key identifier.
  • Response bodies are opaque in the same way, which indicates the layer is applied in both directions.

Repeating the same action is informative. Encryption with a random vector makes the body differ on every call even when the parameters are identical. A signature also changes every call, so pair the observation with a look at the body structure: a signed JSON body still parses, while an encrypted one does not.

Entropy and compression

Compressed data and encrypted data are both high entropy, which is why tools that score randomness produce false positives. The difference shows in structure. DEFLATE output carries a header and a checksum, and its internal patterns survive a byte-level look. Ciphertext has no such landmarks, and a good cipher leaves no repeated blocks when the mode uses a fresh vector.

Test it cheaply: try to decompress the decoded bytes. A successful inflate means the body was compressed and probably encrypted underneath, so the compression step sits on one side of the cipher and has to be reproduced in the right order.

Length behaviour is a second cheap test. In a block cipher mode without a stream wrapper, the ciphertext length is a multiple of the block size and grows in steps as the plaintext grows. Send the same request with a noticeably longer value and watch whether the length moves in the same increments as the input. A body that grows one byte per byte is being encoded rather than encrypted.

Signals in the code

Static analysis answers the question without touching the traffic. Search the dex for the crypto APIs: the cipher factory, message digest and MAC classes, the keystore, and the key generator. Their presence in a request path is the clearest indication of payload encryption, and their callers lead to the routine that assembles the body.

Then search for native entry points. A payload built in JNI rarely reveals the algorithm in the dex, and the boundary method shows which values cross into the library: a plaintext, a key, a vector, or a mode selector.

Finally, look at string constants near the request builder. A transformation string, a block mode, and a padding name are specific enough to find with a text search, and a hardcoded key often sits within a few lines of the code that uses it.

Key material rarely looks like a key. It may be assembled from several constants at runtime, read from a resource file, or derived from a value the app already has, such as a package name or an install identifier. Trace what the cipher's init method receives rather than what a constant near it contains, because the value that reaches the cipher is the one you need. When the key is fetched from a configuration endpoint or a login response, note that it can change between sessions, which explains a reproduction that works today and fails after the next app launch.

Reading the failure modes without breaking anything

Send the request with the body left as captured and change nothing. A rejection means validation applied to the ciphertext, which is what a signature over the encrypted bytes looks like. Change a byte in the middle of the ciphertext and resend. A generic error suggests an integrity or padding check. A specific parse error suggests the server decoded the body, which means the encryption is reversible at the edge and the key is likely derived from something the client sends.

None of these tests require a key. Together they establish whether a payload layer exists, whether it is authenticated, and whether the key travels with the request. That is enough to scope the work before you commit to it, and it is enough to tell whether the remaining problem is encryption, signing, or session state.

Reviewed 28 September 2026 · SReverse research desk

Start your full APK reconstruction

Send the full APK

Projects start at $120. Choose WhatsApp or email, then attach the APK in the app that opens. We reply within one hour with the next step and send the fixed quote after review.

Want us to contact you?