Práctica recomendada para almacenar y proteger claves API privadas en aplicaciones

La mayoría de los desarrolladores de aplicaciones integrarán algunas bibliotecas de terceros en sus aplicaciones. Si es para acceder a un servicio, como Dropbox o YouTube, o para iniciar sesión lockings. La cantidad de bibliotecas y servicios de terceros es asombrosa. La mayoría de esas bibliotecas y servicios están integrados de alguna manera al autenticarse con el servicio, la mayoría de las veces, esto sucede a través de una clave API. Por razones de seguridad, los servicios generalmente generan una clave pública y privada, a menudo también denominada clave secreta. Desafortunadamente, para conectarse a los servicios, esta clave privada debe usarse para autenticarse y, por lo tanto, probablemente sea parte de la aplicación. Huelga decir que esto enfrenta un inmenso problema de seguridad. Las API públicas y privadas se pueden extraer de los APK en cuestión de minutos y se pueden automatizar fácilmente.

Asumiendo que tengo algo similar a esto, ¿cómo puedo proteger la clave secreta?

public class DropboxService { private final static String APP_KEY = "jk433g34hg3"; private final static String APP_SECRET = "987dwdqwdqw90"; private final static AccessType ACCESS_TYPE = AccessType.DROPBOX; // SOME MORE CODE HERE } 

¿Cuál es, en su opinión, la mejor y más segura forma de almacenar la clave privada? Ofuscación, encriptación, ¿qué piensas?

  1. Tal como está, la aplicación comstackda contiene las cadenas clave, pero también los nombres constantes APP_KEY y APP_SECRET. Extraer claves de dicho código de auto-documentación es trivial, por ejemplo con la herramienta estándar Android dx.

  2. Puede aplicar ProGuard. Dejará las cadenas de teclas intactas, pero eliminará los nombres constantes. También cambiará el nombre de clases y métodos con nombres cortos y sin sentido, siempre que sea posible. Extraer las claves lleva un poco más de tiempo, para averiguar qué cadena sirve para qué propósito.

    Tenga en cuenta que la configuración de ProGuard no debe ser tan difícil como teme. Para empezar, solo necesita habilitar ProGuard, como se documenta en project.properties. Si hay algún problema con las bibliotecas de terceros, es posible que deba suprimir algunas advertencias y / o evitar que se ofusquen, en proguard-project.txt. Por ejemplo:

     -dontwarn com.dropbox.** -keep class com.dropbox.** { *; } 

    Este es un enfoque de fuerza bruta; puede refinar dicha configuración una vez que la aplicación procesada funcione.

  3. Puede ofuscar las cadenas manualmente en su código, por ejemplo con una encoding Base64 o preferiblemente con algo más complicado; tal vez incluso el código nativo. Un pirata informático tendrá que realizar una ingeniería inversa estática de su encoding o interceptar dinámicamente la deencoding en el lugar adecuado.

  4. Puede aplicar un ofuscador comercial, como el hermano especializado ProGuard DexGuard . También puede encriptar / ofuscar las cadenas y clases por usted. Extraer las claves requiere aún más tiempo y experiencia.

  5. Es posible que pueda ejecutar partes de su aplicación en su propio servidor. Si puedes mantener las llaves allí, están a salvo.

Al final, es una compensación económica que tiene que hacer: qué tan importantes son las claves, cuánto tiempo o software puede permitirse, qué tan sofisticados son los hackers que están interesados ​​en las claves, cuánto tiempo les gustaría tener. gastar, cuánto vale una demora antes de que se pirateen las claves, a qué escala distribuirán las claves los hackers exitosos, etc. Pequeñas piezas de información, como las claves, son más difíciles de proteger que las aplicaciones enteras. Intrínsecamente, nada en el lado del cliente es irrompible, pero ciertamente puede elevar el listón.

(Soy el desarrollador de ProGuard y DexGuard)

Pocas ideas, en mi opinión solo la primera da alguna garantía:

  1. Mantenga sus secretos en algún servidor en Internet, y cuando sea necesario solo agárrelos y use. Si el usuario está a punto de usar Dropbox, entonces nada le impide realizar una solicitud a su sitio y obtener su clave secreta.

  2. Coloque sus secretos en el código jni, agregue algún código variable para hacer sus bibliotecas más grandes y más difíciles de descomstackr. También puede dividir la cadena clave en algunas partes y mantenerlas en varios lugares.

  3. use el ofuscador, también ponga el código hash secreto y más tarde deshagalo cuando sea necesario.

  4. Coloque su clave secreta como últimos píxeles de una de sus imágenes en activos. Luego, cuando sea necesario, léalo en su código. Ofuscar su código debería ayudar a ocultar el código que lo leerá.

Si quieres echar un vistazo rápido a lo fácil que es leer tu código apk, entonces toma APKAnalyser:

http://developer.sonymobile.com/knowledge-base/tool-guides/analyse-your-apks-with-apkanalyser/

Siga 3 simples pasos para asegurar la API / clave secreta

Podemos usar Gradle para asegurar la clave API o la clave secreta.

1. gradle.properties (Propiedades del proyecto): Crear variable con la clave.

 GoolgeAPIKey = "Your API/Secret Key" 

2. build.gradle (Module: app): establece la variable en build.gradle para acceder a ella en actividad o fragmento. Agregue el código siguiente a buildTypes {}.

 buildTypes.each { it.buildConfigField 'String', 'GoogleSecAPIKEY', GoolgeAPIKey } 

3. Acceda a él en Activity / Fragment por BuildConfig de la aplicación:

 BuildConfig.GoogleSecAPIKEY 

Actualización:

La solución anterior es útil en el proyecto de código abierto para comprometer a Git. (Gracias a David Rawson y riyaz-ali por su comentario).

Según los comentarios de Matthew y Pablo Cegarra, la forma anterior no es segura y Decompiler permitirá que alguien vea BuildConfig con nuestras claves secretas.

Solución:

Podemos usar NDK para proteger claves de API. Podemos almacenar claves en la clase C / C ++ nativa y acceder a ellas en nuestras clases de Java.

Siga este blog para proteger las claves de API con NDK.

Un seguimiento sobre cómo almacenar los tokens de forma segura en Android

La clave de la aplicación secreta debe mantenerse en privado, pero algunos usuarios pueden revertir la liberación de la aplicación.

para esos tipos no se ocultará, bloqueará el código de ProGuard . Es un refactor y algunos ofuscadores pagados están insertando algunos operadores bit a bit para recuperar el jk433g34hg3 String. Puedes hacer 5 -15 min más de pirateo si trabajas 3 días 🙂

La mejor manera es mantenerlo tal como está, en mi humilde opinión.

Incluso si almacena en el lado del servidor (su PC) la clave puede ser pirateada e impresa. Tal vez esto lleva más tiempo? De todos modos, es una cuestión de unos minutos o unas pocas horas en el mejor de los casos.

Un usuario normal no descomstackrá su código.

Una posible solución es codificar los datos en su aplicación y usar la deencoding en tiempo de ejecución (cuando desee usar esa información). También recomiendo usar progaurd para dificultar la lectura y comprensión del código fuente descomstackdo de tu aplicación. por ejemplo, puse una clave codificada en la aplicación y luego usé un método de deencoding en mi aplicación para decodificar mis claves secretas en tiempo de ejecución:

 // "the real string is: "mypassword" "; //encoded 2 times with an algorithm or you can encode with other algorithms too public String getClientSecret() { return Utils.decode(Utils .decode("Ylhsd1lYTnpkMjl5WkE9PQ==")); } 

El código fuente descomstackdo de una aplicación provada es este:

  public String c() { return com.myrpoject.mypackage.gha(com.myrpoject.mypackage.gha("Ylhsd1lYTnpkMjl5WkE9PQ==")); } 

Al menos es lo suficientemente complicado para mí. esta es la forma en que lo hago cuando no tengo más remedio que almacenar un valor en mi aplicación. Por supuesto que todos sabemos que no es la mejor manera, pero funciona para mí.

 /** * @param input * @return decoded string */ public static String decode(String input) { // Receiving side String text = ""; try { byte[] data = Decoder.decode(input); text = new String(data, "UTF-8"); return text; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return "Error"; } 

Versión descomprimida:

  public static String a(String paramString) { try { str = new String(aa(paramString), "UTF-8"); return str; } catch (UnsupportedEncodingException localUnsupportedEncodingException) { while (true) { localUnsupportedEncodingException.printStackTrace(); String str = "Error"; } } } 

y puedes encontrar tantas clases de cifrado con una pequeña búsqueda en google.

Este ejemplo tiene una cantidad de aspectos diferentes. Mencionaré un par de puntos que no creo han sido explícitamente cubiertos en otra parte.

Proteger el secreto en tránsito

Lo primero que debe tener en cuenta es que para acceder a la API de Dropbox utilizando su mecanismo de autenticación de aplicaciones , debe transmitir su clave y su secreto. La conexión es HTTPS, lo que significa que no puede interceptar el tráfico sin conocer el certificado TLS. Esto es para evitar que una persona intercepte y lea los paquetes en su viaje desde el dispositivo móvil al servidor. Para usuarios normales, es una muy buena forma de garantizar la privacidad de su tráfico.

Lo que no es bueno es evitar que una persona malintencionada descargue la aplicación e inspeccione el tráfico. Es realmente fácil utilizar un proxy man-in-the-middle para todo el tráfico entrante y saliente de un dispositivo móvil. En este caso, no sería necesario desensamblar ni realizar una ingeniería inversa del código para extraer la clave de la aplicación y el secreto debido a la naturaleza de la API de Dropbox.

Podría hacer fijación, lo que verifica que el certificado TLS que recibe del servidor es el que espera. Esto agrega un control al cliente y hace que sea más difícil interceptar el tráfico. Esto haría más difícil inspeccionar el tráfico en vuelo, pero la verificación de fijación ocurre en el cliente, por lo que probablemente sería posible desactivar la prueba de fijación. Sin embargo, lo hace más difícil.

Proteger el secreto en reposo

Como primer paso, usar algo como proguard ayudará a que sea menos obvio dónde se guardan los secretos. También podría usar el NDK para almacenar la clave y el secreto y enviar solicitudes directamente, lo que reduciría en gran medida el número de personas con las habilidades adecuadas para extraer la información. Se puede lograr una mayor ofuscación al no almacenar los valores directamente en la memoria por un período de tiempo prolongado, puede encriptarlos y descifrarlos justo antes del uso, tal como lo sugiere otra respuesta.

Opciones más avanzadas

Si ahora está paranoico acerca de poner el secreto en cualquier lugar de su aplicación, y tiene tiempo y dinero para invertir en soluciones más completas, entonces podría considerar almacenar las credenciales en sus servidores (suponiendo que tenga alguno). Esto boostía la latencia de las llamadas a la API, ya que tendrá que comunicarse a través de su servidor y puede boost los costos de ejecutar su servicio debido al aumento en el rendimiento de los datos.

Luego debe decidir cómo comunicarse mejor con sus servidores para asegurarse de que estén protegidos. Esto es importante para evitar que vuelvan a aparecer los mismos problemas con su API interna. La mejor regla de oro que puedo dar es no transmitir ningún secreto directamente debido a la amenaza del hombre en el medio. En su lugar, puede firmar el tráfico utilizando su secreto y verificar la integridad de las solicitudes que lleguen a su servidor. Una forma estándar de hacer esto es calcular un HMAC del mensaje codificado en un secreto. Trabajo en una empresa que tiene un producto de seguridad que también opera en este campo y es por eso que este tipo de cosas me interesan. De hecho, aquí hay un artículo de blog de uno de mis colegas que repasa la mayor parte de esto.

¿Cuánto debería hacer?

Con cualquier consejo de seguridad como este, debe tomar una decisión de costo / beneficio acerca de qué tan difícil lo quiere hacer para que alguien intervenga. Si usted es un banco que protege a millones de clientes, su presupuesto es totalmente diferente al de una aplicación de apoyo en su tiempo libre. Es virtualmente imposible evitar que alguien rompa su seguridad, pero en la práctica pocas personas necesitan todas las comodidades y con algunas precauciones básicas puede recorrer un largo camino.

La solución más segura es mantener sus claves en un servidor y enrutar todas las solicitudes que necesitan esa clave a través de su servidor. De esta manera, la clave nunca se va de su servidor, por lo que mientras su servidor esté seguro, entonces su clave también. Por supuesto, hay un costo de rendimiento con esta solución.

Una publicación anterior, pero aún es lo suficientemente buena. Creo que esconderlo en una biblioteca .so sería genial, usando NDK y C ++ por supuesto. .so archivos se pueden ver en un editor hexadecimal, pero buena suerte descomstackr eso: P

¡Otro enfoque es no tener el secreto en el dispositivo en primer lugar! Ver las técnicas de seguridad de Mobile API (especialmente la parte 3).

Usando la tradicional tradición de indirección, comparte el secreto entre tu punto final API y un servicio de autenticación de aplicaciones. Cuando su cliente desea hacer una llamada a la API, le pide al servicio de autenticación de la aplicación que lo autentique (utilizando técnicas sólidas de atestación remota), y recibe un token de tiempo limitado (por lo general, JWT) firmado por el secreto. El token se envía con cada llamada API donde el punto final puede verificar su firma antes de actuar en la solicitud.

El secreto real nunca está presente en el dispositivo; de hecho, la aplicación nunca tiene idea de si es válida o no, activa la autenticación de las solicitudes y transmite el token resultante. Como un buen beneficio de la indirección, si alguna vez desea cambiar el secreto, puede hacerlo sin requerir que los usuarios actualicen sus aplicaciones instaladas.

Entonces, si quiere proteger su secreto, no tenerlo en su aplicación en primer lugar es una buena forma de hacerlo.

Todo lo que haga para proteger sus claves secretas no será una solución real. Si el desarrollador puede descomstackr la aplicación, no hay forma de proteger la clave, ocultar la clave es solo seguridad por oscuridad y también lo es la ofuscación del código. El problema con la seguridad de una clave secreta es que para asegurarla debe usar otra clave y esa clave también debe estar protegida. Piense en una clave escondida en una caja que está bloqueada con una llave. Coloca una caja dentro de una habitación y cierra la habitación. Te queda otra clave para asegurar. Y esa clave aún estará codificada dentro de su aplicación.

Entonces, a menos que el usuario ingrese un PIN o una frase, no hay forma de ocultar la clave. Pero para hacer eso tendrías que tener un esquema para administrar los PIN que suceden fuera de banda, lo que significa a través de un canal diferente. Ciertamente, no es práctico para asegurar claves para servicios como las API de Google.

La única forma real de mantener estos archivos privados es mantenerlos en su servidor y hacer que la aplicación envíe lo que sea al servidor y el servidor interactúa con Dropbox. De esa forma, NUNCA distribuyas tu clave privada en ningún formato.

Guarde el secreto en la firebase database y obtenga de él cuando se inicie la aplicación. Es mucho mejor que llamar a un servicio web.