Sugerencias 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 con el objetivo de 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 compilar 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 los de otras.
  • Un marco de trabajo de aplicaciones con implementaciones sólidas de funciones de seguridad comunes, como la criptografía, los permisos y la IPC segura.
  • Tecnologías como ASLR, NX, ProPolice, safe_iop, OpenBSD dlmalloc, OpenBSD calloc y Linux mmap_min_addr para mitigar riesgos asociados con errores comunes de administración de memoria.
  • Un sistema de archivos encriptado que se puede habilitar para proteger los datos en dispositivos perdidos o robados.
  • 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 recomendadas de Android que se explican en este documento. Seguir estas prácticas como hábitos de codificación generales reduce las probabilidades de introducir de forma inadvertida problemas de seguridad que perjudiquen a tus usuarios.

Cómo almacenar 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. Las siguientes son las 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.

Cómo usar el almacenamiento interno

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

En general, evita los modos MODE_WORLD_WRITEABLE o MODE_WORLD_READABLE para los archivos de IPC, ya que no proporcionan la capacidad de limitar el acceso a datos para aplicaciones específicas ni proporcionan control del formato de datos. Si quieres compartir los datos con otros procesos de apps, considera 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.

Para brindar protección adicional a los datos sensibles, puedes encriptar los archivos locales con la biblioteca de seguridad. Esta medida puede proteger un dispositivo perdido sin encriptación del sistema de archivos.

Cómo usar el 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, no debes almacenar información sensible en él.

Para leer y escribir archivos en el almacenamiento externo de una manera más segura, te recomendamos usar la biblioteca de seguridad, que proporciona la clase EncryptedFile.

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 deberías almacenar archivos ejecutables ni de clase en un medio de almacenamiento externo antes de realizar una carga dinámica. Si tu app obtiene archivos ejecutables de medios de almacenamiento externo, los archivos deberían estar firmados y verificados criptográficamente antes de la carga dinámica.

Cómo usar 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 brindarles acceso a otras aplicaciones al ContentProvider, márcalas 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. Deberías limitar tus permisos a los necesarios para lograr la tarea prevista. 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 solamente entre tus propias apps, es preferible utilizar 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 aún 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 mediante 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 mediante 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.

Cómo usar permisos

Debido a que Android aísla las aplicaciones entre sí, estas deben compartir explícitamente recursos y datos. Lo hacen mediante la declaración de 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.

Cómo solicitar permisos

Deberías minimizar la cantidad de permisos que tu app solicita. La restricción de acceso a permisos sensibles reduce el riesgo de no utilizar correctamente esos permisos, mejora la aceptación del usuario y hace que tu app sea menos vulnerable ante los atacantes. En general, si tu app no requiere un determinado permiso para funcionar, no lo solicites. Si hay una función imprescindible para ejecutar la app, indícala con un elemento <uses-feature> en el archivo de manifiesto.

Es preferible diseñar la aplicación de modo que no requiera permisos. Por ejemplo, en lugar de solicitar acceso a información del dispositivo para crear un identificador único, crea un GUID para tu aplicación (consulta la sección Manejo de 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 permitas que se fuguen datos protegidos con 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 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.

Cómo crear permisos

Por lo general, debes esforzarte por definir la menor cantidad de permisos posible sin dejar de cumplir con los 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 mediante los permisos existentes.

Si debes crear 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 el nuevo permiso todavía es obligatorio, se declara en el manifiesto de la aplicación mediante el elemento <permission>. Las aplicaciones que deseen usar el permiso nuevo pueden hacer referencia a este agregando un elemento <uses-permission> en los archivos de manifiesto correspondientes. 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 string que informe a los usuarios de forma concisa la decisión de seguridad que deberán tomar.
  • La string 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, por lo que desalentamos el uso del nivel de permiso peligroso.

Cómo usar la red

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, en especial, 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.

Cómo usar la red 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 confidenciales, como HttpsURLConnection para tráfico web seguro. Es mejor usar HTTPS en lugar de HTTP siempre que se admita HTTPS en el servidor, ya que los dispositivos móviles con frecuencia se conectan a redes no seguras, como hotspots Wi-Fi públicos.

La comunicación a nivel de socket autenticada y encriptada puede implementarse fácilmente mediante la clase SSLSocket. Dada la frecuencia con la que los dispositivos Android se conectan a redes inalámbricas no seguras por 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 te recomendamos usar 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. Es peor vincular INADDR_ANY que usar un bucle invertido, porque tu aplicación puede recibir solicitudes desde cualquier lugar.

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.

Cómo usar la red de telefonía

El protocolo SMS se diseñó principalmente para las comunicaciones entre usuarios y no es adecuado para las aplicaciones que desean transferir datos. Debido a las limitaciones de SMS, deberías usar Google Cloud Messaging (GCM) y redes IP para enviar mensajes de datos de un servidor web a tu app en un dispositivo del usuario.

Ten en cuenta que el protocolo SMS no tiene 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. También debes tener 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.

Cómo realizar la validación de entrada

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 en el nivel de la plataforma que reducen la exposición de las aplicaciones a problemas de validación de entrada, y debes usar esas funciones siempre que sea posible. Ten en cuenta también que esa selección de lenguajes con seguridad de tipos tiende a reducir las probabilidades de que se presenten 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 ASLRDEP, que reducen el impacto de estos errores, pero no resuelven el problema subyacente. Puedes administrar punteros y búferes para evitar estas vulnerabilidades.

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

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 defensa consiste en usar consultas con parámetros, como se discutió en la sección anterior sobre proveedores de contenido. Limitar los permisos a solo lectura o 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 anteriores, deberías asegurarte de usar formatos de datos bien estructurados y verificar que los datos cumplan con el formato esperado. Si bien incluir caracteres o reemplazo de caracteres en la lista negra puede ser una estrategia eficaz, técnicas como esta son propensas a generar errores en la práctica y deben evitarse siempre que sea posible.

Cómo controlar los datos del usuario

En general, el mejor enfoque para la seguridad de los datos del usuario es minimizar el uso de API que accedan a datos sensibles o personales del usuario. Si tienes acceso a los datos del usuario y puedes evitar almacenarlos o transmitirlos, no los almacenes ni los transmitas. Piensa si existe alguna manera para que la lógica de la aplicación se pueda implementar con un hash o una forma no reversible de los datos. Por ejemplo, tu aplicación podría usar el hash de una dirección de correo electrónico como clave primaria a fin de 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 las probabilidades de que los atacantes intenten vulnerar tu aplicación.

Si tu aplicación accede a información personal, como contraseñas o nombres de usuario, recuerda que algunas jurisdicciones podrían solicitar que proporciones una política de privacidad en la que expliques el uso y el almacenamiento de esos datos. Seguir la práctica recomendada de minimizar el acceso a datos del usuario también podría simplificar el cumplimiento.

También debes considerar la posibilidad de que tu aplicación, de forma inadvertida, 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, debes asegurarte de no exponer datos de usuarios de forma inadvertida a otras aplicaciones del dispositivo a través de IPC excesivamente permisivas, archivos que admitan escritura pública o sockets de red. Las IPC sumamente permisivas son un caso especial de datos protegidos con permisos y se analizan en la sección Cómo solicitar permisos.

Si se requiere un GUID, crea un número extenso 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 más detalladamente en el Blog para desarrolladores de Android.

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, las apps de producción deberían limitar el uso de registros. Para implementarlo fácilmente, usa marcas de depuración y clases personalizadas de Log con niveles de registro de configuración sencilla.

Cómo usar 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 (inyecció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 el ejemplo de código, se usa este método, que deberías rediseñar en la aplicación de producción, por lo cual debes quitar esa llamada al método si no es necesaria. De forma predeterminada, WebView no ejecuta JavaScript, de modo que no es posible que se generen secuencias de comandos entre sitios.

Usa addJavaScriptInterface() con especial precaución, ya que permite que JavaScript invoque operaciones normalmente reservadas para aplicaciones de 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 la aplicación accede a datos sensibles con un WebView, te recomendamos usar el método clearCache() para eliminar los archivos almacenados en forma local. También puedes usar encabezados del servidor, como no-cache, para indicar que una aplicación no debe almacenar en caché un contenido determinado.

Los dispositivos con plataformas anteriores a Android 4.4 (API nivel 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 actualizable como se describe en Cómo actualizar tu proveedor de seguridad para protegerte contra vulnerabilidades de SSL. Si tu aplicación debe procesar contenido de la Web abierta, considera proporcionar tu propio procesador para poder mantenerlo actualizado con los últimos parches de seguridad.

Cómo administrar credenciales

A fin de 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.

Cuando sea posible, no almacenes nombres de usuarios y contraseñas en el dispositivo. Como alternativa, realiza la autenticación inicial con el nombre de usuario y la contraseña que haya proporcionado este, y luego usa un token de autorización de corta duración específico para el servicio.

Se debe acceder a los servicios disponibles en varias aplicaciones mediante AccountManager. Si es posible, 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; de ese modo, evitarás 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 mediante checkSignature(). Como alternativa, si solo una aplicación usa la credencial, puedes usar un KeyStore para el almacenamiento.

Cómo usar 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 mediante criptografía.

Por lo general, sabes 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.

Para leer y escribir archivos locales de forma más segura, usa la biblioteca de seguridad.

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 Advertencias sobre el uso directo de SSLSocket.

Si resulta que 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. También deberías seguir 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 uso reiterado, usa un mecanismo, por ejemplo, KeyStore, que proporciona un mecanismo a largo plazo de almacenamiento y recuperación de claves criptográficas.

Cómo usar comunicación entre procesos

Algunas apps intentan implementar IPC con métodos tradicionales de Linux, como sockets de red y archivos compartidos. Sin embargo, debes usar la funcionalidad del sistema Android para IPC en su lugar, 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á pensado para que otras aplicaciones puedan usarlo, configura el atributo android:exported en false en el elemento del manifiesto del componente, como para el elemento <service>. Esto resulta útil para aplicaciones que consisten en 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, puedes aplicar una política de seguridad con el elemento <permission>. Si la IPC está entre dos apps independientes firmadas con la misma clave, es preferible usar el nivel de permiso signature en el android:protectionLevel.

Cómo usar 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, asegúrate de que la app sea segura mediante un intent explícito. 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 un permiso 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 a fin de 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 deberían considerarse una función de seguridad. Los componentes pueden invocarse con intents específicos 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 a fin de confirmar que tenga el formato correcto para el receptor, el servicio o la actividad invocados.

Cómo usar 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 lo desees. 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. Para obtener más información sobre JobScheduler, consulta su API-reference documentation.

Un servicio puede proteger llamadas de IPC individuales que reciba mediante permisos, llamando a checkCallingPermission() antes de ejecutar la implementación de esa llamada. Deberías usar permisos declarativos en el manifiesto, ya 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.

Cómo usar las interfaces Binder y Messenger

El uso de Binder o Messenger es el mecanismo recomendado para IPC de estilo RPC en Android. Proporcionan una interfaz bien definida que habilita la autenticación mutua de los extremos si es necesario.

Deberías diseñar las interfaces de tu app de manera tal que no requieran comprobaciones 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 de forma directa. 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 dado. Si llamas a una interfaz proporcionada localmente por tu propia aplicación, puede ser útil usar el método clearCallingIdentity(), que enmascara los permisos del emisor contra los permisos de la app, para cumplir con los controles de seguridad internos. Puedes restablecer los permisos del emisor más adelante usando el método restoreCallingIdentity().

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

Cómo usar el receptor de emisión

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

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

Cómo cargar código de manera 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 suma complejidad a la administración de versiones y la prueba de aplicaciones. Además, puede imposibilitar la verificación del comportamiento de una aplicación, por lo cual podría estar prohibida en algunos entornos.

Si tu aplicación carga código de forma dinámica, lo más importante que debes recordar acerca del código cargado dinámicamente es que 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.

El principal riesgo de seguridad asociado con la carga dinámica de código es que este debe provenir de una fuente verificable. Si se incluyen los módulos en tu APK, otras aplicaciones no podrán modificarlos. Esto es así, independientemente de si el código es una biblioteca nativa o una clase cargada con DexClassLoader. Muchas aplicaciones intentan cargar código desde ubicaciones no seguras, por ejemplo, código descargado de la red mediante 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.

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 deberías 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 MV 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 carga dinámica de clases desde fuentes sin verificar, como fuentes de red sin protección o medios de almacenamiento externo, ya que el código puede haberse modificado para incluir un comportamiento malicioso.

Seguridad en código nativo

En general, deberías usar el SDK de Android para el desarrollo de aplicaciones en lugar de usar código nativo con el SDK 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 for Linux and Unix HOWTO.

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. En el nivel más básico, para los desarrolladores familiarizados con Linux, una buena forma de pensar en esto es saber que cada aplicación recibe un UID único 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.