Lineamientos de seguridad

Android tiene funciones de seguridad integradas que reducen significativamente la frecuencia y el impacto de los problemas de seguridad de las aplicaciones. El sistema está diseñado para que puedas compilar tus apps con permisos predeterminados del sistema y de archivos, y para que no debas tomar decisiones difíciles sobre seguridad.

Las siguientes funciones de seguridad principales te ayudan a crear apps seguras:

  • La zona de pruebas de aplicaciones para Android, que aísla los datos y la ejecución de código de tu app de otras apps
  • Un framework de aplicaciones con implementaciones sólidas de funciones de seguridad comunes, como la criptografía, los permisos y la comunicación entre procesos (IPC) segura
  • Tecnologías como la selección aleatoria del diseño del espacio de direcciones (ASLR), sin ejecución (NX), ProPolice, iop_seguro, OpenBSD, dlmalloc y calloc, y Linux mmap_min_addr para mitigar los riesgos asociados con errores comunes de administración de la memoria
  • Permisos otorgados por el usuario para restringir el acceso a funciones del sistema y datos de los usuarios
  • Permisos definidos por la aplicación para controlar los datos de cada app

Es importante que conozcas las prácticas de seguridad recomendadas de Android que se indican en esta página. Seguir estas prácticas como hábitos de programación generales te ayudará a no introducir inadvertidamente problemas de seguridad que perjudiquen a los usuarios.

Autenticación

La autenticación es un requisito previo de muchas operaciones de seguridad clave. Para controlar el acceso a elementos protegidos, como datos del usuario, funciones de la app y otros recursos, deberás agregar la autenticación a tu app para Android.

Para mejorar la experiencia de autenticación del usuario, puedes integrar tu app con Credential Manager, una biblioteca de Android Jetpack que unifica la compatibilidad de las APIs para la mayoría de los métodos de autenticación principales, incluidas llaves de acceso, contraseñas y soluciones de acceso federado como Acceder con Google.

Para mejorar mucho más la seguridad de tu app, procura agregar métodos de autenticación biométrica, como el escaneo de huella dactilar o el reconocimiento facial. Entre algunos buenos candidatos para agregar autenticación biométrica, se pueden incluir apps de administración financiera, de atención médica o de identidad.

Autofill Framework de Android puede facilitar el proceso de registro y acceso, lo que reduce las tasas de error y las dificultades de los usuarios. Esta funcionalidad se integra con administradores de contraseñas, lo que permite a los usuarios seleccionar contraseñas complejas y aleatorizadas que se pueden almacenar y recuperar de manera fácil y segura.

Almacenamiento de datos

El problema de seguridad más común para una aplicación en Android se produce si otras apps pueden acceder a los datos que guardaste en el dispositivo. Existen tres formas básicas de guardar datos en el dispositivo:

  • Almacenamiento interno
  • Almacenamiento externo
  • Proveedores de contenido

En los siguientes párrafos, se describen los problemas de seguridad asociados con cada enfoque.

Almacenamiento interno

De forma predeterminada, solo se puede acceder a los archivos que creas en el almacenamiento interno a través de tu app. Android implementa esta protección, que es suficiente para la mayoría de las aplicaciones.

Evita los modos MODE_WORLD_WRITEABLE y MODE_WORLD_READABLE obsoletos para archivos IPC. No proporcionan la capacidad de limitar el acceso a los datos para aplicaciones particulares y no proporcionan ningún control del formato de datos. Si quieres compartir tus datos con otros procesos de apps, procura usar un proveedor de contenido, que ofrece permisos de lectura y escritura a otras apps, y puede otorgar permisos de manera dinámica según el caso.

Almacenamiento externo

Los archivos creados en un almacenamiento externo, como una tarjeta SD, pueden leerse y escribirse a nivel global. Debido a que el usuario puede quitar el almacenamiento externo y cualquier aplicación puede modificarlo, almacena solo la información no sensible en el almacenamiento externo.

Cuando administres datos desde el almacenamiento externo, deberías realizar una validación de entrada, de la misma manera que con datos de cualquier fuente que no sea de confianza. No almacenes archivos ejecutables ni de clase en un almacenamiento externo antes de la carga dinámica. Si tu app recupera archivos ejecutables del almacenamiento externo, asegúrate de que los archivos estén firmados y verificados criptográficamente antes de la carga dinámica.

Proveedores de contenido

Los proveedores de contenido ofrecen un mecanismo de almacenamiento estructurado que puede limitarse a tu aplicación o exportarse para permitir el acceso de otras aplicaciones. Si no pretendes proporcionarles a otras aplicaciones el acceso a tu ContentProvider, márcala como android:exported=false en el manifiesto de la aplicación. De lo contrario, establece el atributo android:exported en true para permitir que otras apps accedan a los datos almacenados.

Al crear un ContentProvider exportado para que lo usen otras aplicaciones, puedes especificar un permiso único para lectura y escritura, o bien permisos distintos para cada actividad. Limita tus permisos a los que sean necesarios para realizar la tarea en cuestión. Recuerda que, por lo general, es más fácil agregar permisos posteriormente para exponer nuevas funcionalidades que quitarlos y perjudicar a los usuarios existentes.

Si usas un proveedor de contenido para compartir datos solo entre tus propias apps, te recomendamos que utilices el atributo android:protectionLevel configurado en el nivel de protección signature. Los permisos de firma no requieren la confirmación del usuario, por lo que proporcionan una mejor experiencia para este y un acceso más controlado a los datos del proveedor de contenido cuando las apps que acceden a los datos se firman con la misma clave.

Los proveedores de contenido también pueden proporcionar acceso más detallado cuando declaran el atributo android:grantUriPermissions y usan las marcas FLAG_GRANT_READ_URI_PERMISSION y FLAG_GRANT_WRITE_URI_PERMISSION en el objeto Intent que activa el componente. Se puede limitar mucho más el alcance de estos permisos con el elemento <grant-uri-permission>.

Cuando accedas al proveedor de contenido, usa métodos de consulta con parámetros como query, update y delete() para evitar la posible inserción de SQL desde fuentes no confiables. Ten en cuenta que el uso de métodos con parámetros no es suficiente si se compila el argumento selection a través de la concatenación de datos del usuario antes de enviarlo al método.

Debes evitar la idea errónea de que el permiso de escritura proporcionará seguridad. El permiso de escritura permite las instrucciones de SQL que posibilitan que se confirmen datos a través de cláusulas WHERE de creatividades y el análisis de los resultados. Por ejemplo, un atacante puede llegar a comprobar la presencia de un número de teléfono específico en un registro de llamadas modificando una fila solo si ese número de teléfono ya existe. Si los datos del proveedor de contenido tienen una estructura previsible, el permiso de escritura puede ser equivalente a proporcionar ambos permisos: el de lectura y el de escritura.

Permisos

Debido a que Android aísla las aplicaciones entre sí, estas deben compartir explícitamente recursos y datos. Lo hacen declarando los permisos que necesitan para capacidades adicionales no previstas por la zona de pruebas básica, incluido el acceso a funciones del dispositivo, como la cámara.

Solicitudes de permisos

Minimiza la cantidad de permisos que solicita tu app. La restricción del acceso a permisos sensibles reduce el riesgo de hacer un uso inadecuado de esos permisos de forma no intencional, mejora la aceptación del usuario y hace que tu app sea menos vulnerable ante los atacantes. Por lo general, si tu app no requiere un permiso para funcionar, no lo solicites. Consulta la guía para evaluar si tu app debe declarar permisos.

Si es posible, diseña tu aplicación de manera que no requiera permisos. Por ejemplo, en lugar de solicitar acceso a información del dispositivo para crear un identificador único, crea un UUID para tu aplicación (obtén más información en la sección sobre los datos del usuario). Como alternativa, en lugar de usar almacenamiento externo (que requiere permiso), puedes guardar datos en el almacenamiento interno.

Además de solicitar permisos, la aplicación puede usar el elemento <permission> para proteger la IPC que es sensible a la seguridad y está expuesta a otras aplicaciones, como ContentProvider. En general, recomendamos usar controles de acceso que no sean permisos confirmados por los usuarios cuando sea posible, ya que pueden resultar confusos. Por ejemplo, considera usar el nivel de protección de firma en permisos para comunicación de IPC entre aplicaciones ofrecidas por un mismo desarrollador.

No divulgues datos protegidos por permisos. Esto ocurre cuando tu app expone, a través de IPC, datos que están disponibles únicamente porque tu app tiene permiso para acceder a ellos. Es posible que los clientes de la interfaz IPC de tu app no tengan ese mismo permiso de acceso a los datos. Puedes encontrar más información sobre la frecuencia y los posibles efectos de este problema en el informe de investigación Redelegación de permisos: ataques y defensas, publicado en USENIX.

Definiciones de permisos

Define el conjunto más pequeño de permisos que cumpla con tus requisitos de seguridad. Crear un permiso nuevo es relativamente poco común para la mayoría de las aplicaciones, ya que los permisos definidos por el sistema abarcan muchas situaciones. Cuando corresponda, realiza comprobaciones de acceso a través de los permisos existentes.

Si necesitas un permiso nuevo, considera la posibilidad de realizar la tarea con un nivel de protección de firma. Los permisos de firma son transparentes para el usuario y solo permiten el acceso de las aplicaciones firmadas por el mismo desarrollador de la aplicación que realiza la comprobación de permisos.

Si aún necesitas crear un permiso nuevo, decláralo en el manifiesto de la app con el elemento <permission>. Las apps que usan el nuevo permiso pueden hacer referencia a él agregando un elemento <uses-permission> en sus archivos de manifiesto. También puedes agregar permisos dinámicamente con el método addPermission().

Si creas un permiso con el nivel de protección peligroso, debes tener en cuenta las siguientes complejidades:

  • El permiso debe tener una cadena que informe a los usuarios de forma concisa la decisión de seguridad que deberán tomar.
  • La cadena del permiso debe estar localizada para muchos idiomas diferentes.
  • Los usuarios pueden optar por no instalar una aplicación porque un permiso les resulta confuso o lo perciben como riesgoso.
  • Las aplicaciones pueden solicitar el permiso cuando no se ha instalado el creador del permiso.

Estas opciones te presentan un desafío no técnico como desarrollador y también confunden a los usuarios, motivo por el cual desalentamos el uso del nivel de permiso peligroso.

Redes

Por naturaleza, las transacciones de red presentan un riesgo para la seguridad, ya que implican la transmisión de datos del usuario que quizá sean privados. Las personas son cada vez más conscientes de los problemas de seguridad de un dispositivo móvil, especialmente cuando el dispositivo realiza transacciones en red. Por eso, es muy importante que tu app implemente todas las prácticas recomendadas para proteger la seguridad de los datos del usuario en todo momento.

Herramientas de redes de IP

Realizar operaciones en red en Android no es muy diferente de hacerlo en otros entornos de Linux. La consideración clave es asegurarte de usar los protocolos correctos para datos sensibles, como HttpsURLConnection para tráfico web seguro. Usa HTTPS en lugar de HTTP siempre que sea compatible con HTTPS en el servidor, ya que los dispositivos móviles con frecuencia se conectan a redes no seguras, como hotspots de Wi-Fi públicos.

La comunicación a nivel del socket autenticada y encriptada puede implementarse fácilmente a través de la clase SSLSocket. Dada la frecuencia con la que los dispositivos Android se conectan a redes inalámbricas no seguras a través de Wi-Fi, se recomienda usar redes seguras para todas las aplicaciones que se comunican a través de la red.

Algunas aplicaciones usan puertos de red localhost para controlar IPC sensibles. No uses este enfoque, ya que otras aplicaciones del dispositivo pueden acceder a estas interfaces. En su lugar, usa un mecanismo de IPC de Android que permita la autenticación, como un Service. La vinculación a la dirección IP no específica INADDR_ANY es peor que usar un bucle invertido, ya que permite que la aplicación reciba solicitudes desde cualquier dirección IP.

Asegúrate de no confiar en datos que se descarguen de HTTP o algún otro protocolo no seguro. Esto incluye la validación de entrada en WebView y todas las respuestas a intents enviados por HTTP.

Redes de telefonía

El protocolo de servicio de mensajes cortos (SMS) se diseñó principalmente para la comunicación entre usuarios y no es adecuado para apps que desean transferir datos. Debido a las limitaciones de SMS, te recomendamos usar Firebase Cloud Messaging (FCM) y redes IP para enviar mensajes de datos de un servidor web a tu app en un dispositivo del usuario.

Ten en cuenta que los SMS no cuentan con encriptación ni autenticación sólida en la red ni en el dispositivo. En particular, cualquier receptor de SMS debe prever que un usuario malicioso puede haber enviado el SMS a tu aplicación. No confíes en datos de SMS sin autenticar para realizar comandos sensibles. Además, ten en cuenta que el protocolo SMS puede estar sujeto a falsificación de identidad o interceptación en la red. En el dispositivo que ejecuta Android, los mensajes SMS se transmiten como intents de transmisión, de modo que otras apps que tengan el permiso READ_SMS podrán leerlos o capturarlos.

Validación de entradas

La validación de entrada insuficiente es uno de los problemas de seguridad más comunes que afectan a las aplicaciones, independientemente de la plataforma en la que se ejecuten. Android tiene medidas de prevención a nivel de la plataforma que reducen la exposición de las aplicaciones a problemas de validación de entrada, y te recomendamos que uses esas funciones siempre que sea posible. Además, recomendamos usar lenguajes de tipo seguro para reducir la probabilidad de problemas de validación de entrada.

Si usas código nativo, los datos leídos de archivos recibidos a través de la red o de una IPC pueden introducir un problema de seguridad. Los problemas más comunes son el desbordamiento del búfer, el uso después de la liberación y los errores por un paso. Android proporciona una serie de tecnologías, como ASLR y Prevención de ejecución de datos (DEP), que reducen la posibilidad de explotar estos errores, pero no resuelven el problema subyacente. Puedes administrar punteros y búferes para evitar estas vulnerabilidades.

Los lenguajes dinámicos basados en cadenas, como JavaScript y SQL, también están expuestos a problemas de validación de entrada debido a los caracteres de escape y la inserción de secuencias de comandos.

Si usas datos en las consultas que se envían a una base de datos SQL o un proveedor de contenido, la inyección de SQL puede ser un problema. La mejor medida de seguridad usar consultas con parámetros, como se explica en la sección sobre proveedores de contenido. Limitar los permisos a solo lectura o solo escritura también puede reducir las posibilidades de daños relacionados con la inyección de SQL.

Si no puedes usar las funciones de seguridad que se analizan en esta sección, asegúrate de usar formatos de datos bien estructurados y verifica que los datos cumplan con el formato esperado. Si bien bloquear caracteres específicos o reemplazar caracteres pueden ser estrategias eficaces, estas técnicas son propensas a errores en la práctica, por lo que te recomendamos evitarlas siempre que sea posible.

Datos del usuario

El mejor enfoque para la seguridad de los datos del usuario es minimizar el uso de APIs que accedan a información sensible o personal. Si tienes acceso a los datos del usuario, evita almacenarlos o transmitirlos si puedes. Considera la posibilidad de que la lógica de la aplicación se pueda implementar con un hash o una forma irreversible de los datos. Por ejemplo, tu app podría usar el hash de una dirección de correo electrónico como clave primaria para evitar transmitir o almacenar la dirección de correo electrónico. Así se reducen las posibilidades de exponer los datos de forma accidental y también de que los atacantes intenten vulnerar tu app.

Autentica a tu usuario siempre que se requiera acceso a datos privados y usa métodos de autenticación modernos, como llaves de acceso y Credential Manager. Si tu app necesita acceder a información personal, ten en cuenta que algunas jurisdicciones podrían exigir que proporciones una política de privacidad en la que expliques el uso y el almacenamiento de esos datos. Sigue la práctica recomendada de seguridad de minimizar el acceso a datos del usuario para simplificar el cumplimiento.

También debes considerar la posibilidad de que tu aplicación, de forma accidental, deje expuesta información personal a otros, por ejemplo, componentes externos para publicidad o servicios externos que tu aplicación use. Si no sabes por qué un componente o servicio requiere información personal, no lo proporciones. En general, reducir el acceso a información personal por parte de tu aplicación reduce las probabilidades de que ocurran problemas en esta área.

Si tu app requiere acceso a datos sensibles, evalúa si necesitas transmitirlos a un servidor o si puedes ejecutar la operación en el cliente. Considera ejecutar en el cliente cualquier código que use datos sensibles para evitar la transmisión de datos del usuario. Además, asegúrate de no exponer datos del usuario de forma involuntaria a otras aplicaciones del dispositivo a través de IPC demasiado permisivas, archivos que admiten escritura pública o sockets de red. Las IPC sumamente permisivas son un caso especial de datos protegidos con permisos, que se analizan en la sección Solicitudes de permisos.

Si se requiere un identificador único global (GUID), crea un número grande y único, y guárdalo. No uses identificadores de teléfono, como el número de teléfono o la IMEI, que puedan estar asociados con información personal. Este tema se analiza con más detalle en la página de prácticas recomendadas para identificadores únicos.

Ten cuidado cuando realices operaciones de escritura en registros del dispositivo. En Android, los registros son un recurso compartido y están disponibles para una app con el permiso READ_LOGS. Si bien los datos del registro del teléfono son temporales y se borran durante el reinicio, el registro inadecuado de información de usuarios podría filtrar de forma inadvertida datos de estos a otras aplicaciones. Además de no registrar PII, limita el uso de registros en las apps de producción. Para implementarlo fácilmente, usa marcas de depuración y clases personalizadas de Log con niveles de registro de configuración sencilla.

WebView

Debido a que WebView consume contenido web que puede incluir HTML y JavaScript, el uso inadecuado puede generar problemas comunes de seguridad web, como secuencias de comandos entre sitios (inserción de JavaScript). En Android, se incluyen varios mecanismos para reducir el alcance de estos posibles problemas limitando la capacidad de WebView a la funcionalidad mínima requerida por la aplicación.

Si tu aplicación no usa directamente JavaScript en un WebView, no llames a setJavaScriptEnabled. En algunos códigos de muestra, se usa este método. Si reutilizas código de muestra que lo usa en una aplicación de productos, quita esa llamada de método si no es necesaria. De forma predeterminada, WebView no ejecuta JavaScript, por lo que no es posible realizar secuencias de comandos entre sitios.

Usa addJavaScriptInterface() con especial cuidado, ya que permite que JavaScript invoque operaciones normalmente reservadas para aplicaciones para Android. Si la usas, addJavaScriptInterface() solo se debe exponer a páginas web con entradas confiables. Si se permite la entrada de información no confiable, es posible que JavaScript no sea confiable para invocar los métodos de Android dentro de la app. En general, se recomienda exponer addJavaScriptInterface() solo a JavaScript incluido en el APK de la aplicación.

Si tu aplicación accede a datos sensibles con un WebView, considera usar el método clearCache() para borrar los archivos almacenados de forma local. También puedes usar encabezados del servidor, como no-store, para indicar que una aplicación no debe almacenar en caché un contenido determinado.

Los dispositivos con plataformas anteriores a Android 4.4 (nivel de API 19) usan una versión de webkit que presenta algunos problemas de seguridad. Como solución alternativa, si la app se ejecuta en estos dispositivos, debe confirmar que los objetos WebView solo muestren contenido de confianza. Para asegurarte de que la app no esté expuesta a posibles vulnerabilidades en SSL, usa el objeto de seguridad Provider con capacidad de actualización según se describe en Cómo actualizar tu proveedor de seguridad para protegerte contra exploits de SSL. Si tu aplicación debe renderizar contenido de la Web abierta, considera proporcionar tu propio procesador para poder mantenerlo actualizado con los últimos parches de seguridad.

Solicitudes de credenciales

Las solicitudes de credenciales son un vector de ataque. A continuación, se incluyen algunas sugerencias que te ayudarán a hacer que las solicitudes de credenciales en tus apps para Android sean más seguras.

Minimiza la exposición a credenciales

  • Evita solicitudes de credenciales innecesarias. Para que los ataques de suplantación de identidad (phishing) sean más perceptibles y su éxito sea menos probable, minimiza la frecuencia con la que solicitas credenciales al usuario. En su lugar, usa un token de autorización y actualízalo. Solicita solo la cantidad mínima de información de credenciales necesaria para la autenticación y la autorización.
  • Almacena las credenciales de forma segura. Usa Credential Manager para habilitar la autenticación sin contraseña con llaves de acceso o para implementar el acceso federado usando esquemas como Acceder con Google. Si debes usar la autenticación con contraseña tradicional, no almacenes IDs de usuario ni contraseñas en el dispositivo. Como alternativa, realiza la autenticación inicial con el nombre de usuario y la contraseña que se hayan proporcionado, y luego usa un token de autorización de corta duración específico para el servicio.
  • Limita el alcance de los permisos. No solicites permisos amplios para una tarea que solo requiere un alcance más limitado.
  • Limita los tokens de acceso. Usa operaciones con llamadas a la API y tokens de corta duración.
  • Limita las tasas de autenticación. Las solicitudes de autenticación o autorización rápidas y sucesivas pueden ser una señal de un ataque de fuerza bruta. Limita estas tasas a una frecuencia razonable y, al mismo tiempo, permite una experiencia funcional y fácil de usar en la app.

Usa autenticación segura

  • Implementa llaves de acceso. Habilita las llaves de acceso como una actualización de contraseñas más segura y fácil de usar.
  • Agrega datos biométricos. Ofrece la capacidad de usar autenticación biométrica, como la huella dactilar o el reconocimiento facial, para mayor seguridad.
  • Usa proveedores de identidad federada. Credential Manager admite proveedores de autenticación federada, como Acceder con Google.
  • Encripta la comunicación. Usa HTTPS y tecnologías similares para garantizar los datos que envía tu app a través de una red estén protegidos.

Administra las cuentas de forma segura

  • Conéctate a servicios a los que pueden acceder varias aplicaciones usando AccountManager. Usa la clase AccountManager para invocar un servicio basado en la nube y no almacenes contraseñas en el dispositivo.
  • Después de usar AccountManager para recuperar una Account, usa CREATOR antes de pasar credenciales para evitar pasar credenciales a la aplicación incorrecta sin darte cuenta.
  • Si solo las aplicaciones que creas usan las credenciales, puedes verificar la aplicación que accede a AccountManager a través de checkSignatures. Como alternativa, si solo una aplicación usa la credencial, puedes usar un KeyStore para el almacenamiento.

Mantente alerta

  • Mantén tu código actualizado. Asegúrate de actualizar el código fuente, incluidas las bibliotecas y las dependencias externas, para protegerte de las vulnerabilidades más recientes.
  • Supervisa la actividad sospechosa. Busca posibles usos inadecuados, como patrones de uso inadecuado de la autorización.
  • Audita tu código. Realiza verificaciones de seguridad frecuentes en tu base de código para detectar posibles problemas con la solicitud de credenciales.

Administración de claves de API

Las claves de API son un componente fundamental de muchas apps para Android, ya que les permite acceder a servicios externos y realizar funciones esenciales, como conectarse a servicios de mapas, autenticación y servicios meteorológicos. Sin embargo, exponer estas claves sensibles puede tener consecuencias graves, como violaciones de la seguridad de los datos, acceso no autorizado y pérdidas financieras. Para evitar estas situaciones, los desarrolladores deben implementar estrategias seguras para controlar las claves de API durante todo el proceso de desarrollo.

Para proteger los servicios del uso inadecuado, las claves de API se deben proteger cuidadosamente. Para proteger una conexión entre la app y un servicio que usa una clave de API, debes proteger el acceso a la API. Cuando se compila tu app y su código fuente incluye claves de API, es posible que un atacante descompile la app y encuentre estos recursos.

Esta sección está dirigida a dos grupos de desarrolladores de Android: aquellos que trabajan con equipos de infraestructura en su canalización de entrega continua y aquellos que implementan apps independientes en Play Store. En esta sección, se describen las prácticas recomendadas para controlar las claves de API, de modo que tu app pueda comunicarse con los servicios de forma segura.

Generación y almacenamiento

Los desarrolladores deben considerar el almacenamiento de claves de API como un componente fundamental de la protección de datos y la privacidad del usuario a través de un enfoque de defensa en profundidad.

Almacenamiento de claves seguro

Para una seguridad de administración de claves óptima, usa el almacén de claves de Android y encripta las claves almacenadas con una herramienta sólida, como la biblioteca de Jetpack security-crypto o la biblioteca Tink Java.

En el siguiente ejemplo, se usa la biblioteca de criptografía de seguridad de Jetpack para crear preferencias compartidas encriptadas.

Kotlin

val masterKey = MasterKey.Builder(context)
  .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
  .build()

val encryptedSharedPreferences = EncryptedSharedPreferences.create(
  context,
  "secret_shared_prefs",
  masterKey,
  EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
  EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

// use the shared preferences and editor as you normally would
encryptedSharedPreferences.edit()

Java

MasterKey masterKey = new MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build();

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
    context,
    "secret_shared_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

// use the shared preferences and editor as you normally would
SharedPreferences.Editor editor = sharedPreferences.edit();

Exclusión del control de fuente

Nunca confirmes las claves de API en tu repositorio de código fuente. Agregar claves de API al código fuente corre el riesgo de la exposición de claves en repositorios públicos, ejemplos de código compartido y archivos compartidos accidentalmente. En su lugar, usa complementos de Gradle, como secrets-gradle-plugin, para trabajar con claves de API en tu proyecto.

Claves específicas del entorno

Si es posible, usa entornos de desarrollo, prueba y producción de claves de API independientes. Usa claves específicas para aislar cada entorno, lo que reduce el riesgo de exponer los datos de producción y te permite inhabilitar las claves comprometidas sin afectar el entorno de producción.

Control de uso y acceso

Las prácticas de clave de API segura son esenciales para proteger tu API y a los usuarios. Sigue estos pasos para preparar tus claves para lograr una seguridad óptima:

  • Genera claves únicas para cada app: Usa claves de API independientes para cada app para identificar y aislar el acceso comprometido.
  • Implementa restricciones de IP: Si es posible, limita el uso de la clave de API a direcciones o rangos de IP específicos.
  • Limita el uso de claves de apps para dispositivos móviles: Limita el uso de la clave de API a apps para dispositivos móviles específicas. Para ello, vincúlalas con la clave o usa certificados de app.
  • Registrar y supervisar actividades sospechosas: Implementa mecanismos de supervisión y registro de uso de la API para detectar actividad sospechosa y evitar posibles abusos.

Nota: Tu servicio debe proporcionar funciones para restringir claves a un paquete o una plataforma en particular. Por ejemplo, la API de Google Maps limita el acceso a la clave por nombre de paquete y clave de firma.

OAuth 2.0 proporciona un framework para autorizar el acceso a los recursos. Define estándares sobre la forma en que los clientes y los servidores deben interactuar, y permite una autorización segura. Puedes usar OAuth 2.0 para restringir el uso de la clave de API a clientes específicos y definir el permiso de acceso, de modo que cada clave de API solo tenga el nivel de acceso mínimo requerido para el propósito previsto.

Rotación y vencimiento de claves

Para reducir el riesgo de acceso no autorizado a través de vulnerabilidades de API no descubiertas, es importante rotar las claves de API con regularidad. El estándar ISO 27001 define un marco de trabajo de cumplimiento sobre la frecuencia con la que se debe realizar la rotación de claves. Para la mayoría de los casos, un período de rotación de claves entre 90 días y 6 meses debería ser adecuado. La implementación de un sistema sólido de administración de claves puede ayudarte a optimizar estos procesos, lo que mejora la eficiencia de tus necesidades de rotación y vencimiento de claves.

Prácticas recomendadas generales

  • Usa SSL/HTTPS: Usa siempre la comunicación HTTPS para encriptar tus solicitudes a la API.
  • Fijación de certificados: Para obtener una capa adicional de seguridad, puedes implementar la fijación de certificados para verificar qué certificados se consideran válidos.
  • Valida y limpia las entradas del usuario: Valida y limpia las entradas del usuario para evitar ataques de inyección que podrían exponer las claves de API.
  • Sigue las prácticas recomendadas de seguridad: Implementa las prácticas recomendadas generales de seguridad en tu proceso de desarrollo, incluidas las técnicas de codificación segura, las revisiones de código y el análisis de vulnerabilidades.
  • Mantente al tanto: Mantente al tanto de las amenazas a la seguridad más recientes y las prácticas recomendadas para la administración de claves de API.
  • SDKs actualizados: Asegúrate de que los SDKs y las bibliotecas estén actualizados a la versión más reciente.

Criptografía

Además de proporcionar aislamiento de datos, admitir la encriptación en todo el sistema de archivos y proporcionar canales de comunicación seguros, Android proporciona una amplia variedad de algoritmos para proteger los datos a través de criptografía.

Conoce qué proveedores de seguridad de arquitectura de criptografía de Java (JCA) usa tu software. Intenta usar el nivel más alto de implementación de marco de trabajo preexistente que pueda admitir tu caso práctico. Si corresponde, usa los proveedores que ofrece Google en el orden que Google especifica.

Si necesitas recuperar un archivo de forma segura desde una ubicación de red conocida, un URI HTTPS puede ser adecuado y suficiente, y no requiere conocimientos de criptografía. Si necesitas un túnel seguro, considera usar HttpsURLConnection o SSLSocket en lugar de escribir tu propio protocolo. Si usas SSLSocket, ten en cuenta que no realiza la verificación del nombre de host. Consulta las Advertencias sobre el uso directo de SSLSocket.

Si necesitas implementar tu propio protocolo, no deberías implementar tus propios algoritmos criptográficos. Usa algoritmos criptográficos existentes, como las implementaciones de AES y RSA que se proporcionan en la clase Cipher. Además, sigue estas prácticas recomendadas:

  • Usa AES de 256 bits con fines comerciales. (Si no está disponible, usa AES de 128 bits).
  • Usa claves públicas de 224 bits o 256 bits para criptografía de curva elíptica.
  • Conoce cuándo utilizar los modos de bloqueo CBC, CTR o GCM.
  • Evita volver a utilizar un vector de inicialización o contador en el modo CTR. Asegúrate de que sean criptográficamente aleatorios.
  • Cuando uses encriptación, implementa la integridad con el modo CBC o CTR, a través de una de las siguientes funciones:
    • HMAC-SHA1
    • HMAC-SHA-256
    • HMAC-SHA-512
    • Modo GCM

Usa un generador de números aleatorios seguro, SecureRandom, para inicializar cualquier clave criptográfica generada por KeyGenerator. El uso de una clave que no se cree con un generador de números aleatorios seguro debilitará notablemente la protección del algoritmo y podría permitir ataques sin conexión.

Si necesitas almacenar una clave para volver a usarla, usa un mecanismo, como KeyStore, que proporciona almacenamiento y recuperación de claves criptográficas a largo plazo.

Comunicación entre procesos

Algunas apps intentan implementar IPC con métodos tradicionales de Linux, como sockets de red y archivos compartidos. Sin embargo, te recomendamos que uses las funciones del sistema Android para IPC, como Intent, Binder o Messenger con Service y BroadcastReceiver. Los mecanismos de IPC de Android te permiten verificar la identidad de la aplicación que se conecta a tu IPC y establecer una política de seguridad para cada mecanismo de IPC.

Muchos de los elementos de seguridad se comparten entre mecanismos de IPC. Si tu mecanismo de IPC no está diseñado para que lo usen otras aplicaciones, configura el atributo android:exported como false en el elemento del manifiesto del componente, por ejemplo, para el elemento <service>. Esto resulta útil para aplicaciones que constan de varios procesos dentro del mismo UID o si decides en un momento avanzado del desarrollo que no deseas exponer funcionalidad como IPC, pero tampoco deseas volver a escribir el código.

Si otras aplicaciones pueden acceder a tu IPC, aplica una política de seguridad con el elemento <permission>. Si la IPC está entre apps que son tuyas y están firmadas con la misma clave, usa un permiso de nivel signature-level en android:protectionLevel.

Intents

Para actividades y receptores de emisión, los intents son el mecanismo preferido para la IPC asíncrona en Android. Según los requisitos de la aplicación, podrías usar sendBroadcast, sendOrderedBroadcast o un intent explícito para un componente específico de la aplicación. Para fines de seguridad, se prefieren los intents explícitos.

Precaución: Si utilizas un intent para la vinculación con un **Service**, usa un intent explícito para proteger tu app. El uso de un intent explícito para iniciar un servicio es un riesgo de seguridad porque no puedes estar seguro de qué servicio responderá al intent, y el usuario no puede ver qué servicio se inicia. A partir de Android 5.0 (API nivel 21), el sistema lanza una excepción si llamas a **bindService()** con un intent implícito.

Ten en cuenta que las transmisiones solicitadas pueden ser consumidas por un destinatario, por lo que no se deben entregar a todas las aplicaciones. Si envías un intent que se deba entregar a un receptor específico, debes usar un intent explícito que declare el receptor por su nombre.

Los emisores de un intent pueden verificar que el receptor tenga una autorización que especifique un permiso que no sea null con la llamada al método. Solo las aplicaciones con ese permiso recibirán el intent. Si los datos de un intent de transmisión pueden ser sensibles, deberías considerar aplicar un permiso para asegurarte de que las aplicaciones maliciosas no puedan registrarse para recibir esos mensajes sin los permisos pertinentes. En esas circunstancias, también puedes considerar invocar al receptor directamente en lugar de generar una transmisión.

Nota: Los filtros de intent no son funciones de seguridad. Los componentes pueden invocarse con intents explícitos y es posible que tengan datos que coincidan con el filtro de intents. Debes realizar una validación de entrada en tu receptor de intents para confirmar que tenga el formato correcto para el receptor, el servicio o la actividad invocados.

Servicios

Se suele usar un Service para brindar funcionalidad de uso a otras aplicaciones. Cada clase de servicio debe tener una declaración de <service> correspondiente en el archivo de manifiesto.

De forma predeterminada, los servicios no se exportan y no pueden ser invocados por ninguna otra aplicación. No obstante, si agregas filtros de intents en la declaración del servicio, se realiza la exportación de forma predeterminada. La mejor opción será declarar explícitamente el atributo android:exported para asegurarte de que se comporte como deseas. Los servicios también pueden protegerse usando el atributo android:permission. De esta manera, otras aplicaciones necesitan declarar un elemento <uses-permission> correspondiente en su propio manifiesto para poder vincularse al servicio, iniciarlo o detenerlo.

Nota: Si la app se orienta a Android 5.0 (API nivel 21) o versiones posteriores, deberías usar **JobScheduler** para ejecutar servicios en segundo plano.

Un servicio puede proteger con permisos individuales las llamadas IPC individuales que se realizan en él. Para ello, se debe llamar a checkCallingPermission() antes de ejecutar la implementación de la llamada. Generalmente, recomendamos usar permisos declarativos en el manifiesto debido a que están menos expuestos a la omisión.

Precaución: No confundas los permisos de cliente y los de servidor. Asegúrate de que la app a la que se llama tenga los permisos adecuados y verifica que hayas otorgado los mismos permisos a la app que realiza la llamada.

Interfaces de Binder y Messenger

El uso de Binder o Messenger es el mecanismo recomendado para IPC de estilo RPC en Android. Proporcionan interfaces bien definidas que habilitan la autenticación mutua de los extremos, si es necesario.

Te recomendamos que diseñes las interfaces de tu app de una manera que no requiera verificaciones de permisos específicos de interfaces. Los objetos Binder y Messenger no se declaran dentro del manifiesto de la aplicación y, por lo tanto, no puedes aplicarles permisos declarativos directamente. Suelen heredar los permisos declarados en el manifiesto de la aplicación para el Service o la Activity en los que se implementan. Si creas una interfaz que requiere autenticación o controles de acceso, debes agregar explícitamente esos controles como código en la interfaz Binder o Messenger.

Si proporcionas una interfaz que requiere controles de acceso, usa checkCallingPermission() para verificar si el emisor tiene un permiso obligatorio. Esto tiene particular importancia antes de acceder a un servicio en nombre del emisor, ya que se pasa la identidad de tu aplicación a otras interfaces. Si invocas una interfaz proporcionada por un Service, la invocación de bindService() puede fallar si no tienes permiso para acceder al servicio determinado. Si necesitas permitir que un proceso externo interactúe con la app, pero no tiene los permisos necesarios para hacerlo, puedes usar el método clearCallingIdentity(). Este método realiza la llamada a la interfaz de tu app como si esta estuviera realizando la llamada en sí, en lugar de la llamada del emisor externo. Puedes restablecer los permisos del emisor más adelante con el método restoreCallingIdentity().

Para obtener más información sobre cómo realizar IPC con un servicio, consulta Servicios vinculados.

Receptores de emisiones

Un BroadcastReceiver controla solicitudes asíncronas iniciadas por un Intent.

De forma predeterminada, los receptores se exportan y pueden invocarse a través de cualquier otra aplicación. Si tu BroadcastReceiver está pensado para que otras aplicaciones puedan usarlo, te recomendamos que apliques permisos de seguridad a los receptores a través del elemento <receiver> en el manifiesto de la aplicación. Esto evita que las aplicaciones sin los permisos adecuados envíen un intent a BroadcastReceiver.

Seguridad con código cargado de forma dinámica

No recomendamos cargar código desde fuera del APK de tu aplicación. Hacerlo incrementa considerablemente las probabilidades de que se comprometa la aplicación por la inserción o manipulación de código. También agrega complejidad a la administración de versiones y las pruebas de aplicaciones, y puede imposibilitar la verificación del comportamiento de una aplicación, por lo que podría estar prohibida en algunos entornos.

Si tu aplicación carga código de forma dinámica, lo más importante que debes tener en cuenta es que ese código se ejecuta con los mismos permisos de seguridad que el APK de la aplicación. El usuario toma la decisión de instalar tu aplicación en función de tu identidad y espera que le proporciones todo el código necesario en la aplicación, incluido el código de carga dinámica.

Muchas aplicaciones intentan cargar código desde ubicaciones no seguras, por ejemplo, código descargado de la red a través de protocolos no cifrados o desde ubicaciones que admiten escritura pública, como medios de almacenamiento externo. Esas ubicaciones podrían permitir que alguien en la red modifique el contenido en tránsito o que otra aplicación del dispositivo del usuario modifique el contenido en el dispositivo. Por otro lado, otras aplicaciones no pueden modificar los módulos incluidos directamente en tu APK. Esto es así, independientemente de si el código es una biblioteca nativa o una clase cargada con DexClassLoader.

Cómo implementar seguridad en una máquina virtual

Dalvik es la máquina virtual (MV) en tiempo de ejecución de Android. Se compiló específicamente para Android, pero muchas de las inquietudes en relación con el código seguro en otras máquinas virtuales también se aplican para Android. En general, no tienes que preocuparte por los problemas de seguridad relacionados con la máquina virtual. Tu aplicación se ejecuta en un entorno de zona de pruebas seguro, por lo que otros procesos del sistema no pueden acceder a tu código ni a tus datos privados.

Si quieres obtener más información acerca de la seguridad de máquinas virtuales, familiarízate con la bibliografía existente sobre el tema. Los siguientes son dos de los recursos más importantes:

El enfoque principal de este documento son las áreas específicas de Android o diferentes de otros entornos de máquina virtual. Para los desarrolladores con experiencia en programación de VM en otros entornos, hay dos aspectos amplios que podrían ser diferentes respecto de la escritura de apps para Android.

  • Algunas máquinas virtuales, como el tiempo de ejecución .net o JVM, actúan como barreras de seguridad que aíslan el código de las capacidades del sistema operativo subyacente. En Android, la máquina virtual Dalvik no es una barrera de seguridad; la zona de pruebas de aplicaciones se implementa a nivel del SO. Por ello, Dalvik puede interoperar con código nativo en la misma aplicación sin limitaciones de seguridad.
  • Dada la capacidad limitada de almacenamiento en los dispositivos móviles, es común que los desarrolladores deseen compilar aplicaciones modulares y usar carga dinámica de código. Si haces esto, considera la fuente de la que obtuviste la lógica de tu aplicación y dónde la almacenas a nivel local. No uses la carga dinámica de clases desde fuentes sin verificar, como fuentes de red no seguras o medios de almacenamiento externo, ya que el código podría modificarse para incluir un comportamiento malicioso.

Cómo implementar seguridad en código nativo

En general, recomendamos usar el SDK de Android para el desarrollo de aplicaciones, en lugar de usar código nativo con el NDK de Android. Las aplicaciones compiladas con código nativo son más complejas, menos portátiles y más propensas a incluir errores comunes de daños en la memoria, como los desbordamientos del búfer.

Android se compila con el kernel de Linux, por lo que conocer las prácticas recomendadas sobre seguridad en el desarrollo de Linux resulta útil si vas a utilizar código nativo. Las prácticas de seguridad de Linux no se contemplan en este documento, pero uno de los recursos más populares es Secure Programming HOWTO - Creating Secure Software.

Una diferencia importante entre Android y la mayoría de los entornos Linux es la zona de pruebas de aplicaciones. En Android, todas las aplicaciones se ejecutan en la zona de pruebas de aplicaciones, incluidas las que se escriben con código nativo. Para los desarrolladores familiarizados con Linux, una buena idea es saber que cada aplicación recibe un identificador de usuario único (UID) con permisos muy limitados. Este tema se explica más detalladamente en Descripción general de seguridad de Android, y debes familiarizarte con los permisos de aplicaciones aun cuando uses código nativo.