Overview
The String Encryption Pass replaces plaintextStringLiteral nodes in your kernel AST with calls to an injected runtime decoder. Strings are encoded as XOR-encrypted byte arrays, making them invisible in the compiled binary.
This pass runs after Rename, so the decoder function itself gets an obfuscated name like
_$0 if Rename is enabled.How It Works
The pass operates in two phases (lib/src/engine/passes/string_encrypt/string_encrypt_pass.dart:14):Inject Decoder Function
The pass injects a helper function
_obfDecode$ into the first user library in scope. This function takes an encrypted byte array and XOR key, then returns the decoded string.Encoding Algorithm
Strings are encoded using simple XOR encryption (lib/src/engine/passes/string_encrypt/string_encrypt_pass.dart:178):"Hello" with xor_key: 0x5A
- UTF-8 encode:
[72, 101, 108, 108, 111] - XOR each byte:
[72^90, 101^90, 108^90, 108^90, 111^90] - Result:
[18, 59, 54, 54, 53]
XOR is fast and reversible (
x ^ key ^ key = x), making it ideal for runtime decoding.Configuration
Enable with Defaults
Full Configuration
xor_key
xor_key
Type:
Default:
int (0-255)Default:
0x5A (90 in decimal)The XOR key used for encoding. Choose any byte value (0-255). Using a different key for each build adds another layer of obfuscation.exclude_patterns
exclude_patterns
Type:
Default:
List<String> (regex patterns)Default:
[] (empty list)Regular expressions to match against string values. Matching strings are not encrypted. Useful for:- URLs that need to stay plaintext
- Package import URIs
- Configuration strings
- Strings used in native bindings
What Gets Encrypted
The pass encrypts allStringLiteral nodes that are:
- In user libraries (matching your project scope)
- Not in a const context
- Not in annotations
- Not matching any
exclude_patterns
Encrypted Examples
Skipped Examples
The Decoder Function
The pass injects this decoder function (lib/src/engine/passes/string_encrypt/string_encrypt_pass.dart:49):Injection Process
Find Target Library
The pass finds the first user library in the component that matches your project scope.
Build Kernel AST
Construct the decoder function as a kernel
Procedure node with:- Two parameters:
List<int> bytes,int key - Return type:
String - Body:
String.fromCharCodes(bytes.map((b) => b ^ key))
The decoder function is built entirely using kernel AST nodes — no source code generation. This ensures it’s correctly typed and optimizable by the Dart VM.
Decoder Name
The function is named_obfDecode$ by default. If the Rename Pass runs first, this name gets obfuscated:
Before and After Examples
Example 1: Simple String
Before:xor_key: 0x5A):
"Hello, World!" is now invisible in the binary — only the encrypted bytes appear.
Example 2: String Interpolation
Before:String interpolation gets compiled to concatenation in kernel bytecode. Each literal part is encrypted separately.
Example 3: Excluded Patterns
Configuration:Implementation Details
Transformer Logic
TheStringEncryptTransformer tracks context to avoid encrypting invalid strings (lib/src/engine/passes/string_encrypt/string_encrypt_transformer.dart:5):
Const Context Tracking
The transformer increments depth counters when entering const contexts:Annotation Skipping
Annotations are visited separately to prevent encryption (lib/src/engine/passes/string_encrypt/string_encrypt_transformer.dart:38):Performance Considerations
Runtime Cost
Decoding overhead: Each encrypted string incurs a small XOR operation at first use.- 20 XOR operations (trivial)
- UTF-8 decoding (built-in VM operation)
Binary Size
Increases by:- Decoder function: ~200-500 bytes (injected once)
- Encrypted strings: ~same size as original (UTF-8 bytes → int literals)
Decompilation Resistance
Without encryption:- Finding the decoder function
- Extracting the XOR key
- Decoding all byte arrays
Best Practices
Exclude Patterns
Always exclude patterns that need to stay plaintext:Rotate XOR Keys
Change thexor_key for each release to prevent reuse attacks:
Combine with Rename
Run Rename before String Encryption to obfuscate the decoder function name:Cache Decoded Strings
For hot-path strings, cache the decoded result:Limitations
Const Strings
Const strings cannot be encrypted:final variables:
Annotations
Annotation arguments stay plaintext:Switch Case Labels
Switch case labels must be const:if-else or maps:
Troubleshooting
Error: 'Cannot invoke const constructor in non-const context'
Error: 'Cannot invoke const constructor in non-const context'
You tried to encrypt a string in a const context. Use
exclude_patterns or change to final:URLs are broken after encryption
URLs are broken after encryption
Add URL patterns to
exclude_patterns:Annotations contain encrypted strings
Annotations contain encrypted strings
This shouldn’t happen — annotations are automatically skipped. File a bug report if you see encrypted annotation strings.
Performance is slow
Performance is slow
Check if you’re decoding the same string repeatedly in a hot loop. Cache decoded strings instead.
Next Steps
Dead Code Injection
Insert unreachable branches to confuse decompilers
Obfuscation Overview
Understand how all passes work together