Three places a Flutter app can encrypt a request
A Flutter release build carries its application logic as compiled Dart inside a native library, and the cipher can sit in any of three layers above the socket. Deciding which layer holds it is the first result that changes the work, because each layer is read with different tools.
- The cipher runs in compiled Dart. A Dart cryptography package performs the encryption inside the application's own library, so the key handling, the mode and the padding all live in the code that was compiled ahead of time with the rest of the app.
- The cipher runs in a plugin's native library. A Flutter plugin can wrap a C or C++ implementation, and the encryption then happens in a second shared object that ships beside the application library.
- The cipher runs on the Android side. A platform channel carries the bytes into Java or Kotlin, and code that a Java decompiler reads performs the encryption.
The layer determines the reading cost. Encrypted Dart is the most expensive of the three, a plugin library is usually the most legible, and a platform channel is the cheapest because the JVM side of a channel keeps class and method names in most builds.
What encrypted Dart looks like in the compiled library
Dart compiles ahead of time, and a release build discards the symbol names that would identify a method. You cannot find a call to an encryption routine by its label, so the recovery starts from the data instead.
Two kinds of material survive the compilation and are worth searching for. String literals stay in the snapshot data, so a hardcoded key, an initialization vector or a salt appears as text inside the library. Cryptographic tables also survive, and the substitution tables, round constants and key schedule constants of a block cipher have a recognisable shape in a dump of the library's data sections.
When the constants are absent, the key is produced while the app runs, and the search moves to the inputs of that production.
Recover the key, the mode and the nonce as separate questions
Reproducing the encryption means reproducing four decisions the app made: the algorithm, the key, the mode with its initialization vector or nonce, and the encoding applied to the ciphertext. Settle each with a test rather than treating them as one problem.
- Feed a known plaintext through a candidate key. A block of zeros reveals whether the output length changes, which separates a stream construction from a block construction with padding.
- Encrypt the same plaintext twice in the running app. Identical ciphertext points at a fixed initialization vector, and a changing value means the vector travels with the body or is derived from a counter.
- Compare the captured body with your output byte for byte. A match on the first block and a divergence afterwards points at the mode, and a mismatch from the first byte points at the key.
- Check the transport encoding last. Base64, hexadecimal and URL-safe variants change the text without changing the cipher, and the wrong choice produces a body that looks correct and is not.
When the key depends on the device or the session
A hardcoded key is the easiest case and the least common in an app that wants the protection to hold. The key more often comes from an input that exists at run time: an install identifier, a value returned by a login call, a server-issued wrapping key, or a value derived from the package signature. Recovering the algorithm then achieves little on its own, because the key changes with the context.
Repetition separates a stored key from a derived one. Run the same call on two installs. A key that is the same on both is stored or issued for that account, and a key that differs on each install comes from something the device provides.
Finishing the recovery
The end state is a client that produces the same bytes for the same input. Build the encryption step as a function you can call on its own, then place it in front of the HTTP layer so that request building, signing and transport stay separable. That separation is what lets the client survive a change in the app, because a new key or nonce source can be replaced without touching the rest.
This work usually arrives together with a signature and a session that also have to be reproduced. Deliverables are a Python, JavaScript or TypeScript client, an importable Postman collection and documentation, starting at $120, with most projects finished in 24 to 72 hours.
Related work
Reviewed 28 September 2026 · SReverse research desk