Oculta tus API Keys con NDK

api keys con ndk

Episodio 29 – Oculta tus API Keys con NDK

Uno de los aspectos que debemos cuidar en nuestros proyectos es ocultar las claves privadas que usamos para software de terceros.

Para protegerlas, uno de los métodos más interesantes, es usar código nativo C++.

Si bien no se consigue una seguridad total frente a hackeos, sí que añade una capa de seguridad adicional frente a dejarlo a la vista dentro de los apks, usándolo como buildConfigField mediante Gradle, o peor aún, directamente en el fichero strings.xml.

Hay otro modo de ocultar las claves, que es hacer uso de Firebase Remote Config, algún equivalente, o que te lleguen directamente desde un endpoint proporcionado por tu backend. Pero este método tiene la desventaja de que hace falta internet para poder usar las claves. Además, hay bibliotecas que es conveniente inicializar en la clase que extiende de Application en nuestro proyecto. Y esto puede llevar a ciertos engorros la primera vez que el usuario se instala la app, ya que aún no estará cacheada la respuesta y puede que no esté cargado el valor de la clave al pasar por ahí, con el consiguiente fallo al inicializar la biblioteca.

Por tanto, basta con que sigas los siguientes pasos:

PASO 1

Accede al SDK Manager y asegúrate de tener instalado NDK y CMake en las SDK Tools de tu Android Studio.

PASO 2

Primero de nada, como no queremos que las API Keys queden registradas por el control de versiones, añadimos la siguiente línea al fichero .gitignore de la carpeta raíz del proyecto: app/src/main/cpp/api-keys.cpp.

Para este ejemplo vamos a usar api-keys como nombre del fichero, pero puedes llamarlo como quieras. Eso sí, mantén siempre el mismo para el resto de pasos. 😅

PASO 3

Dentro del directorio app/src/main, crea otro llamado cpp.

Vamos a suponer que esto lo estás creando dentro del módulo app de tu proyecto. Para cualquier otro, simplemente bastaría con extrapolarlo.

PASO 4

Crea un singleton para hacer de puerta de enlace entre el código nativo y Kotlin. Le vamos a llamar ApiKeyRetriever y tendrá la siguiente pinta:

package soy.gabimoreno.example

object ApiKeyRetriever {

    init {
        System.loadLibrary("api-keys")
    }

    external fun getFooApiKey(): String
}

Detalles a tener en cuenta:

  • El nombre del paquete donde está alojado este fichero, ya que lo usaremos en el siguiente paso. En este caso, soy.gabimoreno.example
  • Hay que cargar el fichero nativo api-keys con System.loadLibrary(). Tranqui, este fichero lo crearemos en el siguiente paso
  • Declarar la función puente para acceder a la clave con external

PASO 5

Y ahora, dentro del directorio cpp, creamos el fichero nativo para almacenar las API keys: api-keys.cpp.

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_soy_gabimoreno_example_ApiKeyRetriever_getFooApiKey(JNIEnv* env, jobject /* this */) {
    std::string apiKey = "FOO_API_KEY";
    return env->NewStringUTF(apiKey.c_str());
}

Como estarás deduciendo, el nombre de la función empieza por Java_ y le siguen el paquete, singleton y función puente a emplear, todo separado por underscores.

PASO 6

También te hará falta añadir dentro de la carpeta app el fichero CMakeLists.txt.

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        api-keys

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        src/main/cpp/api-keys.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        api-keys

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

PASO 7

Añade la siguiente configuración al build.gradle de app.

android {
    ...
    defaultConfig {
        ...
    }
    buildTypes {
        ...
    }
    externalNativeBuild {
        cmake {
            path 'CMakeLists.txt'
        }
    }
}

PASO 8

Finalmente, para acceder a la clave, simplemente haz esta llamada:

ApiKeyRetriever.getFooApiKey()


Por cierto, acuérdate de subir el fichero api-keys.cpp a tu sistema de Integración Continua (si es que usas en ese proyecto) al igual que harías con un local.properties, ya que te hará falta para que todo vaya como toca.

Una capa adicional que podrías añadir para conseguir más seguridad, sería encriptar estas claves y ponérselo aún más difícil a los amigos de lo ajeno.

Si quieres indagar más, puedes ir a este artículo, que es el que seguí para llevar esto a la práctica.

Y para poder comprobar si tienes tus claves protegidas, puedes usar ApkPure para descargar el / los apks que hay públicos en la store, y ApkTool, para decompilarlo / s.

Oculta tus API Keys con NDK
Publicado: 2021-01-04 Actualizado: 2023-09-22