Using OpenSSL in Android

Taking HMAC-SHA256 as an example

Posted on March 2, 2016 -

Since Android 6.0, Google has required developers not to use the system OpenSSL. See: https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html?hl=zh-cn. Therefore, please stop using the method introduced in this article. Instead, cross-compile OpenSSL yourself or use a precompiled version.

Because Java is relatively easy to decompile, placing some critical code in .so files becomes a low-cost alternative. Although .so files can still be reverse-engineered, the difficulty of analyzing them is significantly higher than analyzing Java bytecode. Many security-related implementations depend on OpenSSL, yet tutorials on using OpenSSL with the NDK are not very common. After a day of exploration, I finally managed to successfully invoke OpenSSL from the NDK. This article uses the HMAC algorithm in OpenSSL as an example to demonstrate how to configure the NDK with Gradle and how to use OpenSSL in the NDK.

Configuring the NDK in Gradle

In July 2015, Google released a new Gradle plugin providing NDK support. Since then, writing NDK applications no longer requires an Android.mk file, nor does it require the ndk-build script. Instead, you only need simple Gradle configuration to conveniently compile your project.

Currently, the new plugin is still in beta. This article uses the latest version as of March 2, 2016: 0.6.0-beta5. To get the latest updates, please visit here.

Migrating from the traditional Android Gradle plugin to the new one is not difficult. You only need to modify three files in the existing directory structure. For example, consider the following directory:

.
├── app/
│   ├── build.gradle
│   └── src/
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

The files requiring modification include two build.gradle files, gradle-wrapper.properties, and local.properties.

Here are the changes for each:

Each version of the plugin only supports specific Gradle versions. Be sure to refer to the link provided above to fill in the correct version.

  • ./local.properties You need to specify the ndk.dir property to point to the path of the NDK.

  • ./gradle/wrapper/gradle-wrapper.properties This file defines the Gradle version. Here, you must use gradle-2.10, so update the last line accordingly.

  • ./build.gradle This file defines the plugin used during the build. Replace it with: com.android.tools.build:gradle-experimental:0.6.0-beta5.

  • ./app/build.gradle This file changes significantly. The key updates include:

    1. The plugin changes from com.android.application to com.android.model.application.
    2. All configurations must be placed inside the model { } block.
    3. minSdkVersion and targetSdkVersion must configure their apiLevel attributes.

A complete example:

apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion 23
        buildToolsVersion "23.0.2"

        defaultConfig {
            applicationId "com.example.openssltest"
            minSdkVersion.apiLevel 14
            targetSdkVersion.apiLevel 23
            versionCode 1
            versionName "1.0"
        }

        ndk {
            moduleName = "openssl-jni"
            platformVersion = 14
            ldFlags.add("-lcrypto")
            abiFilters.add("armeabi-v7a")
        }
    }
}

dependencies {
    compile 'com.android.support:appcompat-v7:23.1.1'
}

In this configuration, the ndk block is new. Below is an explanation of its configuration:

  • moduleName determines the name of the compiled library. This option is required.
  • platformVersion specifies the NDK platform version. Here, 14 is used because minSdkVersion is 14.
  • ldFlags specifies linker flags. This example uses only HMAC, so it links crypto. If TLS support is needed, change this to: ldFlags.addAll(["-lcrypto", "-lssl"]).
  • abiFilters specifies the ABI version armeabi-v7a.

Using OpenSSL in the NDK

Android already includes OpenSSL, but the NDK does not provide the corresponding libraries. You only need to place OpenSSL’s .so files into the NDK:

$ adb pull /system/lib/libssl.so /myndk/platforms/android-14/arch-arm/usr/lib

$ adb pull /system/lib/libcrypto.so /myndk/platforms/android-14/arch-arm/usr/lib

Then place OpenSSL header files inside the directory: /myndk/platforms/android-14/arch-arm/usr/include.

Refer to JNI documentation when writing code. Below is an example implementation invoking HMAC-SHA256:

#include <jni.h>
#include <openssl/hmac.h>

#ifdef __cplusplus
extern "C" {
#endif
jbyteArray
Java_com_example_openssltest_MainActivity_hmacSha256(JNIEnv *env,
                                                     jobject obj,
                                                     jbyteArray content) {
  unsigned char key[] = {0x6B, 0x65, 0x79};

  unsigned int result_len;
  unsigned char result[EVP_MAX_MD_SIZE];

  // get data from java array
  jbyte *data = env->GetByteArrayElements(content, NULL);
  size_t dataLength = env->GetArrayLength(content);

  HMAC(EVP_sha256(),
       key, 3,
       (unsigned char *) data, dataLength,
       result, &result_len);

  // release the array
  env->ReleaseByteArrayElements(content, data, JNI_ABORT);

  // the return value
  jbyteArray return_val = env->NewByteArray(result_len);
  env->SetByteArrayRegion(return_val, 0, result_len, (jbyte *) result);
  return return_val;
}
#ifdef __cplusplus
}
#endif

Calling it from Java is also straightforward—just reference the library specified in build.gradle:

public native byte[] hmacSha256(byte[] data);

static {
    System.loadLibrary("openssl-jni");
}

DEMO Project Link

https://github.com/dangfan/android-ndk-openssl

Note that this is only a simple demo—do not store keys or similar sensitive information directly in your project.


Cover image: Close-up of a blue and white computer keyboard.

Author: Gavin Phillips