Menu
get a trial
DexProtector Android documentation
iOS documentation

Documentation

Documentation menu

Android

DexProtector documentation

Introduction to DexProtector

1. What is DexProtector?

DexProtector is the complete, offline, one-click security solution to secure apps and libraries on both Android and iOS platforms. Here's how it works:

DexProtector first analyzes, then secures, then signs your app, library, or SDK. And once the app has been released, DexProtector continues to monitor and protect it during runtime (RASP), with all data about risks, threats, and attacks sent automatically to your account in Licel's Attack Telemetry and Threat Intelligence Service (Alice).

DexProtector's protection mechanisms include:

  • Code hardening and resource hardening through obfuscation and encryption, to prevent static analysis and reverse engineering
  • Runtime Application Self-Protection (RASP), with constant checks on the operating environment and communications network, to prevent dynamic analysis and Man-in-the-Middle attacks
  • Integrity checks, to prevent code tampering and cloning
  • Dedicated CryptoModule, isolating cryptographic data and processing in a Trusted Execution Environment to prevent interception of cryptographic keys and calculations

DexProtector works directly with compiled packages (APKs, AABs, AARs, IPAs, xcarchives, and iOS Frameworks), with no coding necessary at your end; all you need to do is download DexProtector, locate your package, specify your configuration options, and then Run Protection.

For the full feature matrix, please click here.

2. The DexProtector process

  1. Feed DexProtector your APK/AAB/AAR/IPA/xcarchive/iOS Framework file as an input.
  2. Choose your protection settings (using DexProtector's configuration file, or via the 'Tune Protection Settings' window in DexProtector Studio).
  3. DexProtector analyzes your package, locating every element specified for protection.
  4. DexProtector systematically secures your package, applying its protection mechanisms at bytecode and native levels to classes, methods, strings, and resources, and integrating its Runtime Application Self-Protection engines automatically.
  5. DexProtector outputs the now-protected APK/AAB/AAR/IPA/iOS Framework file, signed as specified in your configuration: ready for testing or release as soon as you choose.
  6. Once your app has been released, DexProtector can continue to monitor its security as it is being used, with all data about risks, threats, and attacks being sent automatically to your account in Licel's Attack Telemetry and Threat Intelligence Service (Alice).

3. DexProtector Studio

DexProtector Studio is our desktop application, included with every DexProtector license. Studio offers a straightforward GUI where you can control every aspect of the DexProtector protection process. Studio allows you to:

  • activate your DexProtector license
  • observe DexProtector's analysis of your input file
  • configure the protection settings for your project
  • create protection profiles
  • visualize exactly how DexProtector's protection mechanisms are applied to your code and resources
  • manage your Alice integration

4. Alice - Licel's Attack Telemetry and Threat Intelligence System

Once your app has been released, DexProtector continues to monitor its security as it is being used anywhere in the world, with all data about risks, threats, and attacks being sent automatically to your account in Licel's Attack Telemetry and Threat Intelligence Service (Alice).

Alice offers an easy-to-use dashboard where you can keep track of key incidents as they occur, including any cases of HTTP public key pinning anomalies, tampering, and crashes. You can view these incidents according to when and where they took place, and you can tailor the output data to suit your reporting needs.

DexProtector and Alice can also work in combination with your own risk analysis system, so that you have the security monitoring information you need in the form that works best for your organization.


For more information, or to request a trial or full license for DexProtector, please get in touch via the contact form.


DexProtector for Android

Overview

DexProtector secures Android applications, libraries, and SDKs against static and dynamic analysis, tampering, reverse engineering, and Man-in-the-Middle attacks.

As the final stage of the build process, DexProtector works on compiled packages of any size, integrating its protection mechanisms at both bytecode and native levels.

And it is easy to integrate DexProtector into any development cycle, with support for both native and cross-platform apps, as well as build tools like Gradle, and CI/CD platforms like Bitrise, Jenkins, Azure Pipelines, and Bitbucket Pipelines. For more information, see our Implementations and Integrations page.

Key features of DexProtector for Android:

Code Protection

  • String Encryption
  • Class Encryption
  • Hide Access to Method Calls and Fields
  • Native Code Obfuscation
  • Native Code Encryption
  • Native Code Anti-Debugging
  • Annotation Encryption

Content Protection

  • Resource encryption
  • HTML, JS, & CSS code encryption
  • DRM for media resources
  • Game engine resource encryption
  • Support for external access to encrypted resources
  • Encryption of res folder & strings.xml
  • AndroidManifest mangling
  • Encryption of resources in root folder
  • Cryptographic material encryption (Note: This line seems to continue from the previous page)

Integrity Control

  • Certificate Checks
  • Code Integrity Checks
  • Content Integrity Checks

Network Security

  • Network security status monitoring
  • Public Key Pinning
  • Certificate Transparency

Runtime Application Self-Protection (RASP)

  • Anti-debug mechanisms
  • UI protection mechanisms
  • Environment checks (detection and reporting of rooted devices; emulators; debuggers; hooking; tampering; and more)

Note: For some features, a DexProtector Enterprise license is required. For more information, see our feature comparison for DexProtector Standard and DexProtector Enterprise.


Getting started

1. Download

Downloading and activating DexProtector is straightforward. Once you have requested a trial or purchased a full license, we will send you an email containing a download link and a unique, single-use activation code.

Click the link to download a zip file containing the distribution package. Extract the contents of the zip file to the destination folder.

Distribution package contents

  • Executable jar file – dexprotector.jar
  • Annotations library – dexprotector-annotations.jar
  • Maven plugin – dexprotector-maven-plugin.jar
  • Gradle plugin – dexprotector-gradle-plugin.jar
  • Configuration file – dexprotector.xml
  • Ant settings file – custom_rules.xml
  • License texts – NOTICE, LICENSE

⚠️ The email containing the distribution package will also contain a link to DexProtector Studio. For more information, see our guide to DexProtector Studio.

2. Activate

You can activate DexProtector either via your command-line interface, or via DexProtector Studio; activation is possible both online and offline.

⚠️ Activation codes are single-use. If the original license file is lost, or if the hardware or software configuration is changed, you will need to contact support for a new code.

Activate via CLI (online)

To activate DexProtector online via the command line, run the following command, then follow the instructions generated:

java -jar dexprotector.jar -activate

After successful activation, the license file dexprotector.licel will be created in the user's home folder.

To use a license file not located in the home folder, specify the path to the license file via the special option provided in the CLI.

Activate via CLI (offline)

To activate DexProtector offline via the command line, run the following command:

java -jar dexprotector.jar -activationRequest # Corrected command based on context

Enter your activation code as prompted. When you have entered a valid activation code, a request code will be generated. Email this request code to our support team.

You will receive a response code as soon as possible, within a maximum of 1 business day.

When you have received the response code, run the following command and then enter the response code when prompted:

java -jar dexprotector.jar -activationResponse # Corrected command based on context

After successful activation, the license file dexprotector.licel will be created in the user's home folder.

To use a license file not located in the home folder, it is necessary to specify the path to the license file.

Activate via DexProtector Studio (online)

  1. Click on 'License Info' to the right of the bar at the bottom of the window.
  2. Click 'Activate License' and follow the instructions in the new window. To activate your license and obtain a license file online, simply enter the unique, single-use activation code provided in the email with your download links. If you don't have an active Internet connection, or prefer offline activation, see how to Activate via DexProtector Studio (offline).
  3. After successful activation, the license file dexprotector.licel will be created in the user's home folder.

Activate via DexProtector Studio (offline)

  1. Click on 'License Info' to the right of the bar at the bottom of the window.
  2. Click 'Activate License' and follow the instructions in the new window. If you don't have an active Internet connection, or have prefer offline activation, enter the unique, single-use activation code provided in the email with your download links, and then click 'Generate activation request'.
  3. When you have entered a valid activation code, a request code will be generated. Email this request code to our support team. You will receive a response code as soon as possible, within a maximum of 1 business day.
  4. When you have received your response code, return to the same 'Activate License' window, and click 'I Already Have My Activation Response Code'. In the following window, enter the response code you've received and click 'Activate' to complete the license activation procedure.
  5. After successful activation, the license file dexprotector.licel will be created in the user's home folder.

3. After activation

Once you have successfully activated DexProtector, you can begin the process of protecting your app. The first step is to specify what you want to protect and how exactly you want to protect it by configuring your protection settings.


Configuring DexProtector

Introduction to configuring DexProtector

DexProtector works best when it is tailored to your app.

Configuration is by means of a single XML file, which can be edited directly or via the DexProtector Studio interface. A default configuration file (dexprotector.xml) can be found in the root folder of the distribution package, but every app (or SDK) has different requirements. We therefore strongly recommend that you create your own tailored configuration, in order to target the correct code and resources for protection.

You can use the configuration file to control the DexProtector process according to your needs. The configuration file allows you to specify the details of:

  • Build and logging settings for the DexProtector process
  • Signing methods
  • Protection mechanisms and filters for including/excluding code and resources for protection
  • Environment checks, for the detection of rooted devices; debuggers; emulators; and hooking tools
  • Network security options, including certificate monitoring for both Certificate Transparency and Public Key Pinning mechanisms
  • Integration with Licel's Threat Reporting and Attack Telemetry system, Alice

Protection Recommendations

We recommend making use of all of the security features provided, as each element of protection adds more security and more resistance against malware, reverse engineering, tampering, and Man-in-the-Middle attacks.

Note: For some features, a DexProtector Enterprise license is required. For more information, see our feature comparison for DexProtector Standard and DexProtector Enterprise.

Configuration file overview

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<dexprotector>

    <!-- BUILD SETTINGS -->
    <verbose>false</verbose>
    <optimize>false</optimize>
    <securityAssessment>
        <signingCertificateCompromised mode="error"/>
        <signingCertificateWeakKey mode="error"/>
        <dependencyCheck mode="warning" />
    </securityAssessment>
    <proguardMapFile>no_default</proguardMapFile>

    <!-- SIGNING -->
    <signMode>debug</signMode>
    <keystore>no_default</keystore>
    <storepass>no_default</storepass>
    <alias>no_default</alias>
    <keypass>no_default</keypass>
    <certificate>no_default</certificate>
    <sha256CertificateFingerprint>no_default</sha256CertificateFingerprint>
    <legacySha256CertificateFingerprint>no_default</legacySha256CertificateFingerprint>

    <!-- CODE STRIPPING -->
    <stripLogging>no_default</stripLogging>
    <stripMethodCalls>
        <filters>
            <filter>no_default</filter>
        </filters>
    </stripMethodCalls>

    <!-- CODE PROTECTION -->
    <stringEncryption/>
    <annotationEncryption/>
    <jniObfuscation/>
    <hideAccess/>
    <classEncryption/>
    <nativeLibraryEncryption/>

    <!-- RESOURCE PROTECTION -->
    <resourceEncryption>
        <assets>
            <filters>
                <filter>no_default</filter>
            </filters>
        </assets>
        <res>
            <filters>
                <filter>no_default</filter>
            </filters>
        </res>
        <strings>
            <filters>
                <filter>no_default</filter>
            </filters>
        </strings>
        <nameObfuscation> <!-- Corrected typo from OCR -->
            <filters>
                <filter>no_default</filter>
            </filters>
        </nameObfuscation>
        <androidManifestMangling/>
        <xamarinAssemblies/>
    </resourceEncryption>

    <!-- RASP - ENVIRONMENT CHECKS -->
    <antiDebug>true</antiDebug>
    <antiEmulator>true</antiEmulator>
    <antiManualInstall>true</antiManualInstall> <!-- Corrected typo from OCR -->
    <antiMalware>true</antiMalware>
    <runtimeChecks/>

    <!-- NETWORK SECURITY -->
    <publicKeyPinning src="no_default">
        <trace>0</trace>
        <actions>block, report</actions>
        <network-security-config>
            <domain-config>
                <domain includeSubdomains="false">...</domain> <!-- Corrected typo from OCR -->
                <pin-set expiration="no_default">
                    <pin digest="no_default"></pin>
                </pin-set>
            </domain-config>
        </network-security-config>
    </publicKeyPinning>
    <certificateTransparency>
        <trace>0</trace>
        <domain includeSubdomains="false">no_default</domain> <!-- Corrected typo from OCR -->
        <!-- <logFile>no_default</logFile> -->
    </certificateTransparency>

    <!-- UI PROTECTION -->
    <uiProtection/>

    <!-- THREAT REPORTING AND TELEMETRY (ALICE) -->
    <reportMonitoring>
        <apiKey>no_default</apiKey>
        <!-- <customFieldsUpdate>no_default</customFieldsUpdate> -->
        <trace>0</trace>
    </reportMonitoring>

    <!-- AAR PARAMETERS -->
    <aar autoInit="on" autoInitAuthorities="${applicationId}"> <!-- Corrected typo from OCR -->
        <!-- <initMethod>...</initMethod> -->
        <!-- <initClass>...</initClass> -->
        <!-- <kotlinSupport>...</kotlinSupport> -->
        <!-- <nativeClassEncryption>...</nativeClassEncryption> -->
        <!-- <nativeHideAccess>...</nativeHideAccess> -->
    </aar>

</dexprotector>

Configuration Settings Details

(Note: The following sections detail the elements shown in the overview above. Formatting uses headings for categories and sub-sections for individual elements.)

Build Settings

Verbose Logging (boolean)

  • Element: <verbose>
  • Description: Enables/disables verbose logging for the DexProtector process.
  • Valid values: true; false.
  • Default value: false
    <verbose>false</verbose>

Code Optimization (boolean)

  • Element: <optimize>
  • Description: Removes redundant metadata from the package that might otherwise affect performance and/or be exploited by bad actors.
  • Valid values: true; false.
  • Default value: false
    <optimize>false</optimize>

Security Assessment

  • Element: <securityAssessment>
  • Description: With Security Assessment enabled, the DexProtector process will fail if the signing certificate is known to be compromised, or recognized as weaker than recommended.
  • Valid values: true; false.
  • Default value: false
  • Nested elements: (<signingCertificateCompromised>, <signingCertificateWeakKey>, <dependencyCheck>)
    • Element (nested): <signingCertificateCompromised>
      • Description: If the signing certificate has been compromised, the DexProtector process will throw an error and the build will fail.
      • Valid values: warning; error; off.
      • Default value: error
    • Element (nested): <signingCertificateWeakKey>
      • Description: If the signing key is weak (i.e. an RSA key that is <2048 bits), the DexProtector process will throw an error and the build will fail.
      • Valid values: warning; error; off.
      • Default value: error
    • Element (nested): <dependencyCheck>
      • Description: Checks application dependencies for known vulnerabilities.
      • Valid values: warning; error; off.
      • Default value: warning
        <securityAssessment>
        <signingCertificateCompromised mode="error"/>
        <signingCertificateWeakKey mode="error"/>
        <dependencyCheck mode="warning" />
        </securityAssessment>

ProGuard mapping file (string)

  • Element: <proguardMapFile>
  • Description: Specifies the absolute path to ProGuard's mapping file. If you use ProGuard for name obfuscation, it is necessary to provide the path to ProGuard's mapping file so that DexProtector can locate classes for encryption.
  • Default value: no default value.
  • Note: When using the DexProtector Gradle Plugin, the value for the proguardMapFile (if necessary) is set automatically.
    <proguardMapFile>/path/to/mapping.txt</proguardMapFile>

Signing

Signing Mode (string)

  • Element: <signMode>
  • Description: Specifies options for app signing.
  • Valid values:
    • debug - signature is performed with a debug key, derived from ${USER_HOME}/.android/debug.keystore
    • release - signature is performed with the developer's key
    • google - signing mode to be used with Google Play App Signing (see Google Play App Signing)
    • amazon - signing mode to be used for Amazon Appstore (see Amazon App Signing)
    • none - no signing key. This is only appropriate in cases when signing will take place at a later stage, for example with a manufacturer's or platform key for system apps.
      <signMode>google</signMode>

Keystore information (for signMode == release; signMode == google; signMode == amazon)

  • Element: <keystore>
    • Description: Specifies the path to the keystore containing the relevant signing key.
    • Default value: no default value. Note: With the DexProtector Gradle Plugin, the path is specified automatically.
  • Element: <storepass>
    • Description: Specifies the password to the keystore containing the relevant signing key.
    • Default value: no default value. Note: With the DexProtector Gradle Plugin, the path is specified automatically.
  • Element: <alias>
    • Description: Specifies the alias string for the relevant signing key in your keystore.
    • Default value: no default value. Note: With the DexProtector Gradle Plugin, the path is specified automatically.
  • Element: <keypass>
    • Description: Specifies the password for the signing key.
    • Default value: no default value. Note: With the DexProtector Gradle Plugin, the path is specified automatically.
      <keystore>/home/developer/example.keystore</keystore>
      <storepass>examplestorepass</storepass>
      <alias>examplealias</alias>
      <keypass>examplekeypass</keypass>

Keystore information (for signMode == google)

  • Element: <sha256CertificateFingerprint>
    • Description: Specifies the SHA-256 Certificate Fingerprint corresponding to the Google Play App Signing Key used in Play app signing.
    • Default value: no default value. Note: with the DexProtector Gradle plugin, the value is derived automatically.
  • Element: <legacySha256CertificateFingerprint>
    • Description: If you publish your app to Google Play, you can upgrade the signing key for your published app through the Play Console—your new key is used to sign new installs and app updates, while your older app signing key is used to sign updates for users who installed your app before the key upgrade. If you have upgraded the signing key for your published app and are DexProtecting an update, you must specify both the SHA-256 Certificate Fingerprint corresponding to the new key, and the Legacy SHA-256 Certificate Fingerprint corresponding to the original key.
    • Default value: no default value.
      <signMode>google</signMode>
      <keystore>/home/developer/upload.keystore</keystore>
      <storepass>upload_password</storepass>
      <alias>upload_alias</alias>
      <keypass>upload_key_password</keypass>
      <sha256CertificateFingerprint>AB:CD:EF:...</sha256CertificateFingerprint>
      <!-- <legacySha256CertificateFingerprint>12:34:56:...</legacySha256CertificateFingerprint> -->

Code Stripping

Logging Call Stripping

  • Element: <stripLogging>
  • Description: Prevents unwanted log outputs by blocking the calling of methods with android.util.Log. The scale of verbosity (from least to most) is WTF, ERROR, WARNING, INFO, DEBUG, VERBOSE.
  • Valid values: wtf [strip android.util.Log.wtf(...)]; error [strip android.util.Log.e(...)]; warning [strip android.util.Log.w(...)]; info [strip android.util.Log.i(...)]; debug [strip android.util.Log.d(...)]; verbose [strip android.util.Log.v(...)]; all [strip all of the above]
    <stripLogging>all</stripLogging>

Method Call Stripping

  • Element: <stripMethodCalls>
  • Description: Blocks the calling of specified methods (for example, the method calls of third-party logging libraries)
  • Element (nested): filters (Only when mode != off):
    • Format: string (using filter syntax)
    • Default value: no default value
      <stripMethodCalls>
      <filters>
         <filter>glob:com/example/debug/Logger.**</filter>
      </filters>
      </stripMethodCalls>

Code Protection

String Encryption

  • Element: <stringEncryption>
  • Description: Enables DexProtector's String Encryption mechanism. Note: We recommend encrypting as many strings as possible, but especially those containing sensitive data (logins, passwords, API credentials, keys, etc.). There is no need, on the other hand, to encrypt any strings contained in publicly available third party libraries, and DexProtector's default configuration file contains certain example filters to exclude such libraries from the string encryption process.
  • Element (nested): filters
    • Format: string (using filter syntax)
    • Default value: no default value
      <stringEncryption>
      <filters>
          <filter>glob:!com/google/**</filter>
          <filter>glob:com/mycompany/sensitive/**</filter>
      </filters>
      </stringEncryption>

Annotation Encryption

  • Element: <annotationEncryption>
  • Description: Encrypt Kotlin annotations to protect sensitive metadata. Important: Filters for Annotation Encryption work differently than for other mechanisms. Filters must target the classes in which annotations are defined, and not the classes in which annotations are referenced. DexProtector will then locate all instances of the annotations and encrypt them.
  • Element (nested): filters
    • Format: string (using filter syntax)
    • Default value: no default value
      <annotationEncryption>
      <filters>
          <filter>glob:kotlin/Metadata.class</filter> <!-- Example: Encrypt standard Kotlin metadata -->
          <filter>glob:com/mycompany/annotations/SecretAnnotation.class</filter>
      </filters>
      </annotationEncryption>

JNI Obfuscation

  • Element: <jniObfuscation>
  • Description: Enables the obfuscation of JNI (Java Native Interface) method names in native libraries and classes, in accordance with the specified filters. Note: When setting filters for JNI Obfuscation, you only need to specify the classes that contain JNI methods. DexProtector will then automatically process native libraries containing the JNI methods from those classes.
  • Element (nested): filters
    • Format: string (using filter syntax)
    • Default value: no default value
      <jniObfuscation>
      <filters>
          <filter>glob:com/mycompany/nativeinterface/Bridge.class</filter>
      </filters>
      </jniObfuscation>

Hide Access

  • Element: <hideAccess>
  • Description: The Hide Access mechanism conceals method calls and field accesses in the packages and classes specified in the filters, breaking the link between the call site and the function being called.
  • Element (nested): filters
    • Format: string (using filter syntax)
    • Default value: no default value
      <hideAccess>
      <filters>
          <filter>glob:!com/google/**</filter>
          <filter>glob:com/mycompany/internal/**</filter>
      </filters>
      </hideAccess>

Class Encryption

  • Element: <classEncryption>
  • Description: DexProtector encrypts entire classes.dex including Application, Activities, ContentProviders, and Receivers, in accordance with the specified filters. Note: DexProtector will encrypt all classes by default, without affecting the application's performance. However, it is important to exclude any classes that must remain accessible to third parties, such as those that form part of a public API.
  • Element (nested): filters
    • Format: string (using filter syntax)
    • Default value: no default value (encrypts all if element present without filters)
      <classEncryption>
      <filters>
          <filter>glob:!com/mycompany/publicapi/**</filter>
      </filters>
      </classEncryption>

Native Library Encryption

  • Element: <nativeLibraryEncryption>
  • Description: DexProtector encrypts native libraries (.so files) within the target package, in accordance with the chosen filters.
  • Element (nested): filters
    • Format: string (using filter syntax)
    • Default value: no default value
      <nativeLibraryEncryption>
      <filters>
          <filter>glob:lib/armeabi-v7a/libsecret.so</filter>
      </filters>
      </nativeLibraryEncryption>

Resource Protection

Resource Encryption

  • Element: <resourceEncryption>
  • Description: Resource encryption protects against malicious copying, modification, and piracy by encrypting an application's internal resources; resource names; and, for cross-platform apps, HTML, JS, and CSS code. Filters can target the assets and res folders, as well as resources in the root folder, and individual string resources.
  • Nested elements: (<assets>, <res>, <strings>, <nameObfuscation>, <root>, <androidManifestMangling>, <xamarinAssemblies>)
    • Element (nested): assets
      • Description: Encrypts files in assets/. Files can be targeted by file pattern (i.e. *.png denotes all files of PNG file format), name pattern (i.e. File1* denotes all files whose names begin with the string "File1"), by specific file name (e.g. File2.json), or by path (e.g. TestDir/File3.txt). Note 1: If there are no assets/res elements in the configuration file under the resourceEncryption section, the encryption of the assets folder will be performed in accordance with DexProtector's default settings. Note 2: Assets directly accessed by the OS must not be encrypted.
      • Format: contains nested filters elements.
    • Element (nested): res
      • Description: Encrypts files in res/. Files can be targeted by file pattern (i.e. *.png), name pattern (i.e. File1**), specific file name (e.g. File2.json), or by path (e.g. TestDir/File3.txt). Note 1: If there are no assets/res elements... default settings apply. Note 2: Resources directly accessed by Android must not be encrypted.
      • Format: contains nested filters elements.
    • Element (nested): strings
      • Description: Encrypts strings and string arrays in resources.arsc. Note: If there is no 'strings' element... default settings apply.
      • Format: contains nested filters elements.
    • Element (nested): nameObfuscation
      • Description: With Resource Name Obfuscation enabled, DexProtector will obfuscate names of files in res/. It is possible to set filters for Resource Name Obfuscation independently of the filters specified for res folder encryption. For more information, see the guide to filters for resource encryption.
      • Format: contains nested filters elements.
    • Element (nested): root
      • Description: Encrypts files in root/. Files can be targeted by file pattern (i.e. **.png), name pattern (i.e. File1**), specific file name (e.g. File2.json), or by path (e.g. TestDir/File3.txt).
      • Format: contains nested filters elements.
    • Element (nested): androidManifestMangling
      • Description: Mangling settings for AndroidManifest.xml. If the element androidManifestMangling is included in the configuration file, DexProtector will mangle entities in the AndroidManifest.xml file.
    • Element (nested): xamarinAssemblies
      • Description: Encrypts Xamarin assemblies.
      • Attribute: dir
      • Description: If the assemblies are not in the default folder (assets/assemblies), you can set a path to them using the attribute dir. The path should start from the APK's root, for example: assets/xxx/assemblies
      • Format: string
      • Default value: no default value
        <resourceEncryption nameObfuscation="on">
        <assets>
        <filters><filter>glob:data/**</filter></filters>
        </assets>
        <res>
        <filters><filter>glob:raw/**</filter></filters>
        </res>
        <strings>
        <filters><filter>my_secret_string_key</filter></filters>
        </strings>
        <nameObfuscation>
         <filters><filter>layout/secret_layout</filter></filters>
        </nameObfuscation>
        <root>
        <filters><filter>important_config.dat</filter></filters>
        </root>
        <androidManifestMangling/>
        <xamarinAssemblies dir="assets/assemblies"/>
        </resourceEncryption>

RASP (Runtime Application Self-Protection) - Environment & Runtime Checks

Anti-Debug

  • Element: <antiDebug>
  • Description: With this setting enabled, the DexProtector Runtime Engine will close the app instantly if it detects that a debugger is attached.
  • Valid values: true, false
  • Default value: true

Anti-Emulator

  • Element: <antiEmulator>
  • Description: With this setting enabled, the DexProtector Runtime Engine will prevent the app from running on an emulator. Note: Client-side API and callback options are available as an alternative to closing the app. Please request more details on configuration and implementation.
  • Valid values: true, false
  • Default value: true

Anti-Manual Install

  • Element: <antiManualInstall>
  • Description: With this setting enabled, the DexProtector Runtime Engine will prevent the app from running if it was sideloaded. Note: Client-side API and callback options are available as an alternative to closing the app. Please request more details on configuration and implementation.
  • Valid values: true, false
  • Default value: true

Anti-Malware

  • Element: <antiMalware>
  • Description: With this setting enabled, the DexProtector Runtime Engine will report malware and Potentially Harmful Apps detected on the device to the organization's Alice Threat Intelligence database. Note: <reportMonitoring> must be enabled. Client-side API and callback options are also available. Please request more details on configuration and implementation.
  • Valid values: true, false
  • Default value: true

Runtime Checks

  • Element: <runtimeChecks>
  • Description: With this setting enabled, the DexProtector Runtime Engine will prevent the app from running on custom firmware and rooted devices.
  • Valid values: (Element presence enables it)
  • Default value: Enabled if present

Network Security

Public Key Pinning

  • Element: <publicKeyPinning>
  • Description: Settings for SSL/HTTP Public Key Pinning. These can be specified either via a security configuration file in res/xml (in accordance with specification for Android N), or via the network-security-config nested element. If you have a separate security configuration file, you must set the path to it within the attribute src.
  • Attribute: src
    • Valid values: Path to a security configuration file (see details here).
    • Default value: no default value
  • Note: if the file exists, all public key pinning settings are taken from the file. On the other hand, if the src attribute is not set and there is a network-security-config tag, all the settings are taken from the configuration defined inside the tag and the res/xml/networksecurityconfig.xml will be created. It will contain settings from the network-security-config tag, and also the following information will be added into the AndroidManifest.xml:
    <application ...>
        <meta-data android:name="android.security.net.config"
                   android:resource="@xml/networksecurityconfig" />
        ...
    </application>
  • Format: contains nested elements (<actions>, <reportUri>, <reportMethod>, <network-security-config>)
    • Element (nested): actions
      • Description: Specifies the actions to be performed if there are errors or anomalies detected during the Public Key Pinning checks.
      • Format: list with the , separator
      • Valid values: block - block the connection; report - send a report regarding the connection
      • Default value: block, report
    • Element (nested): reportUri
      • Description: Specifies the address that will be used to send JSON reports regarding any errors or anomalies detected during the Public Key Pinning checks.
      • Format: string (URL)
    • Element (nested): reportMethod
      • Description: Specifies a method (in the format ClassName.methodName) to which JSON reports are passed in the event of any errors or anomalies detected during the Public Key Pinning checks. These methods should have public static modifier and (String jsonStr) signature.
      • Format: string
    • Element (nested): cacheTTL (Note: OCR shows this but it's not typically part of standard PKP config)
      • Description: Time to live for a server SSL certificate chain check result for each domain.
      • Format: int (seconds)
      • Default value: 180
    • Element (nested): network-security-config
      • Description: Embedded Security Configuration File. See Android documentation for format.
      • Example #1 (Embedded):
        <publicKeyPinning>
            <network-security-config>
                <domain-config>
                    <domain includeSubdomains="true">example.com</domain>
                    <pin-set expiration="2025-01-01">
                        <pin digest="SHA-256">...</pin>
                    </pin-set>
                </domain-config>
            </network-security-config>
        </publicKeyPinning>
      • Example #2 (External File):
        <publicKeyPinning src="@xml/my_network_security_config"/>

Certificate Transparency

  • Element: <certificateTransparency>
  • Description: Settings for monitoring public key certificates according to the Certificate Transparency standard. DexProtector uses a list of log servers that is located in the distribution package. This list is based on: chromium log list. Alternatively, a list of authorized log servers can be specified manually by entering a path to a file containing that list.
  • Format: contains nested elements (<trace>, <logFile>)
    • Element (nested): trace
      • Format: string (integer)
      • Description: For debugging purposes, set trace to 1000.
      • Default value: no default value (effectively 0)
    • Element (nested): logFile
      • Format: string (path)
      • Description: Path to file containing your own list of authorized log servers.
      • Default value: no default value (uses internal list)
        <certificateTransparency>
        <trace>0</trace>
        <!-- <domain includeSubdomains="false">example.com</domain> -->
        <!-- <logFile>/path/to/custom_log_list.json</logFile> -->
        </certificateTransparency>

UI Protection

UI Protection

  • Element: <uiProtection>
  • Description: DexProtector's UI Protection blocks screen capture and prevents activity hijacking. Screen capture blocking hardens your app against screenshots, screen recording, and screen casting. Hijacking prevention protects the app from malware that takes control of the application and replaces legitimate windows with impostors.
  • Valid values: (Element presence enables it)
  • Default value: Enabled if present

Threat Reporting and Telemetry - Alice Integration

Threat Reporting

  • Element: <reportMonitoring>
  • Description: Configures Licel's Real-Time Attack Telemetry and Threat Intelligence service, Alice. For more information, see our guide to Alice.
  • Format: contains nested elements (<apiKey>, <customFieldsUpdate>, <trace>)
    • Element (nested): apiKey
      • Format: string
      • Default value: no default value
    • Element (nested): customFieldsUpdate
      • Format: string (fully qualified class name)
      • Description: Specifies a method, if one is implemented in the package's code, for customizing the types of data reported to Alice by the monitoring system (e.g. user's IP address; device serial number, etc.). The method specified must have the following format:
        // Example structure
        package com.example;
        import java.util.Map;
        public class AliceCustomData {
            public static void updateFields(Map<String, String> values) {
                values.put("custom_field_1", "some_value");
                values.put("custom_field_2", getDynamicValue());
            }
            private static String getDynamicValue() { return "dynamic"; }
        }
      • Default value: no default value, but the field must have the following structure if used:
        <customFieldsUpdate><![CDATA[com.example.AliceCustomData]]></customFieldsUpdate>
    • Element (nested): trace
      • Format: string (integer, typically 0 or 1000)
      • Description: The logging level of DexProtector messages on the end device. For debugging purposes, set to 1000. Otherwise, set to 0 or exclude the <trace> node.
      • Default value: no default value (effectively 0)
        <reportMonitoring>
        <apiKey>YOUR_ALICE_API_KEY</apiKey>
        <customFieldsUpdate><![CDATA[com.example.AliceCustomData]]></customFieldsUpdate>
        <trace>0</trace>
        </reportMonitoring>

AAR Parameters

AAR

  • Element: <aar>
  • Description: Enables the DexProtection of Android libraries & SDKs (AAR). Since DexProtector's runtime engine must start before the AAR is first accessed, it is necessary to configure the initialization process, and/or to leave DexProtector's auto-initialization mechanism enabled.
  • Attribute: autoInit
    • Valid values: on - enable DexProtector's automatic initialization mechanism; off - disable DexProtector's automatic initialization mechanism. If autoInit="off" you must specify either initMethod(s) and/or initClass(es).
    • Default value: on
  • Attribute: autoInitAuthorities
    • Description: To ensure that DexProtector's runtime engine is initialized when necessary, the autoInit mechanism uses a Content Provider approach, and defines a content provider component in the AndroidManifest.xml. Therefore, if autoInit="on" we strongly recommend to specify autoInitAuthorities="${applicationId}", additionally specifying a unique ID for each AAR where necessary, to avoid the risk of conflict whereby multiple content providers with the same name are specified in android:authorities.
    • Default value: ${applicationId}
  • Attribute: kotlinSupport
    • Valid values: on - enable support for libraries containing Kotlin code; off - disable support for libraries containing Kotlin code
    • Default value: off
  • Attribute: nativeClassEncryption
    • Valid values: on - enable the encryption of native classes within the library; off - disable the encryption of native classes within the library
    • Default value: on
  • Attribute: nativeHideAccess
    • Valid values: on - enable the Hide Access mechanism for native classes within the library; off - disable the Hide Access mechanism for native classes within the library
    • Default value: off
  • Example (Auto Init - Recommended):
    <aar autoInit="on" autoInitAuthorities="${applicationId}.myLibId" kotlinSupport="on"/>
  • Example (Manual Init):
    <aar autoInit="off" kotlinSupport="on">
        <initMethod>com.example.library.Initializer.initializeDexPro</initMethod>
        <initClass>com.example.library.MyReceiver</initClass>
    </aar>

Filters: A guide to targeting code and resources

DexProtector therefore provides a flexible mechanism for setting custom filters for each of its Code Protection, Resource Protection, and Code Stripping mechanisms.

If the protection mechanisms are enabled but no filters are specified, DexProtector automatically protects all strings in all classes (String Encryption), all methods, fields, and field accesses in all classes (Class Encryption, Hide Access), and also encrypts resources (Resource Encryption). This offers maximum protection, and is worth trying during the testing phase, but can occasionally lead to inefficiencies in the application's performance. This is especially true of the Hide Access protection mechanism, when particularly demanding loops are involved. We therefore recommend using the filters to fine-tune your protection where necessary.

You can use 'glob' or 'regex' syntax modes to apply protections according to specified name patterns.

To set a filter to include a given package or class in the protection process, simply specify the syntax mode and the name pattern in the form: 'syntax:pattern', where : stands for itself.

To exclude a given package or class from protection, use an exclamation mark (!) before the name pattern, in the form: 'syntax:!pattern'

The following are guides to filters configured for inclusion, exclusion, and both, using the glob syntax mode.

Inclusion

Protecting every class in a specified package

Say we want to configure a filter in order to specify which classes DexProtector should include in its Class Encryption process. In our classes.dex we have the following structure of packages:

android/support/...
com/google/..
com/facebook/..
com/mycompany/..
com/mycompany/ui/..
com/mycompany/core/..

In this case, we want DexProtector to encrypt all of the classes contained in the com/mycompany package. We should therefore set the following filter in our configuration file:

<filter>glob:com/mycompany/**</filter>
Protecting a single class

To protect a single class – for example com/mycompany/core/InternalEngine.class – we use the following filter format:

<filter>glob:com/mycompany/core/InternalEngine.class</filter>

Exclusion

Excluding every class in a specified package from protection

Exclusion filters are set by adding the exclamation mark (!) before the class or package name pattern:

<filter>glob:!android/support/**</filter>
<filter>glob:!com/google/**</filter>
<filter>glob:!com/facebook/**</filter>
Excluding a single class from protection

Exclusion filters for classes are done in the same way, by adding the exclamation mark (!) before the classpath:

<filter>glob:!com/mycompany/core/BumpGenerator.class</filter>

Mixed filters: Combining inclusion and exclusion

One possible (and recommended) approach for String Encryption and Hide Access is first to set a global exclusion filter, to exclude every class from protection, and then to target specific classes for inclusion. For Class Encryption, no problems should arise from encrypting every class in the package, except for any that must remain accessible to third parties, such as those that form part of a public API.

This would be a simple example configuration for String Encryption, Class Encryption, and Hide Access:

<dexprotector>
    ***
    <stringEncryption mode="filters">
        <filters>
            <filter>glob:!**/**</filter> <!-- Exclude everything globally -->
            <filter>glob:com/test/**</filter> <!-- Include specific package -->
        </filters>
    </stringEncryption>

    <classEncryption/> <!-- DexProtector will encrypt all classes by default -->
                        <!-- Add filters here only if needed to EXCLUDE public APIs -->
                        <!-- <filters><filter>glob:!com/mycompany/publicapi/**</filter></filters> -->

    <hideAccess mode="filters">
        <filters>
            <filter>glob:!**/**</filter> <!-- Exclude everything globally -->
            <filter>glob:com/test/**</filter> <!-- Include specific package -->
        </filters>
    </hideAccess>
    ***
</dexprotector>

Detailed filter syntax information

Filters are determined by the syntax mode and the pattern and take the form: 'syntax_mode:pattern', where : stands for itself. The value of the syntax component is not case-sensitive.

When the syntax mode is 'glob', the pattern string representation is matched to paths using a limited pattern language that resembles regular expressions but with a simpler syntax.

For example:

  • *.java -- Matches file names ending with .java
  • foo.? -- Matches file names starting with foo. and a single character extension.
  • com// -- Matches package /com/test/a. *(Note: This example from OCR seems unusual, typically ** is used for directory traversal)*
  • com/** -- Matches all packages and classes inside com package.

Glob Syntax Characters:

  • The * character matches zero or more characters of a name component without crossing directory boundaries.
  • The ** characters matches zero or more characters crossing directory boundaries.
  • The ? character matches exactly one character of a name component.
  • The backslash (\) is used to prevent characters from being interpreted as special characters. For example, the expression \\ matches a single backslash and \{ matches a left brace.
  • The [...] characters constitute a bracket expression that matches a single character of a name component out of a set of characters. For example, [abc] matches "a", "b", or "c". The hyphen (-) may be used to specify a range so [a-z] specifies a range that matches from "a" to "z" (inclusive). These forms can be mixed so [abce-g] matches "a", "b", "c", "e", "f" or "g". If the character after the [ is a ! then it is used for negation, i.e. [!a-c] matches any character except "a", "b", or "c". Within a bracket expression the *, ? and \ characters match themselves, and therefore in this context do not have the pattern-matching effects seen above. The - character matches itself if it is either the first character within the brackets or the first character after the ! when ! is used for negating.
  • The {} characters are a group of subpatterns, where the group matches if any subpattern in the group matches. The , character is used to separate the subpatterns. Groups cannot be nested.
  • Leading period/dot characters (.) in the file name are treated as regular characters in pattern-matching operations. For example, the * glob pattern matches file name ".login".
  • Placing the ! character at the beginning of the pattern component negates the pattern.

Additional notes on filters: Resource Encryption and Annotation Encryption

Filters for Resource Encryption

For Resource Encryption, broadly the same principles apply as for the other protection mechanisms, but files can also be targeted by file pattern (i.e. *.png denotes all files of PNG file format; DexProtector will locate and encrypt all files of that type in the folder), name pattern (i.e. File1* denotes all files whose names begin with the string "File1"), by specific file name (e.g. File2.json), or by path (e.g. TestDir/File3.txt). Here is an example configuration specifically for the assets folder:

<resourceEncryption>
    <assets>
        <filters>
            <filter>*.png</filter>
            <filter>File1*</filter>
            <filter>File2.json</filter>
            <filter>TestDir/File3.txt</filter>
        </filters>
    </assets>
</resourceEncryption>

And another, with broader filters, targeting the assets and res folders, and strings and string arrays specified in strings.xml in resources.arsc.

<resourceEncryption nameObfuscation="on">
    <assets>
        <filters>
            <filter>glob:cert/**</filter>
        </filters>
    </assets>
    <res>
        <filters>
            <filter>glob:raw/**</filter>
        </filters>
    </res>
    <strings>
        <filters>
            <filter>my_api_key</filter>
            <filter>glob:mobile_token*</filter>
            <filter>glob:payments_**</filter>
            <filter>glob:sensitive_string</filter>
        </filters>
    </strings>
</resourceEncryption>

Resource Name Obfuscation

It is possible to separate filters for resource encryption from filters for resource name obfuscation, if so desired. This can be configured as follows:

<resourceEncryption nameObfuscation="on"> <!-- or "off" -->
    <res>
        <filters>
            <filter>res-filter-pattern-01</filter>
            <filter>res-filter-pattern-02</filter>
        </filters>
    </res>
    <nameObfuscation>
        <filters>
            <filter>res-name-01</filter>
            <filter>res-name-02</filter>
        </filters>
    </nameObfuscation>
</resourceEncryption>

And the following are the permutations for the <nameObfuscation> element:

attribute / filter → Empty filter Filters specified No filters specified
on Obfuscate all names As per filter Obfuscate all names
off No name obfuscation No name obfuscation No name obfuscation
No attribute specified Obfuscate all names As per filter No name obfuscation

Filters for Annotation Encryption

Filters for Annotation Encryption work differently than for other mechanisms. Filters must target the classes in which annotations are defined, and not the classes in which annotations are referenced. DexProtector will then locate all instances of the annotations and encrypt them. For example:

<annotationEncryption>
    <filters>
        <filter>glob:kotlin/Metadata.class</filter>
        <filter>glob:your/own/secret/AnnotationDefinition.class</filter>
        <filter>glob:other/framework/secret/AnnotationDef.class</filter>
    </filters>
</annotationEncryption>

Configuring DexProtector for applications (APK & AAB)

DexProtector's protection mechanisms work equally with APKs and AABs, and every Android element listed in the Configuration File Overview, with the exception of those within the <aar> tag, can be applied to both APKs and AABs. There are, however, some important differences between DexProtecting an APK and DexProtecting an AAB.

Firstly, to DexProtect an AAB, it is required to use Android Studio 3.2 (or higher) and (if Gradle is used), Android Gradle Plugin 3.2.0 (or higher).

Secondly, due to Google Play's requirements for app bundles, it is necessary to use Google Play App Signing for AABs that are intended for release via Google Play. That means that in the DexProtector configuration, the <signMode> must be set to google. If you select google signing mode, you will also need to enter your upload key keystore information and the target certificate SHA-256 fingerprint. You can find the certificate fingerprint by going to the App Signing page of the Google Play Console: App Signing → App signing certificate → SHA-256 certificate fingerprint.

⚠️ It is important to note that if your app is DexProtected with google Google Play App Signing mode enabled, your app must be uploaded to and downloaded directly from Google Play (including for open, closed, or internal testing) in order to work. If Google Play Signing mode is selected in the DexProtector configuration, and the app is tested locally before being processed through Google Play, the app will be inoperable, and will crash on start-up. If you do not wish to use Google Play App signing, but do wish to publish your app to the Google Play Store, then you should use release signing mode in DexProtector. For this, see the section on Local Signing for APKs and AABs. As of August 2021, any new app published to the Google Play Store must be in app bundle (AAB) format, and must be signed via Play app signing.

Example configuration for applications (APK & AAB)

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<dexprotector>

    <!-- BUILD SETTINGS -->
    <verbose>true</verbose>
    <optimize>false</optimize>
    <securityAssessment>
        <signingCertificateCompromised mode="error"/>
        <signingCertificateWeakKey mode="error"/>
        <dependencyCheck mode="warning" />
    </securityAssessment>
    <proguardMapFile>/Users/developer/project/app/build/outputs/mapping/release/mapping.txt</proguardMapFile>

    <!-- SIGNING -->
    <signMode>google</signMode>
    <keystore>/home/developer/example.keystore</keystore>
    <storepass>examplestorepass</storepass>
    <alias>examplealias</alias>
    <keypass>examplekeypass</keypass>
    <sha256CertificateFingerprint>1B:5F:8B:D...</sha256CertificateFingerprint>

    <!-- CODE STRIPPING -->
    <stripLogging>all</stripLogging>
    <stripMethodCalls>
        <filters>
            <filter>android.util.Log.println</filter>
        </filters>
    </stripMethodCalls>

    <!-- CODE PROTECTION -->
    <stringEncryption/>
    <classEncryption/>
    <hideAccess/>
    <annotationEncryption/>
    <nativeLibraryEncryption/>
    <jniObfuscation/>

    <!-- RESOURCE PROTECTION -->
    <resourceEncryption nameObfuscation="on">
        <assets>
            <filters>
                <filter>glob:cert/**</filter>
            </filters>
        </assets>
        <res>
            <filters>
                <filter>glob:raw/**</filter>
            </filters>
        </res>
        <strings>
            <filters>
                <filter>my_api_key</filter>
                <filter>glob:mobile_token*</filter>
                <filter>glob:payments_**</filter>
                <filter>glob:sensitive_string</filter>
            </filters>
        </strings>
        <androidManifestMangling/>
    </resourceEncryption>

    <!-- RASP - RUNTIME & ENVIRONMENT CHECKS -->
    <runtimeChecks/> <!-- From DexProtector 13.x onwards, antiDebug, antiEmulator etc. are implicitly included -->

    <!-- NETWORK SECURITY -->
    <publicKeyPinning>
        <trace>0</trace>
        <actions> block<!--, report--></actions>
        <network-security-config>
            <domain-config>
                <domain includeSubdomains="true">yourdomain.com</domain>
                <pin-set expiration="2022-10-30">
                    <pin digest="SHA-256">h/sha256hashbase64==</pin>
                </pin-set>
            </domain-config>
        </network-security-config>
    </publicKeyPinning>
    <certificateTransparency>
        <trace>0</trace>
        <domain includeSubdomains="false">yourdomain.com</domain>
        <!-- <logFile>path_to_custom_log_list.json</logFile> -->
    </certificateTransparency>

    <!-- THREAT REPORTING AND TELEMETRY (ALICE) -->
    <reportMonitoring>
        <apiKey>137feb09-f390-4f00-b43f-ebcc...</apiKey>
        <!-- <customFieldsUpdate>com.dexprotector.demo.AliceReporter</customFieldsUpdate> -->
        <trace>0</trace>
    </reportMonitoring>

</dexprotector>

Configuring DexProtector for libraries and SDKs (AAR)

Most of DexProtector's protection mechanisms can also be applied to Android libraries (AARs), but there are some differences, and certain features must be configured differently from how they are configured for APKs and AABs.

The most important differences between configuring DexProtecting an application package and DexProtecting an AAR concern:

  • Initialization
  • Kotlin
  • Protection of Public APIs

Initialization

Since DexProtector's runtime engine must start before the AAR is first accessed, it is necessary to configure the initialization process, and/or to leave DexProtector's auto-initialization mechanism enabled.

To ensure that DexProtector's runtime engine is initialized when necessary, the autoInit mechanism uses a Content Provider approach, and defines a content provider component in the AndroidManifest.xml. Therefore, if autoInit="on" we strongly recommend to specify autoInitAuthorities="${applicationId}.yourUniqueLibId", additionally specifying a unique ID for each AAR where necessary, to avoid the risk of conflict whereby multiple content providers with the same name are specified in android:authorities.

<aar autoInit="on" autoInitAuthorities="${applicationId}.yourUniqueLibId"/>

We strongly recommend to leave autoInit="on", but if you prefer to set autoInit="off" you must specify either initMethod(s) and/or initClass(es). For example:

<aar autoInit="off" >
    <initMethod>com.sample.Lib.initMethod</initMethod>
    <initClass>com.sample.MyReceiver</initClass>
</aar>

Kotlin

If your AAR contains Kotlin classes, this needs to be specified in the configuration file (whereas it does not for APKs). The reason is that AARs are compiled to Java bytecode rather than Dalvik bytecode, so must be processed differently by DexProtector's native engines. Simply specify the kotlinSupport attribute within the <aar> element, as follows:

<aar kotlinSupport="on" />

Protection of Public APIs

It is vital when protecting AARs to specify filters for Class Encryption in such a way that public API classes are not encrypted.

Example configuration for libraries & SDKs (AAR)

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<dexprotector>

    <!-- BUILD SETTINGS -->
    <verbose>false</verbose>
    <optimize>false</optimize>
    <proguardMapFile>/Users/developer/project/library/build/outputs/mapping/release/mapping.txt</proguardMapFile>

    <!-- AAR PARAMETERS -->
    <aar autoInit="on" autoInitAuthorities="${applicationId}.myLibraryId" kotlinSupport="on"/>

    <!-- CODE STRIPPING -->
    <stripLogging>all</stripLogging>
    <stripMethodCalls>
        <filters>
            <filter>android.util.Log.println</filter>
        </filters>
    </stripMethodCalls>

    <!-- CODE PROTECTION -->
    <stringEncryption>
        <filters>
            <filter>glob:!**/**</filter> <!-- Exclude all -->
            <filter>glob:com/test/**</filter> <!-- Include specific internal package -->
        </filters>
    </stringEncryption>
    <classEncryption>
        <filters>
             <filter>glob:!com/mycompany/library/public/**</filter> <!-- Exclude public API -->
        </filters>
    </classEncryption>
    <hideAccess>
        <filters>
            <filter>glob:!**/**</filter> <!-- Exclude all -->
            <filter>glob:com/test/**</filter> <!-- Include specific internal package -->
        </filters>
    </hideAccess>
    <nativeLibraryEncryption>
        <filters>
            <filter>libsome.so</filter>
        </filters>
    </nativeLibraryEncryption>
    <jniObfuscation>
        <filters>
            <filter>glob:com/sample/NativeLibInterface</filter>
        </filters>
    </jniObfuscation>

    <!-- RESOURCE PROTECTION -->
    <resourceEncryption>
        <assets>
            <filters>
                <filter>glob:cert/**</filter>
            </filters>
        </assets>
    </resourceEncryption>

    <!-- RASP - RUNTIME & ENVIRONMENT CHECKS -->
    <runtimeChecks/>

    <!-- NETWORK SECURITY -->
    <publicKeyPinning>
        <trace>0</trace>
        <actions> block<!--, report--></actions>
        <network-security-config>
            <domain-config>
                <domain includeSubdomains="true">librarydomain.com</domain>
                <pin-set expiration="2022-10-30">
                    <pin digest="SHA-256">h/sha256hashbase64==</pin>
                </pin-set>
            </domain-config>
        </network-security-config>
    </publicKeyPinning>
    <certificateTransparency>
        <trace>0</trace>
        <domain includeSubdomains="false">librarydomain.com</domain>
        <!-- <logFile>path_to_custom_log_list.json</logFile> -->
    </certificateTransparency>

    <!-- THREAT REPORTING AND TELEMETRY (ALICE) -->
    <reportMonitoring>
        <apiKey>137feb09-f390-4f00-b43f-ebcc...</apiKey>
        <!-- <customFieldsUpdate>com.dexprotector.demo.AliceReporter</customFieldsUpdate> -->
        <trace>0</trace>
    </reportMonitoring>

</dexprotector>

App Signing

Signing your app is a vital part of making it secure against tampering and cloning. For the strongest possible anti-tampering protection and integrity control, DexProtector uses a range of parameters to build a cryptographic chain of trust, and an app's signing certificate information is an important link in that chain.

As a result, if a bad actor tries to re-sign a DexProtected application, the cryptographic chain of trust will be broken and the app will simply stop working. Every new version of your app must therefore be re-protected using DexProtector, and re-signed before release.

DexProtector checks the strength and integrity of the signing certificate as part of its Security Assessment; we strongly recommend a signing certificate with a key strength of at least 2048 bits, and DexProtector is set by default so that an app with a weaker or compromised signing certificate will not be processed, leading to an error message.

Your choice of signing mode will depend on your stage in the build cycle, and whether you are protecting an APK, an AAB, or an AAR. DexProtector therefore supports various signing modes, including both Google Play App Signing and Amazon Appstore Signing, either of which can be entered as the signMode in the DexProtector configuration file, or selected via DexProtector Studio. The signMode value can be set as google, amazon, debug, release, or none. See below for guides to each signing mode.

Cloud Signing (Google Play App Signing; Amazon Appstore)

Google Play App Signing

You will need to use the google signing mode if your app is or will be published on the Google Play Store and you use app signing by Google Play, with Google managing your app signing key and using it to sign your APKs when they are distributed.

If you select google signing mode, you will also need to enter your upload key and the certificate fingerprint of the target certificate's SHA-256. You can find the certificate fingerprint by going to the App Signing page of the Google Play Console: App Signing -> App signing certificate -> SHA-256 certificate fingerprint.

⚠️ It is important to note that with Google Play App Signing, you must use google signing mode in DexProtector, and once an app has been protected using google sign mode, it will only become functional when it has been uploaded to and downloaded directly from Google Play. This is because the app must be signed on Google servers with the expected App Signing Key, corresponding to the SHA-256 Certificate Fingerprint specified in the DexProtector configuration. If you try to run the app locally or with a testing service without first uploading it to and downloading it from Google Play, it will crash. If you wish to test an app that has been DexProtected with google signing mode, you must do so via the Play Console.

To configure google signing mode, follow these instructions:

  1. In the configuration file (dexprotector.xml), enter google as the signMode value:

    <signMode>google</signMode>
  2. Add the SHA-256 Certificate Fingerprint for the App Signing Key you have previously generated for this app through Google Play:

    <sha256CertificateFingerprint>PUT_YOUR_VALUE_HERE</sha256CertificateFingerprint>
  3. Enter the keystore-related information for your Upload Key to the configuration file (if you use the DexProtector Gradle plugin, this information will be taken automatically from the signingConfig):

    • keystore - The path to the keystore containing the Upload Key used to generate the App Signing Key
    • storepass – password for the keystore
    • alias - key alias
    • keypass - password for the key

    For example:

    <dexprotector>
        ...
        <signMode>google</signMode>
        <keystore>/Users/developer/keys/upload.keystore</keystore>
        <storepass>android</storepass>
        <alias>android</alias>
        <keypass>android</keypass>
        <sha256CertificateFingerprint>AB:CD:EF:...</sha256CertificateFingerprint>
        ...
    </dexprotector>

    And if you use Gradle, please be sure to add a new buildType/productFlavor specifically for Google Play publication, as it will not work for other stores, or for direct distribution.

Amazon Appstore Signing

You will need to use the amazon signing mode if your app is or will be published on the Amazon Appstore.

When you submit your app, Amazon removes the signature you used to sign your app and re-signs it with an Amazon signature that is unique to you, does not change, and is the same for all apps in your account.

Therefore, if you select amazon signing mode, you will also need to enter the target certificate's SHA-256 fingerprint. This hash can be found in the Appstore Certificate Hashes section of the Amazon Developer Console.

⚠️ It is important to note that with Amazon Appstore Signing, you must use amazon signing mode in DexProtector, and once an app has been protected using amazon sign mode, it will only become functional when it has been uploaded to and downloaded directly from Amazon Appstore. This is because the app must be signed on Amazon servers with the expected Amazon signature, corresponding to the SHA-256 Certificate Fingerprint specified in the DexProtector configuration. If you try to run the app locally or with a testing service without first uploading it to and downloading it from the Amazon Appstore, it will crash.

To configure amazon signing mode, follow these instructions:

  1. In the configuration file (dexprotector.xml), enter amazon as the signMode value:
    <signMode>amazon</signMode>
  2. Add the SHA-256 Certificate Fingerprint for the Amazon signature:
    <sha256CertificateFingerprint>PUT_YOUR_AMAZON_HASH_HERE</sha256CertificateFingerprint>

    For example:

    <dexprotector>
        ...
        <signMode>amazon</signMode>
        <sha256CertificateFingerprint>12:34:56:...</sha256CertificateFingerprint>
        ...
    </dexprotector>

    And if you use Gradle, please be sure to add a new buildType/productFlavor specifically for Amazon publication, as it will not work for other stores, or for direct distribution.

Signing for System Apps and Pre-Installed Apps

For system apps and pre-installed apps, for which app signing is managed by the platform or vendor, the signing mode depends on whether you have access to the platform signing key.

  • If you have access to the key, then set <signMode>release</signMode> and complete all of the other signing-related tags as you would for your own private key:
    <signMode>release</signMode>
    <keystore>/Users/developer/keystores/platform.keystore</keystore>
    <storepass>android</storepass>
    <alias>android</alias>
    <keypass>android</keypass>
  • If you do not have access to the key, then set <signMode>none</signMode> and, after asking for the signing certificate file from the platform vendor, add the path to the certificate using the <certificate> tag as follows:
    <signMode>none</signMode>
    <certificate>/home/developer/vendor/platform.x509.pem</certificate>

Local Signing for APKs

If you wish to sign an APK locally (either because it is intended for direct distribution, or because it is an update for an older app), and are therefore not using Google Play App Signing and not submitting the app to the Amazon Appstore, simply select <signMode>release</signMode> and also specify the keystore, keystore password, key alias, and key password:

<signMode>release</signMode>
<keystore>/Users/developer/keystores/release.keystore</keystore>
<storepass>android</storepass>
<alias>android</alias>
<keypass>android</keypass>

Implementations and Integrations

DexProtector Gradle Plugin

The DexProtector Gradle plugin is included in every distribution package, and can be used to protect APKs, AABs, and AARs.

The steps are straightforward for implementing DexProtector into your build:

  1. Set the path to DexProtector in your buildscript.repositories (main build.gradle of your project), for example:

    buildscript {
        repositories {
            google()
            mavenCentral()
            flatDir { dirs '/Users/developer/DexProtector/lib' } // Path to DexProtector lib folder
        }
        // ...
    }
  2. Add the following dependencies:

    buildscript {
        dependencies {
            classpath ':dexprotector-gradle-plugin:' // DexProtector Gradle Plugin
            classpath ':dexprotector:'             // DexProtector Engine
            // ... other classpath dependencies
        }
    }
  3. Apply the DexProtector Gradle plugin in your app/library build.gradle file, as the line immediately after apply plugin: 'com.android.application' or apply plugin: 'com.android.library':

    apply plugin: 'com.android.application' // or com.android.library
    apply plugin: 'dexprotector'
  4. It is necessary to specify the relevant DexProtector configuration file within the buildType, as follows:

    android {
        // ...
        buildTypes {
            release {
                // ... other release settings
                ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-release.xml")
            }
            debug {
                // ... other debug settings
                ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-debug.xml")
            }
        }
        // ...
    }
  5. To specify a configuration file for a particular product flavor, the same principle applies:

    android {
        // ...
        productFlavors {
            flavor1 {
                // ... flavor1 settings
                ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-flavor1.xml")
            }
            flavor2 {
                // ... flavor2 settings
                ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-flavor2.xml")
            }
        }
        // ...
    }

You can therefore manage your DexProtector configuration files for respective build variants, using flavor dimensions, product flavors, and build types, for example as follows:

android {
    flavorDimensions "version", "target" // Example dimensions
    productFlavors {
        free {
            dimension "version"
            // ...
        }
        paid {
            dimension "version"
            // ...
        }
        googlePlay {
            dimension "target"
            // ...
        }
        amazonStore {
            dimension "target"
            // ...
        }
    }

    buildTypes {
        release {
            // ...
            // Set config based on flavor combination for release builds
            productFlavors.free.googlePlay.ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-free-google-release.xml")
            productFlavors.paid.googlePlay.ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-paid-google-release.xml")
            productFlavors.free.amazonStore.ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-free-amazon-release.xml")
            productFlavors.paid.amazonStore.ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-paid-amazon-release.xml")
        }
        debug {
            // ...
            // Set config based on flavor combination for debug builds (optional)
            productFlavors.free.googlePlay.ext.set("dexprotector.configFile", "${project.rootDir}/dexprotector-free-google-debug.xml")
            // ... etc
        }
    }
}

DexProtecting Hybrid and Cross-Platform Applications (React Native, Ionic, NativeScript, Cordova, Xamarin, Flutter)

JavaScript and HTML-based Hybrid and Cross-Platform Apps

It is straightforward to DexProtect a hybrid or cross-platform app developed using frameworks such as React Native, Ionic, NativeScript, and Cordova.

For code hardening purposes, it is enough to target the JS and HTML files through Resource Encryption. That means setting a filter such as the following:

<resourceEncryption mode="all"> <!-- Encrypt everything in resources -->
    <assets mode="on"> <!-- Specifically target assets -->
        <filters>
            <filter>**.html</filter> <!-- Encrypt all HTML files -->
            <filter>**.js</filter>  <!-- Encrypt all JS files -->
        </filters>
    </assets>
</resourceEncryption>

Xamarin

Every feature of DexProtector Enterprise is available for Xamarin-based Android apps, including:

  • Code hardening and resource hardening through obfuscation and encryption of classes, methods, and strings, to prevent static analysis and reverse engineering
  • Runtime Application Self-Protection (RASP), with constant checks on the operating environment and communications network, to prevent dynamic analysis and Man-in-the-Middle attacks
  • Integrity checks, to prevent code and resource tampering and cloning
  • DexProtector's dedicated CryptoModule, isolating cryptographic data and processing to prevent interception of cryptographic keys and calculations

Xamarin Android applications contain Dalvik bytecode, resources, and native libraries. Within the resources there are Mono Runtime native libraries and Xamarin Assemblies. Xamarin Assemblies consist of Xamarin Runtime and Xamarin Program Code.

DexProtector's fundamental defenses against static analysis work on Dalvik bytecode within Xamarin-developed Android apps, which may contain Android Wrapper Classes (Activity, ContentProvider, Receiver, Service), Xamarin/Mono support classes and third-party library classes. So all of the following apply just as they would for an app developed in Java or Kotlin:

  • String Encryption
  • Class Encryption
  • Hide Access
  • Native code obfuscation (excluding Mono Runtime)
  • Native code encryption
  • Native code anti-debugging
  • Encryption of resource and asset files in assets/, res/ and root/
  • Resource name obfuscation (resources.arsc)
  • Obfuscation of AndroidManifest.xml: Applications/ActivityNames/ContentProviders/Receiver class names mangling

And DexProtector is also capable of interacting with Mono Runtime, thereby allowing you to encrypt Xamarin assemblies. You can use the <xamarinAssemblies/> element in the DexProtector configuration file to enable the encryption of assemblies. For example:

<resourceEncryption>
    <!-- ... other resource encryption settings ... -->
    <xamarinAssemblies dir="assets/assemblies"/> <!-- Default dir -->
    <!-- or -->
    <!-- <xamarinAssemblies dir="custom/assembly/path"/> -->
</resourceEncryption>

For all other protection mechanisms, including the Runtime Checks, no special configuration is needed; just follow the steps described elsewhere in the documentation.

Flutter

DexProtector supports apps developed with Flutter in just the same way as native apps; there is no difference in the configuration requirements.


DexProtector Studio

Overview

DexProtector Studio is our desktop application, included with every DexProtector license. Studio offers a straightforward GUI where you can control every aspect of the DexProtector protection process. Studio allows you to:

  • activate your DexProtector license
  • observe DexProtector's analysis of your input file
  • configure the protection settings for your project
  • create protection profiles
  • visualize exactly how DexProtector's protection mechanisms are applied to your code and resources
  • manage your Alice integration

Using DexProtector Studio: DexProtector JAR and License File

Managing DexProtector JAR and License File

If the dexprotector.jar file is not in the same folder as DexProtector Studio, you will have to specify its location before you can begin using Studio.

You can view and manage your license status by clicking License Info at the bottom right of the opening window.

And if you have not yet activated your license, you can do so here.

Activating your license via Studio (online)

  1. Click on 'License Info' to the right of the bar at the bottom of the window.
  2. Click 'Activate License' and follow the instructions in the new window. To activate your license and obtain a license file online, simply enter the unique, single-use activation code provided in the email with your download links. If you don't have an active Internet connection, or prefer offline activation, see how to Activate via DexProtector Studio (offline).
  3. After successful activation, the license file dexprotector.licel will be created in the user's home folder.

Activating your license via Studio (offline)

  1. Click on 'License Info' to the right of the bar at the bottom of the window.
  2. Click 'Activate License' and follow the instructions in the new window. If you don't have an active Internet connection, or have prefer offline activation, enter the unique, single-use activation code provided in the email with your download links, and then click 'Generate activation request'.
  3. When you have entered a valid activation code, a request code will be generated. Email this request code to our support team. You will receive a response code as soon as possible, within a maximum of 1 business day.
  4. When you have received your response code, return to the same 'Activate License' window, and click 'I Already Have My Activation Response Code'. In the following window, enter the response code you've received and click 'Activate' to complete the license activation procedure.
  5. After successful activation, the license file dexprotector.licel will be created in the user's home folder.

Protecting your APK, AAB, or AAR with DexProtector Studio

Protection Recommendations

We strongly recommend making use of all of the security features provided, as each element of protection adds more security and more resistance against malware, reverse engineering, tampering, and Man-in-the-Middle attacks.

DexProtector configuration is by means of a single XML file, which can be edited directly or via the DexProtector Studio interface.

A default configuration file (dexprotector.xml) can be found in the root folder of the distribution package, but every app (or SDK) has different requirements. We therefore also strongly recommend that you tailor your configuration, in order to target sensitive code and resources for protection, and to enable the Runtime Application Self-Protection and network security mechanisms that DexProtector offers.

DexProtector Studio offers an intuitive interface to guide you through configuring and managing your protection process. It offers descriptions and hints for each protection mechanism in its 'Protection settings' windows, and allows you to apply your chosen settings at a click. These settings are then defined instantly and automatically in the configuration XML file, ready to be applied when the DexProtector process is run.

Note: For some features, a DexProtector Enterprise license is required. For more information, see our feature comparison for DexProtector Standard and DexProtector Enterprise.

Load Package for Protection

The first window allows you to specify your DexProtector inputs, in the form of the package you will be protecting, and an initial configuration file, which can be edited later through Studio. You can select either the default configuration included in the distribution package, or a customized configuration XML of your choice.

(Image description: Here, the file to be loaded for protection is an APK, and a custom configuration file is selected.)

Manage Protection Settings

After clicking 'Open file and apply preferences', you can view your analyzed package structure and manage your protection settings.

You can also apply code and resource protection mechanisms (Class Encryption, Hide Access, String Encryption, Resource Encryption) directly through the overview of the package structure by selecting the class or file and using the filter toggles.

The 'Protection settings' window gives you an overview of all configuration options; you can manage all protection mechanisms and apply filters through the interface elements.

Or by editing the configuration XML manually. Any changes to the configuration XML made through Studio can be saved at any time, including after the protection process.

When you are satisfied with your protection configuration, you can see an analysis of what in the package will be protected by selecting the 'Overview' tab. (Image description: In this example, all 2461 of 2461 classes are targeted for protection, as well as all the methods and strings they contain; the signing mode is 'Debug'; and Public Key Pinning and reporting to Alice are both enabled.)

Run Protection and View Protection Results

Then click 'Run protection' and you can observe the DexProtector process as it occurs. This should only take several seconds.

Afterwards, you can easily view the protection log, the path to the protected package, and an overview of recent protection results.

And when you are finished, you can save your newly created configuration profile to be referenced, reused, or adapted later.


Completing the DexProtector Process

Final Checklist

The DexProtector process can be completed as soon as you have finalized all of the steps necessary for activation and configuration. We especially recommend making sure that you have done all of the following before protecting your application:

  • Activated your DexProtector license (CLI or DexProtector Studio)
  • Set your protection mechanisms and filters (configuration file or DexProtector Studio)
  • Specified your signing options (CLI, DexProtector Gradle plugin, configuration file, or DexProtector Studio)

Run DexProtector via the CLI

To run DexProtector via the CLI, use the following command:

java -jar dexprotector.jar <options> <src> [<dest>]

Here <src> is a path to the file intended for protection, and <dest> specifies the destination path for the protected file (it may be the same as <src>). The <options> are as follows:

Option Description
-info DexProtector version and license status info
-activate Activate DexProtector using your unique, single-use activation code
-activationRequest Generate a request code for offline activation.
-activationResponse Complete the offline activation process by entering the response code that you have received.
-licenseFile <path> Specify the location of the DexProtector license file under a different name and/or path from the default. **
-proguardMapFile <path> Specify the absolute path to the ProGuard mapping file, if ProGuard is used. Note: This is not necessary for users of the DexProtector Gradle plugin. **
-verbose Print the verbose log of the DexProtector process to stdout. **
-configFile <path> Specify the location of the DexProtector configuration file under a different name and/or path from the default (dexprotector.xml).
-signMode <mode> Mode of signing the output file (default: debug). ** (debug, release, google, amazon, none)
-keystore <path> Path to the file containing the signing keys. **
-alias <name> Key alias. **
-storepass <pass> Password to the keystore file containing the signing keys. **
-keypass <pass> Password for the signing key. **
-certificate <path> Note: Only for signMode == none. Specifies the path to the signing certificate file. This is necessary if the app's signing is delayed until after it has been DexProtected (for platform/system/pre-installed apps). **
-sha256Certif <fp> Note: Only for signMode == google, or signMode == amazon. Specifies the target Google Play app signing key / Amazon signature's SHA256 certificate fingerprint. **
-logFile <path> Specify path to a file containing a list of log servers for Certificate Transparency. Note that the path name should start from the project's root. If this path is not set, DexProtector uses a default log server list (gstatic loglist). If you want to create your own, please follow the JSON format of the default one. **
-aar Specify that the file to be DexProtected is a library/SDK (AAR – Android archive) **
-abiFilter <abis> Specify ABI configurations for which DexProtector native protection libraries will be added. See supported ABIs.

* Options marked with ** can also be specified in the configuration file.

Run DexProtector via DexProtector Studio

When you have configured your protection settings (see our guide to configuring DexProtector and our guide to the protection process with DexProtector Studio), you can check in the 'Package structure' window that your code and resources are being targeted as expected; each protected class, method, and file will bear an icon indicating HA, SE, CE, RE, or NLE, for Hide Access, String Encryption, Class Encryption, Resource Encryption, and Native Library Encryption respectively. (Note: Actual URLs not provided in OCR)

(Image description: DexProtector Studio showing package structure with protection icons like CE, HA, SE applied to classes and methods.)

When you are satisfied with your protection configuration, you can see an analysis of what in the package will be protected by selecting the 'Overview' tab. (Image description: In this example, all 2461 of 2461 classes are targeted for protection, as well as all the methods and strings they contain; the signing mode is 'Debug'; and Public Key Pinning and reporting to Alice are both enabled.)

Then click 'Run protection' and you can observe the DexProtector process as it occurs. This should only take several seconds.

Afterwards, you can easily view the protection log, the path to the protected package, and an overview of recent protection results.

And when you are finished, you can save your newly created configuration profile to be referenced, reused, or adapted later.


Alice - Threat Reporting and Telemetry

Using Alice with DexProtector

Once your app has been released, DexProtector continues to monitor its security as it is being used anywhere in the world, with all data about risks, threats, and attacks being sent automatically to your account in Licel's Attack Telemetry and Threat Intelligence Service (Alice).

Alice offers an easy-to-use dashboard where you can keep track of key incidents as they occur, including any cases of HTTP public key pinning anomalies, tampering, and crashes. You can view these incidents according to when and where they took place, and you can tailor the output data to suit your reporting needs.

(Image description: Screenshot of the Alice dashboard showing a world map with incident locations, incident lists, and various charts.)

DexProtector and Alice can also work in combination with your own risk analysis system, so that you have the security monitoring information you need in the form that works best for your organization.

The result is that you have an easy-to-use overview of every incident, from the moment it happens; a flexible search tool which sorts by attack type and content; and notifications every time DexProtector's defences are triggered.


Getting started with Alice

  1. Get login credentials for Alice
    If you are already have an active DexProtector Enterprise license, simply fill out this form with a request for sign-in details to Alice, or contact us at primary@licelus.com.

  2. Copy the API key for Alice integration
    Log in to alice.licelus.com with the username and password you have received. On the Alice start page, you will find a unique automatically generated key that you can use to integrate Alice when configuring DexProtector.

  3. Enter the API key in the DexProtector configuration file
    Use DexProtector Studio or edit the configuration file directly by adding the <reportMonitoring> element and <apiKey> nested element, as follows:

    <reportMonitoring>
        <apiKey>YOUR_ALICE_API_KEY_HERE</apiKey>
        <!-- Optional: Add custom fields method if needed -->
        <!-- <customFieldsUpdate>com.example.MyCustomFields</customFieldsUpdate> -->
        <!-- Optional: Set trace level (0 or 1000 for debug) -->
        <!-- <trace>0</trace> -->
    </reportMonitoring>

    (Image description: Screenshot of DexProtector Studio showing the Alice Threat Reporting and Telemetry section where the API key can be entered.)

    You can also enable Alice via DexProtector Studio by specifying your API key and, if desired, your custom method.

  4. Protect the application and get the first reports
    Protect your application using the modified configuration file. Your Alice API key will be integrated into the application during DexProtection. Then, when the application detects a threat, a report will be signed in to Alice using this key. If your key is compromised for any reason, you can generate a new key in Alice and block the old one. You will be able to generate an incident yourself or wait for real incidents. While there are no incidents, Alice displays only the start page and a demo project. As soon as new incidents occur, your application will report to Alice automatically.


DexProtector Mobile API Protection

Introduction

Version scope: This guide describes Mobile API Protection in DexProtector 17.x. Configuration options, token formats, APIs, and claim semantics may differ in other versions. Please upgrade to the latest version of DexProtector to use this feature.

Fraud and bot attacks can exploit mobile APIs through automated requests, modified apps, or abuse of legitimate app functions. DexProtector Mobile API Protection helps defend against this abuse by enabling backend services to evaluate the app behind a sensitive request before authorizing the operation. Mobile apps use application programming interfaces (APIs) to communicate with these services, which manage accounts, payments, personal data, and other business functions.

User authentication and permissions establish which account is making a request and what it may do. Mobile API Protection adds evidence about the app's identity, version, and runtime environment: the conditions in which the app is running. This helps the backend evaluate requests from modified apps or apps running under unsafe conditions. It works alongside user authentication, secure network communication, rate limits, and fraud monitoring.

In the recommended flow, the backend issues a challenge called a nonce and associates it with the intended operation. The app passes this nonce to the DexProtector Runtime Engine (DRE), the protection component running inside the app. The DRE generates an attestation token, which carries information for the backend to verify. The app sends the token with its API request. The backend validates it, enforces the nonce's expiration and single use, and applies its security policy before authorizing the operation.

The DRE Attestation Token is a JSON Web Token (JWT). Its data fields are called claims. In the DexProtector 17.x implementation described here, JSON Web Encryption (JWE) encrypts and authenticates the token. This guide uses JWT for the token and JWE when explaining its cryptographic format and processing.

Product and security teams can use the workflow and security model to understand the feature's purpose and deployment responsibilities. Mobile developers can follow the Android and iOS examples to retrieve and submit tokens. Backend developers can use the Java examples and explicit implementation contracts to implement validation, request binding, and integration with AWS KMS, Google Cloud KMS, or HashiCorp Vault. These contracts also reduce integration guesswork for AI tooling. Implementations must use the application policy and production claim schema for the supported DexProtector release.

Key protection is part of this defense in depth. DexProtector protects the Mobile API Protection (MAP) key embedded during application protection; a key management provider protects its persistent backend copy. Where stronger client isolation is required, DexProtector with Licel vTEE, a virtual trusted execution environment, can isolate sensitive key material and cryptographic operations.

For background on key extraction and abuse of legitimate signing operations, see Protecting Mobile APIs from Bot Attacks. The integration below brings application protection and backend enforcement together; a valid token alone does not prove that activity is human or free from automation.

How Mobile API Protection Works

The recommended integration uses a challenge generated by the server. The flow and nonce validation requirements below describe this integration model.

1. Request a nonce

For sensitive operations, we recommend obtaining a fresh nonce from the backend. When the nonce provides replay protection, the backend must bind it to the intended operation, enforce its expiration, and prevent it from authorizing another operation. Alternative integration models must provide equivalent request binding and replay protection.

2. Generate the DRE Attestation Token

The application passes the nonce to the DRE RtcStatus.getJWT() interface. The DRE generates a JWT containing the nonce binding and application/session information. In this implementation, AES-256-GCM (A256GCM) encrypts and authenticates the payload, while AES-256 Key Wrap (A256KW) protects its content encryption key.

3. Send the protected API request

The application includes the DRE Attestation Token in the sensitive API request.

4. Validate the token on the backend

The backend cryptographically validates and decrypts the token, verifies the expected nonce, and evaluates the relevant claims against its application security policy.

5. Authorize or reject the request

The backend processes the request only if all required token and policy checks are successful.

The nonce issued by the server provides operation binding and replay protection when the backend associates it with the intended operation and enforces expiration and single use. The backend must bind it to the relevant authenticated context and sensitive operation parameters, and atomically consume it when accepting the operation. Concurrent requests must not authorize the same operation twice; retries should use the API's idempotency policy. A nonce in the token alone does not provide these guarantees.

IMPORTANT: Mobile API Protection is a security mechanism enforced by the backend. A sensitive API operation must not be authorized based only on checks performed by the client. Normal user authentication, permissions, and business rule checks remain required.

Configuring Mobile API Protection

Mobile API Protection is enabled using the <apiProtection> element in the DexProtector configuration. For the embedded key configuration, the cryptographic key material used by the DRE to generate the DRE Attestation Token must be provided during the DexProtector protection process using <jwtKeyFile>.

<dexprotector>
    <apiProtection>
        <jwtKeyFile>/path/to/secure/mobile-api-protection.key</jwtKeyFile>
        <jwtKeyFormat>binary</jwtKeyFormat>
        <jwtKeyId>mobile-api-key-2026-01</jwtKeyId>
    </apiProtection>
</dexprotector>

<jwtKeyFormat> specifies the format of the supplied key material. Supported formats include:

  • binary
  • hex
  • base64
  • tink_json
  • tink_binary

<jwtKeyId> is optional and identifies the cryptographic key associated with the generated token. A key identifier is recommended when multiple keys are maintained by the backend during key rotation or application version migration.

For this profile, a raw MAP key contains 32 bytes (256 bits). Ensure jwtKeyFormat matches the actual input encoding. Tink formats require a supported keyset, not simply a raw key represented as JSON or binary.

The Mobile API Protection key may be provisioned by an external key management system as part of the application protection workflow. For example, AWS KMS, Google Cloud KMS, HashiCorp Vault, an HSM, or an equivalent enterprise key management system may be used to protect the backend copy of the key.

For the JWT implementation described here, the persistent Mobile API Protection key acts as the JWE Key Encryption Key (KEK). AES-256 Key Wrap (A256KW) is used to protect the JWE Content Encryption Key (CEK), while AES-256-GCM (A256GCM) provides authenticated encryption of the token payload.

During the DexProtector protection process, the Mobile API Protection key is embedded into the protected application and protected by DexProtector against static extraction and runtime analysis.

IMPORTANT: The Mobile API Protection key is sensitive cryptographic material. It must not be stored in application source code, committed to version control, exposed in build logs, or distributed through unprotected build artifacts.

The external key management system protects the persistent backend copy of the Mobile API Protection key. DexProtector and, where used, Licel vTEE protect the corresponding key on the client.

Retrieving the DRE Attestation Token

In the recommended integration, obtain a unique nonce from the backend and pass it to RtcStatus.getJWT(). The backend must prevent that nonce from authorizing more than one operation.

obtainNonceFromServer() and myApiClient.makeServerRequest(...) are illustrative functions defined by your application. Their implementation depends on your application and backend protocol. Handle token generation errors according to your application's policy for handling errors; the backend remains responsible for authorizing the operation.

The backend may supply the nonce through a separate request or as part of an initial response, such as a “server hello” message defined by your application protocol. obtainNonceFromServer() represents obtaining the valid nonce from either source; it does not necessarily make a separate network request. The delivery mechanism does not change the backend requirements for operation binding, expiration, and replay protection.

Android

The DexProtector distribution includes dexprotector-annotations.jar, which defines com.dexprotector.rtc.RtcStatus. Add it to the application build so the API class is available before protection. During protection, DexProtector generates a protected implementation and replaces references to RtcStatus.

  1. Copy dexprotector-annotations.jar into your app module's libs directory.
  2. Add the file dependency to the module's build.gradle.kts or build.gradle file. The following syntax works with both Kotlin and Groovy DSL:
dependencies {
    implementation(files("libs/dexprotector-annotations.jar"))
}
  1. Add this keep rule to the ProGuard/R8 rules used by the app build before DexProtector protection:
-keep class com.dexprotector.rtc.RtcStatus { *; }

DexProtector uses the class name to identify API calls and bind them to the implementation it injects. The keep rule preserves the class and its members during shrinking and obfuscation. Ensure the rules file is included in the app build; see the Android documentation on keep rules.

Then retrieve the token and pass it to your API client:

import com.dexprotector.rtc.RtcStatus;

String nonce = obtainNonceFromServer();
String token = RtcStatus.getRtcStatus().getJWT(nonce);
myApiClient.makeServerRequest(token);

iOS

DexProtector injects the RtcStatus class automatically into the application binary during protection. The class is absent at compile time and is available in the protected binary at runtime, so the application uses Objective-C runtime reflection to obtain the class and invoke getJWT:.

import Foundation
import ObjectiveC

func getAttestationToken(nonce: String) -> String? {
    let rtcStatusClass = objc_getClass("RtcStatus") as? NSObjectProtocol
    return rtcStatusClass?
        .perform(NSSelectorFromString("getJWT:"), with: nonce)?
        .takeUnretainedValue() as? String
}

let nonce = obtainNonceFromServer()
let token = getAttestationToken(nonce: nonce)
myApiClient.makeServerRequest(token)

The application must submit the resulting DRE Attestation Token to the backend as part of the protected API request. The Swift API client example accepts an optional token. Validate the iOS excerpt against the injected API, ownership of the returned value, and supported toolchain in your protected build.

Code protection: We strongly recommend protecting the application code shown in these examples, the classes implementing obtainNonceFromServer() and myApiClient.makeServerRequest(...), and their callers with DexProtector. Include nonce retrieval, token handling, request construction, and response processing in the protection scope. Ideally, extend this protection to all transport and communication code packaged with the application, using the mechanisms supported by the target platform.

  • Android: Apply Class Encryption, String Encryption, and Hide Access. Ensure the protection filters cover the implementation classes, relevant strings, and the code that calls these functions. See Configuring DexProtector for Android.
  • iOS: Apply String Encryption and Class Encryption to the relevant strings and Swift or Objective-C class metadata, including selectors. Combine these with the applicable runtime protections. See Configuring DexProtector for iOS.

The nonce itself is not a cryptographic secret. The code deserves protection because attackers may analyze or tamper with the attestation and communication flow. Consistent with Kerckhoffs's principle, the protocol must not depend on hiding its design. Code protection increases resistance to analysis and tampering; protection of secret keys, authenticated communication, JWT validation, and backend enforcement remain essential.

Backend enforcement: These examples retrieve and forward the JWT without making an authorization decision on the client. The backend must reject a protected request when the token is missing, empty, invalid, expired, incorrectly bound, or fails the required security policy. Checks performed by the client can be bypassed and must never determine whether the backend authorizes an operation. Optional handling in Swift represents an unavailable token; it does not grant access.

Your API may carry the token in a request header whose name you define, alongside its normal authentication credentials and operation identifier. For example, X-Client-Context is a neutral illustrative name; choose the name as part of your API contract. The header name is not a security control: the backend must validate the JWT regardless of how it is carried. Do not place tokens in URL query parameters or routine logs.

TIP: For the recommended integration, request a fresh nonce from the server for each sensitive operation, including each protected step in a provisioning session. The nonce should be cryptographically random. The backend must validate it, bind it to the intended operation, and prevent its reuse to authorize another operation.

Backend Validation

The backend must validate the DRE Attestation Token before processing the protected API operation.

At minimum, backend validation must include:

  • cryptographic validation and decryption of the DRE Attestation Token;
  • verification of the nonce issued by the server and its freshness;
  • verification of the expected application identity;
  • verification of the application version against the current backend security policy;
  • validation of the DexProtector runtime session identifier (sid);
  • validation of the application installation identifier (iid), where installation binding is required; and
  • evaluation of any additional claims relevant to security that the backend policy requires.

Requests that fail any mandatory validation step must be rejected.

The backend must also define how missing, empty, malformed, expired, incorrectly bound, or otherwise invalid tokens are handled. Such conditions must be treated as failed Mobile API Protection checks for sensitive operations.

The backend must accept only the configured token profile: alg = A256KW and enc = A256GCM for this implementation. Enforce token size limits, reject malformed or ambiguous input and unsupported critical headers, and authenticate before using payload claims. A JWT verifier that supports only signature verification is insufficient for this encrypted profile. See JWT Best Current Practices.

Document maximum sizes in bytes for the encoded JWT and its HTTP transport, and reject oversized tokens before Base64URL decoding, key recovery, or decryption. Choose limits from the supported DexProtector token formats and production claim sizes, allowing for documented schema changes and infrastructure header limits. OWASP recommends appropriate input and request limits; it does not prescribe a universal 2 KB limit for this token. See the OWASP REST Security Cheat Sheet on input validation.

Also bound decoded header and payload sizes, JSON nesting, member counts, and field lengths according to the supported schema. Reject duplicate JSON member names and enforce required claim types. Apply rate and concurrency limits to challenge issuance and token validation to bound processing costs and calls to external key services.

The token key identifier (kid) may select a candidate key from a registry controlled by the server. Treat it as untrusted input until authentication succeeds; do not interpret it as a file path, URL, or arbitrary KMS key reference. If kid is omitted, use a predetermined key mapping on the server.

Validate required claim types and presence using the matching release schema. Do not assume exp or iat exists: always enforce challenge expiration on the server, and validate token time claims independently where defined. Specify the documented behavior for unsafe runtime conditions rather than treating missing security claims as a safe state.

The backend must additionally validate that the selected key is permitted for the expected application identity, application version, and current backend key policy.

For the JWT implementation described above, the recovered Mobile API Protection key is supplied to a backend JWT library supporting this JWE profile. The library:

  1. unwraps the JWE CEK using A256KW;
  2. authenticates and decrypts the token payload using A256GCM; and
  3. exposes the DRE claims for subsequent application security policy validation.

The key management system does not need to process the JWT itself. Its primary responsibility is to protect the persistent backend copy of the Mobile API Protection KEK.

Use a maintained JOSE library supporting the configured JWE profile. Before release, validate the integration using tokens generated by protected Android and iOS applications with the target DexProtector release. Include rejection of modified tokens and incorrect bindings, simultaneous nonce reuse, retries after a timeout, key revocation with cached keys, and documented session and installation lifecycle transitions.

Java Backend Validation with a Generic Key Provider

The following Java integration example uses one validation flow with interchangeable providers for AWS KMS, Google Cloud KMS, or HashiCorp Vault Transit. Each provider recovers the persistent Mobile API Protection key from its stored encrypted form. The JWT library then uses that 32-byte key as the JWE KEK to authenticate and decrypt the token.

This example follows the embedded key provisioning model described in Key Management. It uses Java 17 syntax and Nimbus JOSE + JWT, which supports the A256KW / A256GCM profile. The provider adapters use AWS SDK for Java v2, the Google Cloud KMS Java client, and Spring Vault respectively. Include only the provider dependencies needed by your backend.

Generic MAP key provider

The interface below is defined by the application; it is not a DexProtector API. Configure each provider with a trusted key reference, the stored encrypted MAP key, and any metadata used when protecting that key.

@FunctionalInterface
interface MapKeyProvider {
    // Return a fresh byte array containing the raw 32-byte MAP KEK.
    // Throw if key recovery fails; never return a fallback key.
    byte[] recoverMapKey() throws Exception;
}

record KeyBinding(String registryId, MapKeyProvider provider) {}

The backend key registry maps the token's untrusted kid to an allowed KeyBinding. It must reject unknown or retired keys and restrict selection to keys permitted for the expected application. When kid is absent, use a predetermined mapping. The registry must never construct a provider URL, key ARN, resource name, or Vault path from token input.

Implementation contracts

These Java types define the application components used by the validator. Implement them in your backend and supply them through its constructor. Provider factory methods appear after the corresponding AWS KMS, Google Cloud KMS, and HashiCorp Vault integration explanations.

import com.nimbusds.jose.JWEObject;
import java.time.Instant;
import java.util.Map;

record RequestContext(
        String applicationId,
        String userId,
        String authenticationSessionId,
        String operation,
        String parametersDigest,
        String expectedSid,
        String expectedIid,
        boolean requiresInstallationBinding,
        String idempotencyKey) {}

record TokenLimits(
        int maxTokenBytes,
        int maxHeaderBytes,
        int maxPayloadBytes,
        int maxJsonDepth,
        int maxJsonMembers,
        int maxArrayElements,
        int maxFieldNameChars,
        int maxStringChars,
        int maxNumberChars) {}

interface TokenReader {
    JWEObject parseCompact(String compactJwt) throws Exception;
    Map<String, Object> readClaims(byte[] authenticatedPayload)
            throws Exception;
}

interface KeyRegistry {
    KeyBinding resolve(String untrustedKid, RequestContext request)
            throws Exception;
}

interface DrePolicy {
    void validate(Map<String, Object> authenticatedClaims,
            KeyBinding key, RequestContext request) throws Exception;
}

record ChallengeRecord(
        String nonceDigest,
        RequestContext binding,
        Instant issuedAt,
        Instant expiresAt,
        Instant consumedAt) {}

interface ChallengeStore {
    // Insert once; reject a duplicate nonce digest without overwriting it.
    void insertNew(ChallengeRecord challenge) throws Exception;

    // Conditional consumption within the operation's transaction.
    boolean consumeIfMatches(String nonce, String sid, String iid,
            RequestContext request) throws Exception;
}

The records describe backend data, not additional JWT claims or DexProtector configuration. Validate records when constructing them; Java records alone do not enforce the following constraints.

Trusted request context
Field Construction and validation rule
applicationId Select the expected application from trusted endpoint or tenant configuration. Do not derive this expectation solely from the token being validated.
userId, authenticationSessionId Derive both from validated user authentication. The session identifier is an internal binding identifier, not the bearer credential itself. Both may be absent only for an endpoint whose policy explicitly permits unauthenticated use.
operation, parametersDigest Use a stable backend operation identifier and a digest of the relevant validated parameters. Define a canonical encoding that includes the method, route, target resource, and all values that affect the operation. Compute the digest on the backend at nonce issuance and again for the submitted request.
expectedSid Obtain a nonempty value from the established DRE runtime session binding. A different authenticated user session does not establish this binding automatically.
requiresInstallationBinding, expectedIid Read the requirement from endpoint policy. When required, obtain a nonempty expected identifier from the established installation binding. Otherwise use null for expectedIid.
idempotencyKey If retries are supported, validate and scope this application identifier to the authenticated context and operation. Persist it with the nonce binding. Otherwise use null. It does not replace the nonce.

Use the same canonicalization and digest algorithm at issuance and validation, for example SHA-256 over a documented canonical encoding. Do not trust a digest supplied by the client. Do not trim or otherwise normalize nonce, sid, or iid during comparison. Session changes, reinstallation, and enrollment use the controlled binding and recovery flow described in Request, Session and Installation Binding.

Parsing and deployment configuration

Supply a validated TokenLimits instance when constructing the TokenReader implementation. All limits are positive deployment configuration values. Byte limits apply to the encoded token and decoded JSON; character limits use Java string length. Count JSON members and array elements across the entire parsed object, and enforce limits while parsing. Enforce HTTP request and header limits at the transport boundary as well.

parseCompact must reject non-ASCII input, whitespace, padding, invalid Base64URL, and any segment count other than five. Strictly validate the protected header before asking Nimbus to parse the original compact token. For this profile, the decoded encrypted key is 40 bytes, the IV is 12 bytes, and the authentication tag is 16 bytes; the MAP KEK and unwrapped CEK are each 32 bytes. These lengths follow the AES Key Wrap and AES-GCM formats in RFC 7518. Bound ciphertext length before key recovery and plaintext length before JSON parsing. Reject compression and critical header extensions in this example.

The key registry must be configured with the permitted kid mappings, a default mapping where tokens without kid are supported, provider configuration, and the applications and releases allowed to use each key. Never try every stored key after a lookup or decryption failure. The DRE policy implementation uses the selected registry entry and the authenticated claims to enforce these permissions.

Define the challenge lifetime, supported releases, required runtime conditions, token time rules where applicable, key cache lifetime, provider deadlines, and retry limits in backend configuration. Refuse to enable protected endpoints if their required schema, policy, key mappings, or input limits are missing. This avoids silently generating permissive defaults.

DRE claim contract

The following object illustrates the binding claims already described in this guide. It is not a complete production payload:

{
  "nonce": "0123456789abcdef0123456789abcdef",
  "sid": "example-runtime-session",
  "iid": "example-installation"
}
Claim or policy input Validation contract
nonce Required nonempty JSON string. Match the exact issued value through its stored digest and challenge record.
sid Required nonempty JSON string. Match both the established runtime binding and the nonce record.
iid Required nonempty JSON string when installation binding is required. Match both the established installation binding and the nonce record. Enforce the release schema if the claim is present for other endpoints.
Application identity and version Use the exact claim names, types, and interpretation documented for the supported DexProtector release. Match the expected application and the permitted release/key mapping.
Runtime security conditions Use that release's documented claims and unsafe-state behavior. Reject missing required evidence and any state disallowed by policy.
Token time claims Validate their types and semantics when defined by the release schema. Always enforce the backend challenge expiration independently.

The binding example does not define the complete production claim names or runtime status values. Configure the matching DexProtector release schema and its policy mapping before enabling production validation. An AI coding agent must not invent fields such as appId, appVersion, or isSafe in the DRE payload, infer a safe state from absent claims, or treat the binding example as the production schema. applicationId in RequestContext is explicitly a field defined by the backend application. See JWT Best Current Practices for validating cryptographic processing and application context together.

Nonce issuance and atomic consumption

For the recommended challenge flow, generate each nonce using a cryptographically secure random generator. As an integration recommendation, use at least 16 random bytes with a fixed transport encoding supported by the DRE nonce input. This is a backend design recommendation, not a DexProtector API format requirement. Randomness should follow established security randomness guidance.

Before returning the nonce to the application, store a ChallengeRecord with the SHA-256 digest of its exact UTF-8 representation, the validated RequestContext, issuance and expiration times from the backend clock, and consumedAt = null. Require expiresAt to be later than issuedAt and within the configured challenge lifetime. Enforce uniqueness of nonceDigest; on collision, generate a new nonce. insertNew must never replace an existing binding. Issue the nonce only after storage succeeds. The response may be a dedicated challenge response or part of the initial application exchange.

consumeIfMatches computes the same nonce digest and succeeds only when all of the following hold:

  1. A corresponding record exists, has not been consumed, and has not expired according to the backend clock.
  2. The submitted trusted context matches the stored application, user, authentication session, operation, parameter digest, installation policy, and idempotency key, including the defined treatment of absent values.
  3. The authenticated sid matches both the record's expected session and the current trusted context. When installation binding is required, the same comparison succeeds for iid.
  4. A conditional update marks the record consumed. Exactly one concurrent request may succeed for that nonce.

This example assumes a transactional backend store. Run the conditional update in the same transaction as durable operation acceptance or creation of the idempotency record; it must not commit independently. Roll back on any subsequent failure, including checked exceptions. For external side effects, durably record the accepted work in that transaction and use the backend's delivery and idempotency mechanism. Alternative stores must provide equivalent atomic acceptance and replay protection.

Return false for an absent, expired, consumed, or mismatched challenge; throw for storage failures. Both outcomes prevent acceptance. A retry may return an existing operation result only after validating the current caller and matching the stored operation context; it must never execute the operation again merely because the same idempotency key was supplied.

Shared validation flow

The validator below uses the application interfaces defined above. Supply implementations that satisfy their contracts; successful decryption alone does not complete those contracts. The class shows the validation sequence and dependency wiring. HTTP handling, persistence, and the DRE policy for the supported release remain part of your backend implementation.

import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.crypto.AESDecrypter;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;

final class MapTokenValidator {
    private final TokenReader tokenReader;
    private final KeyRegistry keyRegistry;
    private final DrePolicy drePolicy;
    private final ChallengeStore challenges;

    MapTokenValidator(TokenReader tokenReader, KeyRegistry keyRegistry,
            DrePolicy drePolicy, ChallengeStore challenges) {
        this.tokenReader = Objects.requireNonNull(tokenReader);
        this.keyRegistry = Objects.requireNonNull(keyRegistry);
        this.drePolicy = Objects.requireNonNull(drePolicy);
        this.challenges = Objects.requireNonNull(challenges);
    }

    public void validateAndConsume(String compactJwt, RequestContext request)
            throws Exception {
        // Enforce input limits and strict parsing before any key service call.
        JWEObject token = tokenReader.parseCompact(compactJwt);
        JWEHeader header = token.getHeader();

        if (!JWEAlgorithm.A256KW.equals(header.getAlgorithm())
                || !EncryptionMethod.A256GCM.equals(header.getEncryptionMethod())
                || header.getCompressionAlgorithm() != null
                || (header.getCriticalParams() != null
                    && !header.getCriticalParams().isEmpty())) {
            throw new SecurityException("Unsupported MAP token profile");
        }

        KeyBinding binding = keyRegistry.resolve(header.getKeyID(), request);
        byte[] mapKey = binding.provider().recoverMapKey();
        try {
            if (mapKey == null || mapKey.length != 32) {
                throw new SecurityException("Invalid MAP key configuration");
            }
            // AESDecrypter performs A256KW unwrapping and GCM authentication.
            token.decrypt(new AESDecrypter(mapKey));
        } finally {
            if (mapKey != null) {
                Arrays.fill(mapKey, (byte) 0);
            }
        }

        // Parse and use payload claims only after authentication succeeds.
        Map<String, Object> claims =
                tokenReader.readClaims(token.getPayload().toBytes());
        drePolicy.validate(claims, binding, request);

        String nonce = requiredString(claims, "nonce");
        String sid = requiredString(claims, "sid");
        String iid = request.requiresInstallationBinding()
                ? requiredString(claims, "iid") : null;

        // Match context, check expiration, and consume in one atomic operation.
        if (!challenges.consumeIfMatches(nonce, sid, iid, request)) {
            throw new SecurityException("MAP challenge rejected");
        }
    }

    private static String requiredString(Map<String, Object> claims, String name) {
        Object value = claims.get(name);
        if (!(value instanceof String text) || text.isEmpty()) {
            throw new SecurityException("Required MAP claim is invalid");
        }
        return text;
    }
}

The following summary connects the implementation contracts to the validation flow:

Integration point Required behavior
tokenReader.parseCompact(...) Reject missing or oversized input before decoding. Require five correctly encoded compact JWE segments. Bound decoded sizes and validate the protected header as strict UTF-8 JSON, rejecting duplicate names, invalid types, excessive nesting, member counts, and field lengths. Check the encrypted key, IV, and tag lengths for this profile. Return a JWEObject parsed from the original compact token, preserving its protected header encoding.
tokenReader.readClaims(...) Enforce the payload byte limit and the same strict JSON rules, including rejection of trailing content. Return a JSON object with types preserved. Limits must be configured for the supported production schema.
keyRegistry.resolve(...) Select one permitted key binding from trusted backend configuration, enforce its current status, and reject unknown or ambiguous selection. Never try arbitrary keys or accept key material supplied by the request.
RequestContext Carry the validated user and authentication session where required, the expected application, the intended operation and relevant parameters, and the backend's binding policy. Derive these from authenticated backend processing; do not accept client assertions as trusted context.
drePolicy.validate(...) Validate the required claims for the applicable DexProtector release, application identity and supported version, the selected key's permission for that release, and required runtime security conditions. Validate token time claims where defined. Throw on any failed or missing required check.
challenges.insertNew(...) Persist a new challenge and its complete binding before returning the nonce. Enforce uniqueness without overwriting existing records.
challenges.consumeIfMatches(...) Atomically match the nonce record to the authenticated context, operation and parameters, expected sid, and iid when required; check expiration using the backend clock and mark the nonce consumed. Return false for missing, expired, mismatched, or consumed challenges. All backend instances must share this enforcement.

User authentication, permissions, and business checks remain required. Place this flow in the backend's authorization path, after the checks needed to accept the operation. Coordinate nonce consumption with durable operation acceptance or an idempotency record so concurrent submissions cannot execute the operation twice and retries have a defined result. Do not split challenge validation and consumption into independent read and write operations.

Any parsing, cryptographic, policy, or challenge failure prevents the sensitive operation. Key service failures also prevent it; the API may report temporary unavailability without accepting the request. Do not log complete tokens, recovered keys, or provider responses. Clearing mapKey is a best effort reduction of its lifetime: Java and the SDKs may retain other copies.

Request, Session and Installation Binding

For sensitive API operations, the backend must bind the validated JWT to the nonce issued by the server and the expected DexProtector runtime session (sid). Validate the installation identifier (iid) when installation binding is required by the endpoint policy.

Claim Binding Purpose
nonce Request / operation Supports replay prevention when the backend enforces operation binding, expiration, and single use
sid DRE runtime session Supports rejection outside an established runtime session binding
iid Application installation Supports rejection outside a registered installation binding

The backend should reject a cryptographically valid token if it is presented outside the expected request, runtime session, or installation context. Establish these bindings through a controlled session or enrollment flow; do not overwrite an established binding solely because a token presents a new identifier. sid is distinct from a user login session, and iid is not a permanent hardware identifier. Use the release's documented restart, reinstall, restore, and migration behavior to define how these bindings are restored and how recovery works.

For operations requiring user authentication, validate the normal user credential when issuing the nonce and again when processing the protected request. Store the nonce with the authenticated user and authentication session, intended operation, relevant parameters, expiration, and expected runtime or installation binding established by the backend. Reject a request whose validated context does not match this record. Derive user identity from validated credentials rather than an unverified user identifier supplied in the request.

This associates the attestation with the user session through backend state. It does not require copying the user's access token into the DRE Attestation Token or changing the DRE claim schema. This is an integration approach supporting the authentication and authorization objectives of MASVS-AUTH-1; that control does not prescribe a particular token binding format.

Integration recommendation: Mobile API Protection should be used for sensitive API operations. The backend must validate the DRE Attestation Token, including the nonce issued by the server, runtime session identifier (sid), and application installation identifier (iid) where installation binding is required, before authorizing the request.

Application Identity and Version Policy

The backend must verify the application identity and application version carried in the DRE Attestation Token against its current security policy.

A backend policy may define:

  • permitted application identifiers;
  • minimum supported application version;
  • currently supported protected application releases;
  • required token format and algorithm;
  • applicable key identifier;
  • required runtime security conditions.

Use a release allowlist or the platform's documented version representation rather than comparing version strings lexically.

This allows the backend to reject requests from obsolete or unsupported application versions even when the token itself is cryptographically valid.

IMPORTANT: Application version enforcement must be performed on the backend. Version checks performed by the client alone must not determine whether a protected API operation is authorized.

Key Management

The cryptographic key used for Mobile API Protection must be protected throughout its lifecycle.

Anyone obtaining the shared MAP key can generate valid JWTs under this profile, just as disclosure of an HMAC key permits forged JWTs authenticated with HMAC. JWE additionally hides the claims; both schemes depend on key protection for authenticity. Record whether keys are shared across releases or installations, and separate applications and environments to limit exposure.

In the recovery model described below, the plaintext MAP key is temporarily present in backend process memory. A bounded cache in memory may reduce provider calls, but it must enforce revocation and expiration. If no permitted key is available, a provider outage must not trigger an unverified fallback.

The backend copy of the key must be stored in a dedicated secret management or key management system, or another environment providing equivalent access control. Where appropriate, a Hardware Security Module (HSM) or cloud key management service may be used for protection of backend key material.

Key material required during the DexProtector protection process must be supplied only under an approved and controlled key management procedure.

The key must never be:

  • committed to source control;
  • stored directly in application source code;
  • included in CI/CD logs;
  • transferred over unprotected channels; or
  • retained in temporary build locations longer than required.

Example Key Management Integrations

Mobile API Protection does not depend on a specific key management provider.

A typical integration uses an enterprise key management system to provision the Mobile API Protection key and retain a protected backend copy, while the plaintext key is made available only temporarily during the DexProtector protection process.

flowchart TD
    K["Key management system"] --> T["Temporary plaintext MAP key"]
    K --> E["Persistent encrypted MAP key"]
    T --> P["DexProtector protection process"]
    P --> A["Protected APK/AAB or IPA/XCARCHIVE"]
    E --> B["Backend key repository"]

At runtime, the same general backend flow applies regardless of the key management provider:

  1. Read the JWT kid as an untrusted key selection hint.
  2. Resolve the encrypted MAP key through the backend registry.
  3. Recover the MAP key using the key management system.
  4. Unwrap the CEK and authenticate/decrypt the JWT payload.
  5. Validate the claims and request bindings, then apply the backend policy.

AWS KMS

AWS KMS can be used to generate and protect the symmetric Mobile API Protection key.

A typical integration uses AWS KMS GenerateDataKey with KeySpec = AES_256 and a symmetric encryption KMS key.

During application protection:

  1. The protection environment invokes AWS KMS GenerateDataKey.

  2. AWS KMS returns:

    • a plaintext 256-bit data key; and
    • a copy of the same key encrypted by KMS (CiphertextBlob).
  3. The plaintext key is supplied to DexProtector through <jwtKeyFile>.

  4. DexProtector embeds and protects the key inside the protected application.

  5. The CiphertextBlob is retained by the backend together with the corresponding jwtKeyId, application identifier, and application version.

  6. The temporary plaintext copy is removed when the protection operation has completed.

At runtime, the backend uses the token kid to locate the corresponding CiphertextBlob and invokes AWS KMS Decrypt to recover the Mobile API Protection key.

The recovered key is then supplied to the backend JWT library.

Use a maintained JWT library supporting the described JWE profile and the current AWS KMS SDK. A KMS encryption context containing no secrets can bind the encrypted MAP key to application/environment metadata; retain it in trusted backend records and supply the same context for decryption. Account for binary fields encoded as Base64 in CLI/HTTP responses when preparing the key file. See AWS KMS GenerateDataKey.

AWS KMS Java adapter

This adapter uses software.amazon.awssdk:kms. Supply a configured KmsClient and the stored result of KMS key provisioning. Use the full ARN of the intended symmetric KMS key and the same encryption context used when the MAP key was protected, or an empty map when none was used. The Java SDK returns decoded binary plaintext. See AWS KMS Decrypt and the Java request API.

import java.util.Map;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.DecryptRequest;
import software.amazon.awssdk.services.kms.model.EncryptionAlgorithmSpec;

MapKeyProvider awsProvider(KmsClient kms, String wrappingKeyArn,
        byte[] storedCiphertext, Map<String, String> encryptionContext) {
    DecryptRequest request = DecryptRequest.builder()
            .keyId(wrappingKeyArn)
            .encryptionAlgorithm(EncryptionAlgorithmSpec.SYMMETRIC_DEFAULT)
            .ciphertextBlob(SdkBytes.fromByteArray(storedCiphertext))
            .encryptionContext(Map.copyOf(encryptionContext))
            .build();
    return () -> kms.decrypt(request).plaintext().asByteArray();
}

Google Cloud KMS

Google Cloud KMS can protect the backend copy of the Mobile API Protection key using an envelope encryption model.

A typical integration is:

  1. The protection environment generates a cryptographically secure random 256-bit Mobile API Protection key.
  2. The plaintext key is supplied to DexProtector through <jwtKeyFile>.
  3. The same key is encrypted using a symmetric Google Cloud KMS key.
  4. The encrypted key is retained by the backend together with its jwtKeyId, application identifier, and application version.
  5. The temporary plaintext key is removed when the protection operation has completed.

At runtime, the backend obtains the corresponding encrypted Mobile API Protection key and invokes Google Cloud KMS Decrypt.

The recovered key is then supplied to the backend JWT library.

Google Cloud KMS does not need to process the JWT itself. Its role is to protect the persistent backend copy of the Mobile API Protection KEK, while a JWT library that complies with the standards and supports JWE performs A256KW and A256GCM processing.

See Google Cloud's envelope encryption guidance for this integration model.

Google Cloud KMS Java adapter

This adapter uses com.google.cloud:google-cloud-kms. Supply a configured KeyManagementServiceClient, the trusted CryptoKey resource name, and the ciphertext returned when Cloud KMS encrypted the MAP key. Supply the same additional authenticated data (AAD) used then, or an empty byte array when none was used. The adapter sends CRC32C checksums and verifies the returned plaintext checksum. These transport integrity checks complement the KMS operation; they do not validate the JWT. See Cloud KMS symmetric decryption, the Java request API, and the response integrity fields.

import com.google.cloud.kms.v1.DecryptRequest;
import com.google.cloud.kms.v1.DecryptResponse;
import com.google.cloud.kms.v1.KeyManagementServiceClient;
import com.google.protobuf.ByteString;
import com.google.protobuf.Int64Value;
import java.util.Arrays;
import java.util.zip.CRC32C;

MapKeyProvider googleProvider(KeyManagementServiceClient kms,
        String cryptoKeyName, byte[] storedCiphertext, byte[] aad) {
    DecryptRequest request = DecryptRequest.newBuilder()
            .setName(cryptoKeyName)
            .setCiphertext(ByteString.copyFrom(storedCiphertext))
            .setCiphertextCrc32C(Int64Value.of(crc32c(storedCiphertext)))
            .setAdditionalAuthenticatedData(ByteString.copyFrom(aad))
            .setAdditionalAuthenticatedDataCrc32C(Int64Value.of(crc32c(aad)))
            .build();
    return () -> {
        DecryptResponse response = kms.decrypt(request);
        byte[] key = response.getPlaintext().toByteArray();
        if (!response.hasPlaintextCrc32C()
                || crc32c(key) != response.getPlaintextCrc32C().getValue()) {
            Arrays.fill(key, (byte) 0);
            throw new SecurityException("KMS response integrity check failed");
        }
        return key;
    };
}

long crc32c(byte[] bytes) {
    CRC32C checksum = new CRC32C();
    checksum.update(bytes, 0, bytes.length);
    return checksum.getValue();
}

HashiCorp Vault

HashiCorp Vault can be integrated using the Vault Transit secrets engine.

Vault Transit supports generation of data keys and can return both a plaintext key and a representation of the same key encrypted by Vault.

A typical integration is:

  1. The protection environment requests a 256-bit data key using Vault Transit POST /transit/datakey/plaintext/:name with bits = 256.

  2. Vault returns:

    • the plaintext Mobile API Protection key; and
    • a representation of the same key encrypted by Vault.
  3. The plaintext key is supplied to DexProtector through <jwtKeyFile>.

  4. DexProtector embeds and protects the key inside the protected application.

  5. The Vault ciphertext is retained by the backend together with the corresponding jwtKeyId, application identifier, and application version.

  6. The temporary plaintext key is removed when it is no longer required.

At runtime, the backend locates the corresponding Vault ciphertext and uses Vault Transit decrypt to recover the Mobile API Protection key.

The recovered key is supplied to the backend JWT library.

Vault Transit does not need to process the JWT itself. It provides key generation, protection, decryption, rotation, and access control, while a JWT library that complies with the standards and supports the described JWE profile performs token authentication and decryption.

Vault returns plaintext data key material encoded as Base64. Decode it for binary key input or use the matching supported format. See the Vault Transit API.

HashiCorp Vault Transit Java adapter

This adapter uses org.springframework.vault:spring-vault-core. Supply VaultTransitOperations configured for the trusted Transit mount, the trusted Transit key name, and the stored Vault ciphertext (vault:vN:...). Use the original derivation context where applicable, or VaultTransitContext.empty() when no context was used. The binary overload below already decodes the response's Base64 plaintext; do not decode it again or convert the key to a text string. See the Spring Vault Transit API and Vault Transit decryption.

import org.springframework.vault.core.VaultTransitOperations;
import org.springframework.vault.support.VaultTransitContext;

MapKeyProvider vaultProvider(VaultTransitOperations transit,
        String transitKeyName, String storedCiphertext,
        VaultTransitContext context) {
    return () -> transit.decrypt(transitKeyName, storedCiphertext, context);
}

Provider Comparison

Provider Key provisioning Persistent backend form Backend recovery
AWS KMS GenerateDataKey KMS CiphertextBlob KMS Decrypt
Google Cloud KMS Generate 256-bit key and KMS Encrypt Cloud KMS ciphertext Cloud KMS Decrypt
HashiCorp Vault Transit datakey/plaintext Vault ciphertext Transit decrypt

These examples are illustrative. Equivalent key management systems may be integrated using the same model.

Provider configuration and operation

Keep the AWS and Google adapters in separate classes because their SDKs both define DecryptRequest. The factory methods above capture trusted configuration and return implementations of the same application interface; they do not accept token input as provider configuration.

Configure provider authentication through the backend's workload identity or managed credential mechanism, with decryption permission restricted to the required keys. Reuse provider clients and configure request deadlines, bounded retries, and concurrency limits. Any plaintext key cache must have a defined lifetime and honor key retirement and revocation. This example recovers the MAP key into backend memory; it does not claim that the MAP key remains inside KMS or an HSM during JWT processing.

The adapters recover an existing MAP key. They do not generate a new key per request or rotate the key embedded in an application. AWS encryption context, Google AAD, and Vault derivation context are provider specific metadata for recovering that key; none replaces the token's nonce and backend request binding.

DexProtector with vTEE: Stronger Key Protection with Asymmetric Key Agreement

Licel vTEE strengthens Mobile API Protection by isolating private key generation, storage, and cryptographic use within the vTEE instance.

Where Licel vTEE is used, the key protection model can be further strengthened through an asymmetric key establishment scheme. In this integration model, each application installation generates its own asymmetric key pair inside the isolated vTEE instance, and the private key remains within vTEE. The application and backend establish shared key material through an authenticated key agreement protocol, such as ECDH, followed by a suitable key derivation function. Public keys are exchanged and authenticated; private keys and the resulting shared secret are not transmitted.

This allows symmetric key material unique to each installation to be established at runtime without embedding that shared key during application protection. The protocol must define peer authentication, public key validation, derivation context, and key lifecycle, using established key agreement standards such as NIST SP 800-56A. Use the supported vTEE provisioning and backend integration for the selected release. If the derived key is used as the MAP KEK, the backend must resolve the corresponding installation key; the A256KW / A256GCM JWT profile can remain unchanged.

Configuration Key protection on the client
DexProtector Key embedded and protected in the application
DexProtector with vTEE Key protection and cryptographic use within vTEE

A supported vTEE integration can further reduce exposure of keys used by the client over the long term. Use the provisioning and cryptographic operation procedure supplied for the applicable versions; the embedded key XML example above does not itself enable vTEE. Where the same JWT encryption profile is retained, the backend token validation and policy model remains applicable.

Key Rotation

The Mobile API Protection key should be rotated periodically, preferably with each new protected application release.

The backend should retain keys for application versions that remain supported and select the appropriate key using the token key identifier and application version policy.

When a protected application version reaches end of support, the corresponding key should be retired according to the organization's key management policy.

Rotating a provider wrapping key or encrypting the same MAP key under a new wrapping key does not rotate the MAP key embedded in existing applications. Replacing that embedded key requires the applicable application protection or provisioning workflow.

Key rotation provides cryptographic separation between protected application releases and limits the impact of key exposure over the long term.

For example:

Application 8.4.0 → kid-100 → protected-key-100
Application 8.4.1 → kid-101 → protected-key-101
Application 8.5.0 → kid-102 → protected-key-102

The backend should retain the corresponding protected key only while the application release remains supported.

Integration recommendation: The backend must validate the application identity and version against the currently supported security policy and use the corresponding Mobile API Protection key. Requests from unsupported or obsolete application versions must be rejected.

Network Security

DRE Attestation Tokens must always be transmitted over HTTPS.

For sensitive APIs, prefer TLS 1.3 with normal certificate chain and hostname validation, consistent with MASVS-NETWORK-1. DexProtector Public Key Pinning (PKP) adds an expected key constraint. To address MASVS-NETWORK-2, configure and test pinning for all remote endpoints under the developer's control, with backup pins and a planned rotation process. TLS provides server authentication, confidentiality, and integrity for the network connection.

Review the trust anchors used by the actual network stack. Trust in certificates installed by a user depends on the platform, application configuration, and device state. For example, Android apps targeting API level 24 or later do not trust the user CA store by default. See Android Network Security Configuration.

Certificate Transparency (CT) provides visibility into certificate issuance and supports checks that certificates have been logged. Apply CT according to the platform, certificate infrastructure, and security policy. It does not establish that a certificate contains the expected backend public key, and MASVS-NETWORK-1 and MASVS-NETWORK-2 do not themselves mandate CT. Pinning and CT retain separate roles.

Mobile API Protection and network security provide complementary controls:

Mobile API Protection enables the backend to validate the protected application and its security context.

TLS 1.3 protects the confidentiality and integrity of the communication channel.

Public Key Pinning constrains accepted backend public keys. Certificate Transparency supports detection of misissued certificates and is not an equivalent substitute for pinning. See Certificate Transparency.

Integration Recommendations

For sensitive APIs using the recommended integration, apply the following controls. “Must” indicates a requirement for this integration model; “should” indicates a recommendation.

  • Mobile API Protection must be enabled for the protected application.
  • The backend must generate and validate a unique nonce and ensure it is used only once.
  • The backend must cryptographically authenticate and decrypt the DRE Attestation Token before authorizing the operation.
  • The backend must enforce sid binding and validate iid when installation binding is required.
  • Application identity and version must be validated against the current backend security policy.
  • The token kid must be treated as a key selection identifier and not as an authorization decision by itself.
  • Mobile API Protection key material must be securely stored and should be rotated periodically, preferably with each new protected application release.
  • Backend key material should be protected using an enterprise key management system, cloud KMS, secret management system, or HSM where appropriate.
  • The backend should retain the protected form of the Mobile API Protection key rather than a persistent plaintext copy.
  • Plaintext key material required during the DexProtector protection process should exist only for the minimum time required.
  • Sensitive Mobile API Protection traffic must use HTTPS, preferably with TLS 1.3; use DexProtector PKP and Certificate Transparency according to their distinct roles.
  • Where stronger isolation of keys used by the client over the long term is required, Mobile API Protection may be combined with Licel vTEE.
  • To prevent abuse by bots, combine these checks with rate limits, session controls, and fraud monitoring; use Alice Threat Intelligence telemetry where integrated.

The recommended integration combines nonce validation, cryptographic token validation, sid/iid binding as described above, application/version policy, key protection, and HTTPS transport. Key rotation should follow the deployment policy.

Security Model

Mobile API Protection complements, rather than replaces, DexProtector application and network protections.

A sensitive API request can therefore be evaluated across several independent layers.

Application Integrity and RASP

The DRE evaluates the protected application and runtime environment.

Mobile API Protection

The backend authenticates and decrypts the DRE Attestation Token and evaluates the applicable security claims.

An attacker may try to make a genuine application produce valid tokens without extracting its key, analogous to the signing oracle attack described in the article on protecting mobile APIs from bot attacks. Changing from HMAC signing to JWE encryption alone does not address this risk. The backend must enforce the documented runtime security policy and operation binding alongside client hardening and runtime protection. A valid JWT alone does not establish that activity is human or free from automation.

Request, Session and Installation Binding

The nonce, sid, and optional installation binding policy for iid allow the backend to enforce the expected operation, runtime session, and installation context through the checks described above.

Key Protection

DexProtector protects the Mobile API Protection key embedded into the protected application.

An enterprise key management system such as AWS KMS, Google Cloud KMS, HashiCorp Vault, or an HSM can protect the corresponding persistent backend key material.

Licel vTEE can isolate private keys on the client and the cryptographic operations that use those keys. See DexProtector with vTEE: Stronger Key Protection with Asymmetric Key Agreement for details of key generation and key agreement for each installation.

Network Security

HTTPS, preferably using TLS 1.3, protects communication with the backend. DexProtector PKP adds an expected key constraint; Certificate Transparency provides complementary transparency checks.

This allows backend services to base sensitive authorization decisions not only on user credentials, but also on the identity, version, installation context, and security state of the protected mobile application making the request.

OWASP MASVS and ASVS Mapping

The OWASP Mobile Application Security Verification Standard (MASVS) covers mobile application security. The OWASP Application Security Verification Standard (ASVS) provides additional requirements relevant to backend services. This mapping uses MASVS 2.1.0 and ASVS 5.0.0; ASVS input validation is in V2 in this version.

The table relates practices in this guide to selected OWASP objectives. The implementation guidance is our application of those objectives. Assessment requires evidence from the configured application and backend; this mapping alone does not demonstrate compliance.

Mobile API Protection is directly relevant to MASWE-0056: App Attestation Not Implemented, which OWASP maps to MASVS-RESILIENCE-2. The DRE Attestation Token and backend validation workflow described here address the need to provide verifiable evidence about the application instance and enforce the expected application identity and integrity policy before authorizing protected operations.

MASWE-0054: Device Attestation Not Implemented maps to MASVS-RESILIENCE-1 and concerns evidence about the device. DexProtector platform checks and supported DRE runtime claims contribute to evaluating the execution environment. Where platform device attestation is required, assess and validate that evidence separately; the DRE JWT alone does not establish the guarantees of a platform device attestation service.

OWASP reference Application to this integration Guide section
MASVS-RESILIENCE-1 Apply DexProtector platform integrity checks and evaluate supported DRE environment claims against the backend policy. Assess device attestation separately where required, including the evidence and validation described in MASWE-0054. Application Integrity and RASP and Backend Validation
MASVS-RESILIENCE-2 Combine DexProtector application integrity protections with DRE JWT validation and backend enforcement of application identity, required integrity claims, and request binding. This addresses the app attestation concerns described in MASWE-0056. Backend Validation and Security Model
MASVS-AUTH-1 Validate user authentication and permissions independently of attestation. Associate the nonce with the validated user session, intended operation, and expected runtime context in backend state. Request, Session and Installation Binding
ASVS v5.0.0-2.2.1 and v5.0.0-2.2.2 Enforce documented token and parser limits, required claim types, and expected structures on the backend. Select size limits for the supported token profile. Backend Validation
ASVS v5.0.0-9.1.1, v5.0.0-9.1.2 and v5.0.0-9.1.3 Verify the JWE authentication tag before trusting claims, restrict algorithms to the configured profile, and resolve keys only through trusted backend records. Backend Validation
ASVS v5.0.0-15.4.2 In the recommended nonce model, make the check and consumption of the challenge atomic so concurrent requests cannot authorize the same operation twice. How Mobile API Protection Works
ASVS v5.0.0-2.4.1 Limit excessive calls to challenge and protected API endpoints, including requests that carry cryptographically valid tokens. Bound validation work and external key service calls. Backend Validation and Security Model
MASVS-NETWORK-1 Use HTTPS with secure TLS settings, certificate chain validation, hostname validation, and an appropriate trust configuration. Network Security
MASVS-NETWORK-2 Apply identity pinning to remote endpoints under the developer's control. Configure DexProtector PKP with a tested backup and rotation plan. CT provides complementary certificate visibility. Network Security
MASVS-CRYPTO-1 and MASVS-CRYPTO-2 Use the defined authenticated encryption profile and protect keys throughout generation, provisioning, storage, use, rotation, and retirement. Assess the configured DexProtector or DexProtector with vTEE key protection model. Key Management and DexProtector with vTEE

Frequently Asked Questions

Which DexProtector version does this guide cover?

This guide describes Mobile API Protection in DexProtector 17.x. Please upgrade to the latest version of DexProtector to use this feature. Configuration options, token formats, APIs, and claim semantics may differ in other versions.

Is the DRE Attestation Token a JWT or JWE?

The DRE Attestation Token is a JWT. In the implementation described here, JWE provides its encryption and authentication using A256KW and A256GCM. The backend JWT library must support this encrypted profile; verification limited to signatures is insufficient.

Does Mobile API Protection replace user authentication?

No. Mobile API Protection adds application and runtime security information to the backend's decision. The backend must also enforce the user authentication, permissions, and business rules required for the operation.

What prevents token replay?

The backend binds a nonce issued by the server to the intended operation and relevant authenticated context, checks its expiration, and atomically consumes it when accepting the operation. It must reject attempts to authorize another operation using an expired or consumed nonce. Including a nonce in the JWT without these backend checks does not prevent replay.

Does the key management service decrypt the JWT?

In the AWS KMS, Google Cloud KMS, and Vault integrations described here, the service recovers the protected backend copy of the MAP key. The backend JWT library then uses that key to unwrap the CEK and authenticate and decrypt the token. Claim validation and operation authorization remain backend responsibilities.

Is Licel vTEE required?

No. DexProtector protects the MAP key embedded during application protection. DexProtector with Licel vTEE provides stronger isolation of key material and cryptographic operations, including the asymmetric key agreement model described in this guide.

How does Mobile API Protection support OWASP MASVS?

Mobile API Protection supports relevant resilience, authentication, cryptography, and network security objectives when combined with the backend controls and application protections described in this guide. App attestation and backend enforcement are particularly relevant to MASWE-0056 and MASVS-RESILIENCE-2. DexProtector platform checks and supported runtime claims also contribute to MASVS-RESILIENCE-1. See the OWASP MASVS and ASVS mapping for the applicable references and the distinction between app and device attestation. Compliance depends on the complete integration, the assessment scope, and verification of the deployed controls.

Related Documentation

FAQs

General FAQs - DexProtector for Android

  1. What types of final packages can be DexProtected?
    For Android: All APKs, App Bundles (AABs), and Android libraries/SDKs (AARs).

  2. Is DexProtector server-side or offline?
    The protection process takes place entirely offline, but Runtime Checks can (optionally) send reports to servers.

  3. Does DexProtector work with apps created with cross-platform frameworks, such as Cordova (/Phonegap), Ionic, ReactNative, Flutter, NativeScript?
    Yes; see the page on Implementations and Integrations.

  4. Can DexProtector secure Native Code / Native Libraries?
    Yes; see Native Library Encryption and JNI Obfuscation in the Configuration File Overview.

  5. Is DexProtector compatible with name obfuscators such as R8 or ProGuard?
    Yes, DexProtector is fully compatible with name obfuscators such as R8 and ProGuard. For maximum security, it is worth using R8/ProGuard for additional name obfuscation, but certainly not as a replacement for DexProtector's protection mechanisms, which are far more comprehensive and work at a deeper level. The two tools can be combined seamlessly, with no conflicts. Apply ProGuard first for name obfuscation, and then either direct DexProtector to the ProGuard mapping file (via the CLI, via DexProtector Studio, or in the <proguardMapFile> tag in the configuration file: e.g. <proguardMapFile>/Users/developer/project/proguard/mapping.txt</proguardMapFile>) or specify both dependencies in Gradle and DexProtector will locate the mapping file automatically.

  6. How does DexProtector work with Gradle?
    The DexProtector Gradle plugin .jar file is included in the standard distribution package; all you need to do is set the path to DexProtector in buildscript.repositories in the project.build.gradle, add ':dexprotector-gradle-plugin:' and ':dexprotector:' as dependencies in the buildscript, apply the DexProtector plugin to the app.build.gradle, and specify the DexProtector configuration file in the productFlavor and/or buildType block. For more information, see our guide to the DexProtector Gradle plugin.

  7. Can we use DexProtector with Bitrise / Jenkins / Bitbucket Pipelines?
    Yes; and we have a verified Bitrise Step: https://www.bitrise.io/integrations/steps/dexprotector

FAQs on Installation, Activation, Configuration, and Distribution

  1. Where can I download the distribution package?
    Links to distribution packages will be sent directly when trial or full licenses are issued, and when there are major updates to DexProtector. If you would like to request the latest version, please write to our support team.

  2. Which is the most recent version of DexProtector?
    To check the most recent version of DexProtector, along with all release notes, please see our Product updates page.

  3. Which version am I currently using?
    To check which version of DexProtector you are currently using, you can (a) click License Info in DexProtector Studio; (b) run the following command in the CLI: java -jar dexprotector.jar -info; or, (c) check the DexProtector log for the following information:

    DexProtector Enterprise
    Version: 12.5.9
    Build id: 20220131
    Licensed to: dexprotector@licelus.com
    Support until: 01/01/2023
  4. When does my license expire?
    To check when your support license expires, you can (a) click License Info in DexProtector Studio; (b) run the following command in the CLI: java -jar dexprotector.jar -info; or, (c) check the DexProtector log for the following information:

    DexProtector Enterprise
    Version: 12.5.9
    Build id: 20220131
    Licensed to: dexprotector@licelus.com
    Support until: 01/01/2023
  5. Can I continue to use DexProtector after my license has expired?
    Under the terms of our End User Licensing Agreement, you need to have an active license to use DexProtector to protect an app for distribution.

  6. What will happen to previously DexProtected apps after my license has expired?
    In the majority of cases, there will be no problems with DexProtected apps that have already been distributed; conflicts sometimes arise for two reasons: (1) When new OS versions are released; (2) When new devices are released. It is not always possible to futureproof DexProtector against developments related to these. In terms of security, though, published applications will be vulnerable when new tools and techniques used for attacks are developed, or when existing tools and techniques are updated. DexProtector has to be updated frequently to detect and prevent these tools.

  7. What are the system requirements for DexProtector?
    DexProtector works equally with Linux, Windows, or MacOS. The only requirement (or rather strong recommendation) is use Oracle's JRE, and it is also recommended to use the most recent version of Java.

  8. Does DexProtector work on MacOS with Apple Silicon (ARM64)?
    Yes, versions of DexProtector work on MacOS with Apple Silicon (ARM64), such as the M1 chip.

  9. Why do I get the error message: 'The license file is not found or corrupted. Try running DexProtector with the -activate option'?
    Activation codes are single-use, and license files are restricted to a specific user on a specific device / operating environment; they cannot be transferred between users or between devices / operating environments. Every time you try to use DexProtector on an additional device, you will need to request a new code. License files can also be corrupted due to OS updates, the effects of antivirus software, and the presence of external storage devices. Depending on your license agreement, there may be a limit to the number of active license files that you can have at any one time, but you are free to request a new activation code as often as you find necessary by writing to our support team.

  10. Does DexProtector support Google Play App Signing?
    Yes; just specify the keystore information and SHA256 fingerprint of the target certificate in your configuration. DexProtector will re-sign the package with the upload key, and then during every runtime will perform checks to make sure that the App Signing Key's certificate corresponds to the SHA256 fingerprint it calculates. For more information, see our guide to Google Play App Signing.

  11. My app is crashing after DexProtection; what can I do?
    If you are testing and your app is crashing unexpectedly, please make sure that (a) you are using the most recent version of DexProtector; (b) you have the correct signing configuration, and (c) you have not encrypted any classes or resources that need to be accessed directly by the system. If you continue to have problems, please write to our support team, and attach the DexProtected app, the relevant configuration file, and the verbose protection log. If you suspect a conflict with R8/ProGuard, please also send the mapping file.

    If your app is crashing unexpectedly on end users' devices after distribution, please write to our support team, and include all relevant stack traces, and details from the developer console (Google Play or Amazon Appstore) about app version, affected devices, and OS versions. Please also attach the DexProtected app, the relevant configuration file, and the verbose protection log. To generate the verbose log, do as follows depending on how you use DexProtector:

    • DexProtector Studio: Enable verbose logging via the Build Settings window, then after protection find the link to the saved log in the 'Protection results' window
    • CLI: Add -verbose to your command line for running DexProtector, then redirect the standard output to a file (... > dexprotector_log.txt 2>&1)
    • Gradle plugin: add <verbose>true</verbose> to your DexProtector configuration file; the logs will be saved at build/outputs/dexprotector/<buildType>/* (please send all files from the folder)