Browse documentation
Documentation / Protection passes / String & constant encryption

String & constant encryption

ConditionalEvery string literal inside a method body is encrypted, and decrypted at the exact point of use.

What it does

Every LDC of a String literal inside a method body is replaced with a per-string, position-keyed obfuscated payload, plus a call to a runtime decrypt routine at the point of use. Each UTF-8 byte is XORed with a key rotated by its position, so a repeated plaintext byte doesn't produce a repeated ciphertext byte. The result is Base64-encoded so it embeds as an ordinary class-file string constant.

Scope and boundaries

Covers LDC of a string literal used inside a method body. Does not cover a static final String field's own compile-time constant value: other classes compiled against that field may already have inlined the plaintext into their own constant pool at compile time, a copy this pass cannot reach. Encrypting only the field's declaration would look complete without being complete, so the field is left alone.

Where it sits in the pipeline

Runs before nativization's own eligibility scan. This means a method that later gets nativized never has its plaintext string embedded in the generated Rust code either. The compiled native code calls back into the same runtime decrypt routine through a JNI static-method call, and holds the string in Java-side memory only briefly, at the point it's used. This wasn't designed as a combined feature. It follows from the pipeline's fixed ordering, and it has been verified.

Threat model

Raises the bar against a static scan for string literals inside a method body, such as a plaintext search of the disassembly or a "strings" command run against the jar or the compiled native library. It does not resist a debugger reading the decrypted value at its actual point of use, and it doesn't cover a static final String's own constant declaration (see Scope above).

Verified. A distinctive literal from Apache Commons Exec's source was present, verbatim, in the original jar's raw bytes and absent from the protected jar's. The same check against the compiled native library's strings output for methods that were later nativized also found it absent, so the effect of string encryption survives translation to native code as well as in the remaining bytecode.