confidential 0.4.1
confidential: ^0.4.1 copied to clipboard
Dart literals obfuscator to defend against static reverse engineering.
Dart Confidential #
A highly configurable and performant tool for obfuscating Dart literals embedded in the application code that you should protect from static code analysis, making the app more resistant to reverse engineering.
This is a Dart port of the Swift Confidential project by SecureVale, bringing the same powerful obfuscation capabilities to the Dart ecosystem.
Simply integrate the tool with your Dart project, configure your own obfuscation algorithm along with the list of secret literals, and build the project 🚀
Motivation #
Pretty much every single app has at least few literals embedded in code, those include: URLs, various client identifiers (e.g. API keys), pinning data (e.g. X.509 certificates or SPKI digests), database connection strings, RASP-related literals (e.g. list of suspicious packages or file paths for tamper detection), and many other context-specific literals. While the listed examples of code literals might seem innocent, not obfuscating them, in many cases, can be considered as giving a handshake to the potential threat actor. This is especially true in security-sensitive apps, such as mobile banking apps, 2FA authenticator apps and password managers.
This tool aims to provide an elegant and maintainable solution to the above problem by introducing the composable obfuscation techniques that can be freely combined to form an algorithm for obfuscating selected Dart literals.
Note: While Dart Confidential certainly makes the static analysis of the code more challenging, it is by no means the only code hardening technique that you should employ to protect your app against reverse engineering and tampering. To achieve a decent level of security, we highly encourage you to supplement this tool's security measures with runtime application self-protection (RASP) checks, as well as Dart code obfuscation. With that said, no security measure can ever guarantee absolute security. Any motivated and skilled enough attacker will eventually bypass all security protections. For this reason, always keep your threat models up to date.
Getting Started #
Begin by creating a confidential.yaml YAML configuration file in the root directory of your Dart project. At minimum, the configuration must contain obfuscation algorithm and one or more secret definitions.
For example, a configuration file for a hypothetical security module could look like this:
algorithm:
- encrypt using aes-192-gcm
- shuffle
defaultNamespace: create Secrets
secrets:
- name: suspiciousDynamicLibraries
value:
- Substrate
- Substitute
- FridaGadget
# ... other suspicious libraries
- name: suspiciousFilePaths
value:
- /.installed_unc0ver
- /usr/sbin/frida-server
- /private/var/lib/cydia
# ... other suspicious file paths
Warning: The algorithm from the above configuration serves as example only, do not use this particular algorithm in your production code. Instead, compose your own algorithm from the obfuscation techniques described below and don't share your algorithm with anyone.
Having created the configuration file, you can use the dart-confidential CLI tool to generate Dart code with obfuscated secret literals:
dart run dart-confidential obfuscate --configuration confidential.yaml --output lib/generated/confidential.dart
Upon successful command execution, the generated confidential.dart file will contain code similar to the following:
// GENERATED CODE - DO NOT MODIFY BY HAND
// Generated by dart-confidential
import 'package:confidential/confidential.dart';
import 'dart:typed_data';
import 'dart:convert';
class ObfuscatedLiterals {
static final suspiciousDynamicLibraries = ObfuscatedValue<List<String>>(
Secret(
data: Uint8List.fromList([0x14, 0x4b, 0xe5, 0x48, /* ... */]),
nonce: 13452749969377545032,
),
_deobfuscateData,
);
static final suspiciousFilePaths = ObfuscatedValue<List<String>>(
Secret(
data: Uint8List.fromList([0x04, 0xdf, 0x99, 0x61, /* ... */]),
nonce: 4402772458530791297,
),
_deobfuscateData,
);
static T _deobfuscateData<T>(Uint8List data, int nonce) {
// Deobfuscation implementation
}
}
You can then, for example, iterate over a deobfuscated array of suspicious dynamic libraries in your own code using the projected value of the generated suspiciousDynamicLibraries property:
final suspiciousLibraries = ObfuscatedLiterals.suspiciousDynamicLibraries.$
.map((lib) => lib.toLowerCase())
.toList();
final checkPassed = loadedLibraries
.every((lib) => !suspiciousLibraries.any((suspicious) =>
lib.toLowerCase().contains(suspicious)));
Installation #
Add this package to your pubspec.yaml:
dependencies:
confidential: ^0.4.0
dev_dependencies:
confidential: ^0.4.0
Then run:
dart pub get
Configuration #
Dart Confidential supports a number of configuration options, all of which are stored in a single YAML configuration file.
YAML configuration keys #
| Key | Value type | Description |
|---|---|---|
algorithm |
List of strings | The list of obfuscation techniques representing individual steps that are composed together to form the obfuscation algorithm. See Obfuscation techniques section for usage details. Required. |
defaultAccessModifier |
String | The default access-level modifier applied to each generated secret literal, unless the secret definition states otherwise. The default value is internal. |
defaultNamespace |
String | The default namespace in which to enclose all the generated secret literals without explicitly assigned namespace. The default value is create Secrets. |
experimentalMode |
Boolean | Specifies whether to use experimental mode. The default value is false. |
internalImport |
Boolean | Specifies whether to generate internal import statements. The default value is false. |
secrets |
List of objects | The list of objects defining the secret literals to be obfuscated. See Secrets section for usage details. Required. |
Obfuscation techniques #
The obfuscation techniques are the composable building blocks from which you can create your own obfuscation algorithm. You can compose them in any order you want, so that no one except you knows how the secret literals are obfuscated.
Encryption
This technique involves data encryption using the algorithm of your choice. The encryption technique is polymorphic, meaning that given the same input data, different output data is produced with each run.
Syntax
encrypt using <algorithm>
Supported algorithms:
aes-128-gcm- The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) with 128-bit keyaes-192-gcm- The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) with 192-bit keyaes-256-gcm- The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) with 256-bit keychacha20-poly1305- The ChaCha20-Poly1305 algorithm
Compression
This technique involves data compression using the algorithm of your choice. In general, the compression technique is non-polymorphic, meaning that given the same input data, the same output data is produced with each run. However, Dart Confidential applies additional polymorphic obfuscation routines to mask the bytes identifying the compression algorithm used.
Syntax
compress using <algorithm>
Supported algorithms:
zlib- The zlib compression algorithmgzip- The GZip compression algorithmbzip2- The BZip2 compression algorithmlz4- The LZ4 compression algorithmlzfse- The LZFSE compression algorithm (fallback to gzip)lzma- The LZMA compression algorithm (fallback to bzip2)
Randomization
This technique involves data randomization. The randomization technique is polymorphic, meaning that given the same input data, different output data is produced with each run.
Syntax
shuffle
Secrets #
The configuration file utilizes YAML objects to describe the secret literals, which are to be obfuscated.
| Key | Value type | Description |
|---|---|---|
name |
String | The name of the generated Dart property containing obfuscated secret literal's data. This value is used as-is, without validity checking. Thus, make sure to use a valid property name. Required. |
value |
String or List of strings | The plain value of the secret literal, which is to be obfuscated. The YAML data types are mapped to String and List<String> in Dart, respectively. Required. |
accessModifier |
String | The access-level modifier of the generated Dart property containing obfuscated secret literal's data. The supported values are internal, public and private. If not specified, the top-level defaultAccessModifier value is used. |
namespace |
String | The namespace in which to enclose the generated secret literal declaration. |
Namespaces #
In accordance with Dart programming best practices, Dart Confidential encapsulates generated secret literal declarations in namespaces (i.e. classes). The namespaces syntax allows you to either create a new namespace or extend an existing one.
Syntax
create <namespace> # creates new namespace
extend <namespace> [from <module>] # extends existing namespace, optionally specifying
# the module to which this namespace belongs
Example usage
Assuming that you would like to keep the generated secret literal declaration(s) in a new namespace named Secrets, use the following YAML code:
defaultNamespace: create Secrets
If, however, you would rather like to keep the generated secret literal declaration(s) in an existing namespace named Pinning and imported from Crypto module, use the following YAML code instead:
namespace: extend Pinning from Crypto
Usage #
Build Runner Integration (Recommended) #
The easiest way to use dart-confidential is with build_runner, which automatically generates obfuscated code when you build your project:
- Add build_runner to your
pubspec.yaml:
dev_dependencies:
build_runner: ^2.4.7
confidential: ^0.4.0
- Create a
confidential.yamlconfiguration file in your project root - Run the build:
dart run build_runner build
The obfuscated code will be automatically generated in lib/generated/confidential.dart.
CLI Usage #
You can also use the command-line tool directly:
# Basic usage
dart run dart-confidential obfuscate -c confidential.yaml -o lib/generated/confidential.dart
# Show help
dart run dart-confidential --help
# Show version
dart run dart-confidential --version
Commands #
obfuscate- Obfuscate literals based on configuration
Options #
-c, --configuration- Path to the configuration file (required)-o, --output- Output file path (required)-h, --help- Show help message-v, --version- Show version information
Examples #
See the example/ directory for complete examples:
example/confidential.yaml- Example configuration fileexample/confidential_example.dart- Example usageexample/- Complete Flutter app demonstrating build_runner integration
Running the Flutter Example #
- Navigate to the example directory:
cd example
- Get dependencies:
dart pub get
- Generate obfuscated code:
dart run build_runner build
- Run the Flutter app:
flutter run
The example app demonstrates how to use obfuscated literals in a real Flutter application.
Security Considerations #
Warning: The example algorithms in this documentation are for demonstration purposes only. Do not use these particular algorithms in your production code. Instead, compose your own algorithm from the available obfuscation techniques and don't share your algorithm with anyone.
Following secure SDLC best practices, consider not committing the production algorithm in your repository, but instead configure your CI/CD pipeline to run a custom script (ideally just before the build step), which will modify the configuration file by replacing the algorithm value with the one retrieved from the secrets vault.
Differences from Swift Confidential #
This Dart implementation maintains feature parity with the original Swift Confidential while adapting to Dart's ecosystem:
- Property Wrappers → ObfuscatedValue: Dart doesn't have property wrappers, so we use
ObfuscatedValue<T>classes instead - Macros → Code Generation: Instead of Swift macros, we use a CLI tool for code generation
- Package Manager: Uses
pubinstead of Swift Package Manager - Platform Support: Supports all Dart platforms (Flutter, web, server, etc.)
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Acknowledgments #
This project is a Dart port of Swift Confidential by SecureVale. We thank the original authors for their excellent work and for making it available under an open-source license.