Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Sugerencias de seguridad

Android tiene funciones de seguridad integradas que reducen significativamente la frecuencia y el impacto de los problemas de seguridad de la aplicación. El sistema está diseñado de modo que puedas compilar tus apps con permisos predeterminados del sistema y de archivos, y evitar tener que tomar decisiones difíciles sobre seguridad.

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

  • La zona de pruebas de aplicaciones de Android, que aísla los datos y la ejecución de código de tu app de los de otras apps
  • Un marco de trabajo de aplicaciones con implementaciones sólidas de funcionalidades de seguridad comunes, como criptografía, IPC segura y permisos
  • 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.

Almacenamiento de datos

El problema de seguridad más común para una aplicación en Android es 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.

Uso del almacenamiento interno

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

Como regla general, evita los modos MODE_WORLD_WRITEABLE o MODE_WORLD_READABLE para los archivos IPC, porque no proporcionan la posibilidad de limitar el acceso de datos a aplicaciones en particular y tampoco ofrecen un control del formato de los datos. Si quieres compartir tus 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.

A fin de ofrecer protección adicional para datos confidenciales, puedes encriptar los archivos locales con una clave a la cual la aplicación no pueda acceder directamente. Por ejemplo, puedes colocar una clave en un KeyStore y protegerlo con una contraseña de usuario que no esté almacenada en el dispositivo. Si bien esto no se protege los datos contra un riesgo en la raíz que puede controlar al usuario que ingresa la contraseña, puede brindar protección para un dispositivo perdido sin encriptación del sistema de archivos.

Uso del 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 el almacenamiento externo.

Cuando administres datos desde el almacenamiento externo, deberías realizar una validación de entrada, al igual que lo harías con datos de cualquier fuente que no fuera 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.

Uso de 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 a otras aplicaciones. Si no pretendes permitir que otras aplicaciones accedan a tu ContentProvider, márcalas como android:exported=false en el manifiesto de la aplicación. De lo contrario, configura el atributo android:exported en true para permitir que otras apps accedan a los datos almacenados.

Al crear un ContentProvider que se exporta para que otras aplicaciones lo usen, puedes especificar un solo permiso de lectura y escritura, o puedes especificar diferentes permisos de lectura y escritura. 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 solo entre tus apps, es preferible que uses el atributo android:protectionLevel configurado a 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 declarando el atributo android:grantUriPermissions y usando los indicadores FLAG_GRANT_READ_URI_PERMISSION y FLAG_GRANT_WRITE_URI_PERMISSION en el objeto Intent que activa el componente. El elemento <grant-uri-permission> puede aumentar el límite del alcance de estos permisos.

Cuando accedas a un proveedor de contenido, usa métodos de consulta con parámetros como query(), update() y delete() a fin de evitar la posible inyección de SQL desde fuentes no seguras. Ten en cuenta que el uso de métodos con parámetros no resulta suficiente si el argumento selection se compiló 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 podrí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.

Uso de permisos

Debido a que Android aísla las aplicaciones entre sí, estas deben compartir recursos y datos explícitamente. 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.

Solicitud de 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 esos permisos correctamente, 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 alguna función sin la que la app no podría funcionar, declárala con un elemento <uses-feature> en el archivo de manifiesto.

Si puedes diseñar tu aplicación de modo que no requiera permisos, hazlo. 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 Control de datos de usuario). Como alternativa, en lugar de usar almacenamiento externo (que requiere permiso), puedes guardar datos en el almacenamiento interno.

Además de solicitar permisos, tu aplicación puede usar el elemento <permission> para proteger IPC que conlleve riesgos de seguridad y se exponga 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 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.

Creación de permisos

Por lo general, debes esforzarte por definir la menor cantidad de permisos posible sin dejar de cumplir 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 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 permiten el acceso solo de las aplicaciones firmadas por el mismo desarrollador de la aplicación que realiza la comprobación de permisos. Si todavía es necesario el nuevo permiso, se declara en el manifiesto de la app mediante el elemento <permission>. Las apps que deseen usar el nuevo permiso, pueden agregar el elemento <uses-permission> a los archivos del manifiesto para hacer referencia a él. También puedes agregar permisos de manera dinámica 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 haya instalado el creador del permiso.

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

Uso de 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 podrían ser 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.

Uso de 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 sensibles, como HttpsURLConnection para el 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 autenticada y encriptada a nivel de socket 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 cambio, usa un mecanismo de IPC de Android en el que se pueda realizar la autenticación con 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 emitidas por HTTP.

Uso de red de telefonía

El protocolo SMS se diseñó principalmente para la comunicación entre usuarios y no es adecuado para apps que intenten 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 móvil.

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 Android, los mensajes SMS se transmiten como intents de transmisión, de modo que otras aplicaciones que tengan el permiso READ_SMS podrán leerlos o capturarlos.

Realiza 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 a nivel de 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 los errores de desbordamiento del búfer, de uso después de liberación y por un paso. Android ofrece una variedad de tecnologías, como ASLRDEP, que reducen la posibilidad de explotar esos 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.

Control de 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 del usuario sensibles o personales. 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 usa. Si no sabes por qué un componente o servicio requiere información personal, no la 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. Asegúrate también de no exponer datos de usuarios de forma inadvertida a otras aplicaciones del dispositivo a través de PC excesivamente 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 Solicitud de 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 el IMEI, que puedan estar asociados con información personal. Este tema se analiza con mayor profundidad 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 aplicación con 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. A fin de implementar lo anterior de manera simple, usa indicadores de depuración y clases de Log personalizadas con niveles de registro personalizables.

Uso de WebView

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

Si tu aplicación no usa JavaScript directamente dentro de 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. Por ello, no es posible usar 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, muestra addJavaScriptInterface() solo a páginas web cuyas entradas sean por completo confiables. Si se permiten entradas no seguras, JavaScript no seguro podría invocar métodos de Android en tu app. En general, te recomendamos mostrar addJavaScriptInterface() solo a JavaScript dentro del APK de tu aplicación.

Si tu aplicación accede a datos sensibles con un WebView, te recomendamos usar el método clearCache() para eliminar los archivos almacenados localmente. También puedes usar encabezados del servidor, como no-cache, para indicar que una aplicación no debería almacenar en caché un contenido en particular.

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 temporal, si tu app se ejecuta en esos dispositivos, debe confirmar que los objetos de WebView solo muestren contenido de confianza. Para asegurarte de que tu app no esté expuesta a posibles vulnerabilidades en SSL, usa el objeto Provider de seguridad que se puede actualizar, 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.

Administración de credenciales

A fin de que los ataques de suplantación de identidad 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.

El acceso a los servicios que permitirán acceder a varias aplicaciones debe concretarse 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, incluye CREATOR antes de pasar credenciales a fin de no transmitirlas accidentalmente a la aplicación incorrecta.

Si solo las aplicaciones que tú crees van a usar credenciales, puedes verificar la aplicación que acceda a AccountManager con checkSignature(). De manera alternativa, si solo la aplicación usara la credencial, podrías usar un KeyStore para el almacenamiento.

Uso de 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. Si necesitas recuperar de forma segura un archivo de una ubicación conocida, un simple URI HTTPS puede ser adecuado 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 de SSLSocket directamente.

Si resulta que necesitas implementar tu propio protocolo, no deberías implementar tus propios algoritmos criptográficos. Usa algoritmos criptográficos existentes como los de la implementación de AES o RSA, proporcionados 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 tamaños de 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 con 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 las claves criptográficas, 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 debes almacenar una clave para volver a usarla, emplea un mecanismo como KeyStore, que proporciona un mecanismo para el almacenamiento y la recuperación de claves criptográficas a largo plazo.

Uso de comunicación entre procesos

Algunas apps intentan implementar IPC con métodos tradicionales de Linux, como sockets de red y archivos compartidos. Sin embargo, deberías usar la función del sistema Android para IPC, como Intent, Binder o Messenger con un 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, al igual que 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.

Uso de 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 tu aplicación, puedes 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 usas un intent para vincular un Service, asegúrate de que tu app esté protegida. Para ello, usa 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 (nivel de API 21), el sistema arroja una excepción si llama 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.

Uso de servicios

Un Service generalmente se usa para brindar funcionalidad que usen otras aplicaciones. Cada clase de servicio debe tener una declaración de <service> correspondiente en su 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, la exportación se realiza 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 con el atributo android:permission. Al hacerlo, otras aplicaciones deberán declarar un elemento <uses-permission> correspondiente en su propio manifiesto para poder iniciar y detener un servicio, o realizar la vinculación a este.

Nota: Si tu app se orienta a Android 5.0 (nivel de API 21) y versiones posteriores, deberías usar JobScheduler para ejecutar servicios en segundo plano. Para obtener más información acerca de JobScheduler, consulta su API-reference documentation.

Mediante permisos, un servicio puede proteger llamadas de IPC individuales que reciba; para lo cual debe llamar 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.

Uso de las interfaces Binder y Messenger

El uso de Binder o Messenger es el mecanismo preferido 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 de Binder y Messenger no se declaran dentro del manifiesto de la aplicación y, por lo tanto, no es posible aplicarles permisos declarativos de manera directa. Generalmente, heredan 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 esos controles de manera explícita como código en la interfaz de Binder o Messenger.

Si proporcionas una interfaz que no 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 específico. 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 con respecto a los permisos de la app, para cumplir con controles de seguridad internos. Puedes restaurar los permisos del emisor con el método restoreCallingIdentity().

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

Uso del 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. De esta manera, se evita que las aplicaciones que no cuentan con los permisos adecuados envíen un intent a BroadcastReceiver.

Carga dinámica de código

Desalentamos enfáticamente la carga de código desde fuera del APK de tu aplicación. Esa carga aumenta de manera significativa la probabilidad de que la aplicación se vea comprometida debido a la inyección de código o a la manipulación del 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 los módulos se incluyen en tu APK, otras aplicaciones no podrán modificarlos. Esto es así, independientemente de que el código sea 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. Al hacerlo, 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, una buena manera de pensar en esto para los desarrolladores con conocimientos de Linux es saber que a cada aplicación se le asigna un UID único con permisos muy limitados. Este tema se discute con mayor profundidad en Descripción general de seguridad de Android, y debes familiarizarte con los permisos de aplicaciones aun cuando uses código nativo.