Autenticación con una tarjeta MIFARE DESFire Light usando un Keystore de Android | clave guardada de Lucia Lenz | diciembre 2022
Foto de Muhammad Zaqy Al Fattah en UnsplashSe requiere una clave AES precompartida para la autenticación con tarjetas MIFARE DESFire Light. Esta clave debe mantenerse segura. En Android, el lugar más seguro para la clave es el hardware seguro del dispositivo que utiliza el almacén de claves de Android. El almacén de claves de Android es muy restrictivo. La clave se importa al almacén de claves junto con una lista de las operaciones permitidas. Debido a que la clave no puede salir del hardware seguro, solo se puede usar con algoritmos que están disponibles en el almacén de claves. Estas características tienen dos consecuencias desafiantes. Voy a entrar en la solución a continuación. Este artículo no cubre los detalles de la distribución de claves AES desde un servidor a los dispositivos y su importación al almacén de claves de Android. Este es un tema bastante avanzado en sí mismo. Supongo que ya tienes este mecanismo configurado.Autenticación MIFARE DESFire Light y el desafío del almacén de claves de AndroidRecapitulemos los pasos necesarios para configurar la comunicación cifrada. Solo veremos AuthenticateEV2First. Este es el método de autenticación estándar para este tipo de tarjeta. Para obtener más información, consulte la documentación oficial de nxp.Proceso simplificado para iniciar la comunicación en CommunicationMode.FullEl teléfono prueba a la tarjeta que posee la clave AES sin revelarla y viceversa. El proceso consta de los siguientes pasos:
- El teléfono inicia la autenticación y recibe un número RndB aleatorio encriptado por la tarjeta. Se utiliza un cifrado AES/CBC con la clave AES precompartida y un vector de inicialización (IV) de cero fijo.
- El teléfono descifra RndB y devuelve la prueba junto con un número aleatorio cifrado RndA.
- La tarjeta, a su vez, demuestra que pudo descifrar RndA al enviar la prueba junto con los datos de la sesión original.
- Con los datos de la sesión inicial, las claves de la sesión se calculan utilizando un algoritmo CMAC. Toda la comunicación posterior con la tarjeta se cifra mediante estas claves de sesión, que mutan con cada llamada.
Al pasar por este proceso con una clave de almacén de claves de Android, el primer desafío es que un IV establecido por la persona que llama no está permitido de forma predeterminada, pero es necesario para los pasos 1-3. El segundo desafío es que el algoritmo CMAC requerido para el paso 4 no está disponible en el almacén de claves de Android. Consideremos los dos problemas a la vez.Resolver el desafío IV no permitido proporcionado por la persona que llama.De forma predeterminada, el almacén de claves de Android no permite establecer un IV para un cifrado por parte de la persona que llama. En cambio, el cifrado genera aleatoriamente su propio IV para garantizar que la misma entrada no proporcione la misma salida. Por lo tanto, intentar establecer el IV nulo para nuestro cifrado AES/CBC da como resultado una excepción como esta: java.security.InvalidAlgorithmParameterException: no se permite el IV proporcionado por el autor de la llamada. Esto es bueno si usa la clave para el cifrado. Pero cuando se usa para la autenticación, es crucial que el teléfono y la tarjeta sigan los mismos pasos. Por lo tanto, permitimos un IV proporcionado por la persona que llama para esta clave. La distribución de claves secretas a Android y su importación al almacén de claves de Android se explica en la documentación de Android. En términos generales, la clave AES está cifrada en el lado del servidor con una clave pública creada en el almacén de claves de Android. Luego, la clave AES cifrada se envía de vuelta al dispositivo junto con sus metadatos en formato ASN.1. En el dispositivo, la clave se importa al almacén de claves, donde a su vez se descifra. El ASN.1 para la clave y sus metadatos se ve así: Para nuestro desafío, la parte interesante es la AuthorizationList. Esta propiedad se explica en esta referencia de Android. Allí también encontramos la descripción ASN.1 de AuthorizationList: la propiedad que debemos configurar para proporcionar un IV personalizado se llama CALLER_NONCE y se omite en la documentación, pero se implementa en el keymaster hal de Android. Allí aprendemos que se cuenta con 7. También necesitamos el modo de bloque, que se enumera con 4. Aquí lo estoy agregando a la definición ASN.1 de AuthorizationList: Ahora necesitamos agregar esta propiedad en nuestra implementación que construye la matriz de bytes ASN.1. Solo agregamos callerNonce para la clave AES donde sea necesario. Como se mencionó anteriormente, proporcionar un IV para un cifrado es una mala práctica. Solo lo habilitamos para la clave AES utilizada para la autenticación. Además, solo debe configurar el algoritmo requerido y el modo de bloqueo para asegurarse de que la clave no se use con algoritmos inseguros. Aquí está el fragmento: Usamos Bouncy Castle para crear la matriz de bytes. Si quieres entender ASN.1, lee este excelente artículo. Por supuesto, se requiere mucho más código para crear el SecureKeyWrapper, pero aquí solo nos centraremos en los pasos necesarios para comunicarse con la tarjeta MIFARE. Después de configurar correctamente las propiedades de la clave, la clave AES cifrada debe distribuirse a los dispositivos e importarse al almacén de claves como de costumbre.Resolviendo el desafío del Algoritmo CMAC.El segundo desafío es que no hay un algoritmo CMAC disponible en el almacén de claves de Android. Es imposible usar una clave de Android Keystore fuera de Android Keystore (esa es la razón principal por la que es tan seguro). Por lo tanto, no podemos usar, por ejemplo, la implementación CMAC de Bouncy Castles. La buena noticia es que la única vez que necesitamos la clave AES es cuando ciframos algo con un cifrado AES/CBC/NoPadding. En otras palabras, podemos escribir la función CMAC compatible con el almacén de claves de Android independientemente de la clave en sí: fun cmac(mensaje: ByteArray, cipher: () -> Cipher): ByteArray El cifrado AES/CBC está disponible en el almacén de claves de Android, en realidad lo usamos en el proceso de autenticación. Así que solo tenemos que implementar el resto del algoritmo CMAC nosotros mismos. El papel CMAC original parece muy sofisticado, pero afortunadamente ya hay muchas implementaciones. Me refiero a la implementación de Crypto Swift para entender cómo funciona. Al final, todo se reduce a piratear el mensaje en fragmentos del tamaño de un bloque, algo de desplazamiento de bits, cifrado y Xor’ing. Si implementó el proceso AuthenticateEV2First hasta este punto, es muy fácil.EnvolverFinalmente podemos seguir los pasos 1 a 3 para demostrarle a la tarjeta que el teléfono está autorizado para establecer comunicación encriptada. E incluso podemos calcular las claves de sesión en el paso 4 y comunicarnos encriptados en CommunicationMode.Full. Con todo, no es fácil implementar el proceso con una clave de almacén de claves de Android. ¡Pero al final funciona y la clave AES es lo más segura posible! Gracias a Stefan Schrass y mi equipo en Deutsche Bahn por hacer posible este trabajo.