Android AES Cipher Encryption/Decryption -using JNI library

HareshVaghela
Mobile App Development Publication
4 min readJan 11, 2022

Nowadays, posting secure data from Android apps to the backend service is one of the most important parts of development and process the data.

Furthermore, things need to be encrypted and decrypted from the backed Android and iOS devices as well.

A Java/Android based solution is shown here to encrypt and decrypt any data. Additionally, you can encrypt the information of the user while providing API parameters and storing it locally as well

For an additional layer of security, we will use Native NJI references to store Encryption Keys and Encryption IV as securely as possible within the application.

1. The native support version shall be defined in the module: Gradle file

Here’s what you need to know about android.mk and Application.mk:

ndkVersion '22.1.7171670'
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}

2. Create JNI directory in module: src/main/jni and setup JNI code

The encryption key and encryption IV should be stored in a c/c++ file.

The path must be followed: module: src/main/jni

For your reference, here is a photo showing how to create files and directories

Here is the code that will appear in Android.mk, which specifies where to find the local module. Our encryption keys will be stored in keys.c, in the corresponding file.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := keys
LOCAL_SRC_FILES := keys.c

include $(BUILD_SHARED_LIBRARY)

You can find the code here that will appear in Application.mk, and it has been set to support all abi versions. If you like, you can also target specific CPU architectures.

APP_ABI := all
# APP_ABI := armeabi-v7a arm64-v8a x86
# APP_ABI := armeabi-v7a
# APP_ABI := arm64-v8a
# APP_ABI := x86
# APP_ABI := x86_64
# APP_ABI := all

The keys.c file holds our plain text, which will be used for further operations such as encryption and decryption.

  • As a first step, you will need to create a method, which will be named as follows.
  • As opposed to plain text, we will encode IV and Secret Key using base64 encoding.
  • Using this method you are able to get your base64 encoded strings in a specific path only. In my case, that’s the file com.example.app.SecurityCrypto.java.
  • Make sure to also stipulate the method name.
#include <jni.h>

/**
* Encryption/Decryption Secret Key Spec
*/
JNIEXPORT jstring JNICALL
Java_com_example_app_SecurityCrypto_getEncryptionKey(JNIEnv *env, jobject thiz) {
return (*env)->NewStringUTF(env, "clRUNGJlek9JSXVrbjdBNC9sUFhlME9yR0ViU29JMkY=");
}
/**
* Encryption/Decryption IV Key Spec
*/
JNIEXPORT jstring JNICALL
Java_com_example_app_SecurityCrypto_getEncryptionIV(JNIEnv *env, jobject thiz) {
return (*env)->NewStringUTF(env, "UW00ZHNPVUtFZnAxcG5HdA==");
}

3. It’s now only a matter of accessing those methods in your Java code

  1. Create a file SecurityCrypto.java
  2. Load the keys.c JNI library
static {
System.loadLibrary("keys");
}

3. Create a method and method name same as you have mentioned in the keys.c file

private native String getEncryptionKey();

private native String getEncryptionIV();

Encryption and decryption of the data will be carried out using AES/CTR/NoPadding algorithm.

private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;

private void configCipherEnc() {
keyspec = new SecretKeySpec(Base64.decode(getEncryptionKey(), Base64.DEFAULT), "AES");
ivspec = new IvParameterSpec(Base64.decode(getEncryptionIV(), Base64.DEFAULT));

try {
cipher = Cipher.getInstance("AES/CTR/NoPadding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
}

public byte[] decryptBytes(byte[] bytes) throws Exception {
if (bytes == null)
throw new Exception("Empty");

byte[] decrypted = null;
try {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(bytes);
} catch (Exception e) {
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}

private byte[] encrypt(String text) throws Exception {
if (text == null || text.length() == 0)
throw new Exception("Empty string");

byte[] encrypted = null;

try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);

encrypted = cipher.doFinal(padString(text).getBytes());
} catch (Exception e) {
throw new Exception("[encrypt] " + e.getMessage());
}

return encrypted;
}

private static String padString(String source) {
char paddingChar = ' ';
int size = 16;
int x = source.length() % size;
int padLength = size - x;

for (int i = 0; i < padLength; i++) {
source += paddingChar;
}
return source;
}

A code example is given below that illustrates how to use the encryption and decryption method in an activity or other class.

String encData="content that needs to be decrypted - in base64 or plain";
String decryptedData = new String(decryptBytes(Base64.decode(data, Base64.DEFAULT)));
String encData=Base64.encodeToString(encrypt(plainText), Base64.DEFAULT).replace("\n", "");

To make it Compatible with the Node.js and Angular, you should use the same “aes-256-ctr” algorithm and the secret key and IV key as well.

I hope you find it useful 🤝

— Thanks

--

--

HareshVaghela
Mobile App Development Publication

Passionate about crafting high-performance mobile applications and actively sharing insights to elevate the collective expertise of the developer community.