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.propertiesYou need to specify thendk.dirproperty to point to the path of the NDK. -
./gradle/wrapper/gradle-wrapper.propertiesThis file defines the Gradle version. Here, you must usegradle-2.10, so update the last line accordingly. -
./build.gradleThis file defines the plugin used during the build. Replace it with:com.android.tools.build:gradle-experimental:0.6.0-beta5. -
./app/build.gradleThis file changes significantly. The key updates include:- The plugin changes from
com.android.applicationtocom.android.model.application. - All configurations must be placed inside the
model { }block. minSdkVersionandtargetSdkVersionmust configure theirapiLevelattributes.
- The plugin changes from
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:
moduleNamedetermines the name of the compiled library. This option is required.platformVersionspecifies the NDK platform version. Here, 14 is used becauseminSdkVersionis 14.ldFlagsspecifies linker flags. This example uses only HMAC, so it linkscrypto. If TLS support is needed, change this to:ldFlags.addAll(["-lcrypto", "-lssl"]).abiFiltersspecifies the ABI versionarmeabi-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